Why Self-Host Symfony on a VPS?
Symfony requires precise PHP configuration: extensions (intl, opcache, pdo), PHP version, memory settings, and permissions on the var/cache and var/log folders. Shared hosting often imposes frozen versions and disables essential extensions, while a PaaS hides the PHP-FPM layer. On a VPS, you choose PHP 8.2/8.3, enable and tune OPcache and preloading for major performance gains, and configure PHP-FPM (number of processes, pm.max_children) according to your load. You also control MySQL or PostgreSQL and the Redis cache for sessions and Doctrine. For a production Symfony application, this level of control directly determines response time.
Concrete Benefits of a Self-Hosted Symfony
- Choice of the PHP version and all required extensions without restriction.
- OPcache and Symfony preloading enabled for significantly reduced response times.
- Fine-tuning of PHP-FPM (
pm.max_children, workers) according to available RAM. - MySQL/PostgreSQL database and Redis cache under your control, with scheduled backups.
- Execution of console commands and Messenger (workers) continuously via systemd or Supervisor.
- Atomic deployments with symbolic links, with no service interruption.
Hardware and Software Prerequisites
A Symfony application with a database runs comfortably on 2 vCPU and 2 GB of RAM; go up to 4 GB if you add Redis, Elasticsearch, or Messenger workers. Install PHP 8.2+ with FPM and the intl, opcache, pdo_mysql, mbstring, xml extensions, along with Composer 2. Nginx serves as the front end, with MySQL 8 or PostgreSQL as the database. You can containerize everything with Docker (php:8.3-fpm image) or install natively. A domain pointed to the VPS is required for TLS. Plan for the APP_ENV=prod environment and asset generation via AssetMapper or Webpack Encore.
Deploy Symfony Step by Step
Prepare the Server and the Code
Over SSH, install PHP-FPM 8.3, the extensions, and Composer (or Docker). Clone the repository and create .env.local with APP_ENV=prod, APP_SECRET, and DATABASE_URL. Run composer install --no-dev --optimize-autoloader.
Compile the Cache and Assets
Run php bin/console cache:clear --env=prod, then cache:warmup. Compile the assets with php bin/console asset-map:compile (AssetMapper) or npm run build (Encore). Check the permissions on var/.
Migrate the Database
Run php bin/console doctrine:migrations:migrate --no-interaction to apply the schema. Configure a Redis connection for sessions and the Doctrine cache if your application needs it.
Configure Nginx and PHP-FPM
Point the Nginx root to public/, route all requests to index.php via try_files, and pass the .php files to the PHP-FPM socket with fastcgi_pass. Set pm.max_children according to RAM and enable OPcache in php.ini.
Secure with an SSL Certificate
Obtain a Let's Encrypt certificate via Certbot for yourdomain.com, force HTTPS, and enable automatic renewal. Block direct access to the config/, src/, and var/ folders at the Nginx level.
Supervise the Messenger Workers
If you use the Messenger component, declare a systemd or Supervisor service that runs php bin/console messenger:consume async continuously, with automatic restart. Schedule a cron job for messenger:stop-workers after deployment in order to reload the new code.
Enable Symfony preloading by pointing opcache.preload to var/cache/prod/App_KernelProdContainer.preload.php in your php.ini. The service container and the framework classes are then loaded once into memory at PHP-FPM startup, which eliminates a significant part of the autoloading cost on each request and noticeably reduces latency in production.