Running Sulu in a Multi-Server Setup

Sulu is built with horizontal scaling in mind and allows to distribute the load of your application between multiple servers. In such a setup, it is important to configure your application to use centralized solutions for session management, cache management, search indexes and media storage.

Media Storage

Media files that are uploaded in the media section of the Sulu administration interface are stored in the filesystem of the current server by default. If your application runs on multiple servers, you need to configure an external storage adapter for your media files.

# config/packages/sulu_media.yaml
sulu_media:
    storage: s3
    storages:
        s3:
            key: 'your aws s3 key'
            secret: 'your aws s3 secret'
            bucket_name: 'your aws s3 bucket name'
            path_prefix: 'optional path prefix'
            region: 'eu-west-1'

Image Formats

In the Media Storage, only original files are stored. The image formats are generated by Sulu the first time they are requested. That mechanism (reverse proxy cache) is not supported by storages like S3, Blob Storage, etc. So for every server, the image will be regenerated once. As long as no media are removed, that might not be a problem. You can configure bin/console sulu:media:format:cache:cleanup as a cron job per server to ensure that an image format is also removed on all servers.

Still, in Kubernetes/container setups where you might lose the public/uploads/media directory every time a container restarts, you need to find different ways to cache images. You can make the public/uploads/media directory a mounted volume shared between all containers (which might not be good for performance). It is better to use some kind of CDN (Cloudflare, Fastly, etc.) or a reverse proxy cache via Nginx to cache generated image formats.

If you configure a CDN/reverse proxy correctly, you do not require the cron job and can disable the image format filesystem cache completely via:

# config/packages/sulu_media.yaml
sulu_media:
    format_cache:
        save_image: false

Never disable the image format cache if you do not use a CDN or reverse proxy cache that caches files for a very long time.

HTTP Cache

Sulu utilizes the FOSHttpCacheBundle to improve response times by caching rendered pages. This cache is automatically invalidated when the content of a page is changed. To prevent different cache entries on different servers, you need to configure Varnish as a centralized caching proxy.

# config/packages/sulu_http_cache.yaml
sulu_http_cache:
    proxy_client:
        symfony:
            enabled: false
        varnish:
            enabled: true
            servers: [ '192.168.0.10:80' ]

Application Cache

The Symfony cache improves the speed of your application by caching metadata and doctrine results. If your application runs on multiple servers, you need to configure a centralized caching adapter like redis for your app cache. Additionally, if the application runs in different directories on different servers, you need to set a static prefix_seed:

# config/packages/cache.yaml
framework:
    cache:
        app: cache.adapter.redis
        default_redis_provider: 'redis://192.168.0.10:6379'

        # Set "prefix_seed" to use same cache namespace independent of project location:
        # https://symfony.com/doc/current/reference/configuration/framework.html#prefix-seed
        prefix_seed: your_vendor_name/app_name

Search Index

Sulu uses the MassiveSearchBundle for its search functionality on the website and in the administration interface. By default the bundle creates an optimized search index in the filesystem of the current server. To prevent outdated search results, you need to configure Elasticsearch as a centralized search adapter.

# config/packages/massive_search.yaml
massive_search:
    adapter: elastic
    adapters:
        elastic:
            version: 7.13
            hosts: [ '192.168.0.10:9200' ]

Session Management

By default, Symfony stores active sessions in the filesystem of the current server. To prevent random logouts between requests, you need to manage your sessions in a centralized storage that is accessed by all your servers. Have a look at the Store Sessions in a Database section of the Symfony documentation to find out how to store sessions in a database like Redis or MySQL. Alternatively, you can set a centralized session.save_handler directly in your php.ini:

session.save_handler = redis
session.save_path = "tcp://192.168.0.10:6379"