# Symfony on a VPS: Deploy, Nginx Config, and Folder Rights

> Symfony on a VPS: directory layout, Nginx config for public/index.php, rights on var/cache, and common 502 errors.

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](https://www.zevshost.net/en/kb/composer-ustanovka-nastroika/).

## 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](https://www.zevshost.net/en/kb/php-fpm-nastroika-optimizatsiya/).

## 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](https://www.zevshost.net/en/kb/php-composer-autoload-optimizatsiya/).

## Checklist before going live

Before switching the domain to the new release, go through this list:

1. Confirm `APP_ENV=prod` and `APP_DEBUG=0` in the `.env.local` file on the server, not only locally.
2. 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](https://www.zevshost.net/en/kb/php-opcache-nastroika-linux/).
3. Run a smoke test of the key pages right after deployment, before real visitors reach the site.

---

Source: https://www.zevshost.net/en/kb/symfony-nastroika-vps/
