Symfony by default assumes a layout where the entry point lives in the
public/ directory, and everything else — source code, cache,
logs — is not reachable by the web server directly. Mistakes in the Nginx
config or in directory permissions are the most common cause of a white
screen and 502 errors after the first deployment.
Project directory layout
The web server must point strictly at public/, not the
project root — otherwise .env, composer.json, and
the contents of var/ become reachable through the browser. The
var/cache and var/log directories must be
writable by the PHP-FPM user, while vendor/ is built with
Composer on the server itself or shipped together with the release. For the
dependency manager itself, see the article on
installing and configuring
Composer.
Nginx config for Symfony
The key part is the try_files rule, which routes every
request except real files through index.php:
server {
listen 80;
server_name example.com;
root /var/www/example/public;
location / {
try_files $uri /index.php$is_args$args;
}
location ~ ^/index\.php(/|$) {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
internal;
}
location ~ \.php$ {
return 404;
}
}
The internal; line forbids opening /index.php
directly by URL — only through try_files. The PHP-FPM pool settings
themselves, including worker limits, are covered in the article on
PHP-FPM pools and
optimization.
Rights on var/cache and var/log
Symfony writes the service container cache, compiled templates, and logs
into var/. If the PHP-FPM user cannot write to these folders,
the application fails with a permission error on the very first request
after deployment.
| Directory | Owner | Rights | Purpose |
|---|---|---|---|
| var/cache | www-data | 775 | container and template cache |
| var/log | www-data | 775 | application logs |
| public/uploads | www-data | 775 | files uploaded by visitors |
| config, src | deploy user | 755 | source code, read-only for the web |
Release deployment order
Updating the code without warming up the cache means the first visitor after a release waits several seconds for the service container to compile. The command order on the server is:
composer install --no-dev --optimize-autoloader— installs dependencies without dev packages and builds an optimized autoloader.php bin/console cache:clear --env=prod— clears the old cache before the rebuild.php bin/console cache:warmup --env=prod— warms up the cache ahead of time, before the first request arrives.php bin/console doctrine:migrations:migrate --no-interaction— applies database migrations if the release includes any.
composer install --no-dev --optimize-autoloader
php bin/console cache:clear --env=prod
php bin/console cache:warmup --env=prod
php bin/console doctrine:migrations:migrate --no-interaction
Common 502 errors and white screens
After the first deployment, the most common problems are these.
A 502 Bad Gateway usually means Nginx cannot reach the PHP-FPM socket —
either the path in fastcgi_pass does not match the real
socket, or the pool itself crashed from running out of memory. A white
screen with no error text means APP_DEBUG=0 and Symfony is
hiding the details — temporarily check var/log/prod.log
instead of the browser. PSR-4 autoloading is tied to Composer autoloader
optimization — details are in the article on
optimizing the
Composer autoloader.
Checklist before going live
Before switching the domain to the new release, go through this list:
- Confirm
APP_ENV=prodandAPP_DEBUG=0in the.env.localfile on the server, not only locally. - Make sure OPcache is enabled and configured to invalidate on a timer, not on every request — the configuration is described in the article on configuring OPcache.
- Run a smoke test of the key pages right after deployment, before real visitors reach the site.