Why Sylius Feels Slow on Shared Hosting

Sylius is a Symfony application with a Doctrine ORM layer, a large service container, and a Twig-heavy storefront. Each of those layers has a compilation or hydration cost, and on shared hosting that cost is paid far more often than it should be. When a page takes two or three seconds to return, the problem is rarely the database query itself. It is usually the framework rebuilding metadata, recompiling templates, or re-reading thousands of PHP files from disk because PHP has nothing cached in memory.

The first thing to confirm is the environment. Sylius ships with a dev environment that disables most caching, runs the Symfony profiler on every request, and writes verbose logs. If your site is deployed with APP_ENV=dev, every page load recompiles the container and collects debug data. Open the .env or .env.local file in your document root through cPanel File Manager (use Settings → Show Hidden Files) and verify these two lines:

APP_ENV=prod
APP_DEBUG=0

With prod set, Symfony reads a pre-compiled container and cached templates from the var/cache/prod directory. If that directory is missing or was generated on a different PHP version, Sylius rebuilds it on the first request, and that single request can take five to ten seconds while everyone else waits. The compiled cache is the single largest lever you control, and everything below either feeds it or protects it.

Before changing anything, measure. In DirectAdmin or cPanel open the application's var/log/prod.log file. Symfony logs slow deprecations and errors there, and repeated cache-rebuild warnings are a strong signal that the prod cache is not persisting. You can also append a lightweight timing check by watching response headers in your browser's network tab: a cold request that drops from 4s to 300ms after a second reload almost always means OPcache or the compiled container was empty on the first hit.

Tuning OPcache and PHP Through PHP Selector

Sylius loads several thousand PHP class files per request. Without OPcache, PHP parses and compiles every one of them on each request, which is the most common cause of consistently slow first-byte times on shared hosting. In CloudLinux the fix lives in the PHP Selector, not in php.ini.

In cPanel go to Select PHP Version (Jupiter theme) or in DirectAdmin open PHP Selector under the user menu. First confirm you are on PHP 8.2 or newer, which recent Sylius releases require and which have the fastest OPcache. Then switch to the Options (cPanel) or Extensions tab and enable opcache if it is not already ticked. On the options screen set these values where the panel exposes them:

opcache.enable = 1
opcache.memory_consumption = 256
opcache.max_accelerated_files = 30000
opcache.validate_timestamps = 1
opcache.revalidate_freq = 60
opcache.interned_strings_buffer = 32

The max_accelerated_files value matters more for Sylius than for most CMSs because the class count is so high. If it sits at the default 10000, OPcache evicts files mid-request and you lose the benefit. Raising memory to 256MB keeps the whole application cached. Leave validate_timestamps at 1 on shared hosting so that a deployment or config change is picked up without a full PHP restart, which you cannot trigger yourself.

If the PHP Selector does not expose an OPcache option you need, create or edit a .user.ini file in your document root. This file is read per-directory by LiteSpeed's PHP handler:

memory_limit = 512M
max_execution_time = 120
realpath_cache_size = 4096K
realpath_cache_ttl = 600
opcache.max_accelerated_files = 30000

The realpath_cache settings are underrated for Symfony. Sylius resolves long relative include paths constantly, and a larger realpath cache with a longer TTL removes a surprising amount of filesystem stat overhead. Give .user.ini changes up to five minutes to take effect, since LiteSpeed caches the parsed ini between PHP worker recycles.

Moving Cache and Sessions to Redis

By default Sylius stores its cache and sessions on the filesystem. On shared storage, thousands of small cache files and per-visitor session files create heavy inode pressure and slow reads, especially during traffic spikes. If your Hostiso plan exposes a Redis instance (check cPanel → Redis or ask support for your socket path), moving Sylius to Redis removes that filesystem contention.

Configure the Redis DSN in .env.local so it is not overwritten by updates. Use the socket path or host and port your panel provides:

REDIS_URL=redis://127.0.0.1:6379
# or a unix socket, which is faster on shared boxes:
# REDIS_URL=redis:///home/USER/.redis/redis.sock

Then point Symfony's cache and session handling at Redis in config/packages/cache.yaml and framework.yaml, which you can edit through File Manager:

framework:
    cache:
        app: cache.adapter.redis
        default_redis_provider: '%env(REDIS_URL)%'
    session:
        handler_id: '%env(REDIS_URL)%'

Confirm the redis PHP extension is enabled in the PHP Selector extensions list before doing this, otherwise Sylius will throw a connection error. Redis-backed sessions also fix a subtle problem: filesystem session locking can serialize concurrent requests from the same customer, making an already busy cart page feel slower. Redis avoids that lock contention entirely.

After changing cache configuration, the compiled container is stale. Rebuild it with the Sylius console if your plan allows terminal access in cPanel (Terminal feature) or DirectAdmin, running from your application root:

php bin/console cache:clear --env=prod --no-debug
php bin/console cache:warmup --env=prod

Warmup is the step people skip. cache:clear empties the directory, but the next visitor pays the rebuild cost unless you warm it yourself. Always run warmup immediately after clearing so the compiled container and Twig templates exist before real traffic arrives.

LiteSpeed Caching and Static Asset Delivery

LiteSpeed can serve responses without ever entering PHP, but Sylius is a dynamic storefront with per-user carts, so full-page caching must be scoped carefully. The safe wins are static assets and public catalog pages that do not vary per session. Add cache rules to the .htaccess file in your document root:

<IfModule LiteSpeed>
CacheLookup on

# Long cache for compiled Sylius/Symfony assets
<FilesMatch "\.(css|js|woff2|jpg|jpeg|png|webp|svg|gif|ico)$">
    Header set Cache-Control "public, max-age=2592000"
</FilesMatch>

# Never cache checkout, cart, or admin
RewriteEngine On
RewriteCond %{REQUEST_URI} (checkout|cart|_partial|/admin) [NC]
RewriteRule .* - [E=Cache-Control:no-cache]
</IfModule>

# Compress text responses
<IfModule mod_deflate.c>
    AddOutputFilterByType DEFLATE text/html text/css application/javascript application/json
</IfModule>

The no-cache escape for checkout, cart, and admin routes is what keeps LiteSpeed from serving one customer's cart to another. Sylius stores compiled frontend assets under public/assets and public/media; those are safe to cache aggressively because their filenames change when the content changes. Run php bin/console assets:install --symlink and the Sylius asset build during deployment so that directory is fully populated, otherwise LiteSpeed falls through to PHP for missing files.

Finally, address Doctrine. Sylius benefits enormously from a warmed metadata and query cache, but on shared hosting you should avoid caching that grows unbounded. Point Doctrine's result and metadata caches at the same Redis pool through config/packages/doctrine.yaml, and confirm the storefront is not running with the profiler enabled. If pages are still slow after all of this, open var/log/prod.log and look for repeated slow queries; adding a database index through phpMyAdmin on a frequently filtered product attribute column often removes the last hundreds of milliseconds without touching any server-level configuration.