Pourquoi Symfony sur VPS plutôt que sur mutualisé ?
Un hébergement mutualisé impose la version PHP du serveur, désactive les extensions jugées « dangereuses » et interdit l'installation de processus persistants. Symfony 7.4 LTS requiert PHP 8.2 au minimum et Symfony 8 PHP 8.4 — des versions que de nombreux mutualisés ne proposent pas encore. Sur VPS, vous choisissez PHP 8.3 dès le premier jour, activez OPcache avec une configuration fine, et installez ext-redis, ext-intl ou ext-amqp selon vos besoins.
Les workers asynchrones (Symfony Messenger) exigent un processus qui tourne en fond, géré par Supervisor ou systemd. Sur mutualisé, ce processus est tout simplement impossible. Sur VPS, il démarre en une commande et redémarre automatiquement après un crash. Pour une application Symfony qui traite des emails transactionnels, des notifications push ou de l'import de fichiers, c'est une différence non négociable.
Ce que le VPS débloque pour Symfony
- Accès root complet — installez n'importe quelle extension PHP, configurez OPcache à la ligne près et modifiez php.ini sans ticket support
- PHP 8.3 natif — profitez des typed class constants, et de l'attribut
#[Override]sans attendre la migration du serveur partagé - Supervisor et workers persistants — faites tourner
messenger:consumeen continu, avec redémarrage automatique et rotation des logs, pour traiter vos queues sans cron hacké - Redis en local — installez Redis sur le même VPS pour les sessions, le cache Doctrine et les transports Messenger, éliminant ainsi la latence réseau vers un service externe
- Multi-version PHP via ondrej/php — maintenez PHP 8.1 pour une app legacy et PHP 8.3 pour la nouvelle, sur le même hôte, avec des pools FPM séparés
- Xdebug et profiling en staging — activez Blackfire ou Xdebug sur l'environnement de recette sans impacter la prod, dans le même datacenter
- Déploiements atomiques avec Deployer — zéro downtime grâce aux symlinks, rollback en une commande, hooks
dep deployintégrés dans votre pipeline CI/CD
Prérequis chiffrés — ressources et logiciels
Système d'exploitation : Ubuntu 24.04 LTS ou Debian 12. Ces deux distributions bénéficient du dépôt PHP d'Ondřej Surý (PPA ondrej/php sur Ubuntu, packages.sury.org sur Debian) et d'un support long terme compatible avec les cycles de maintenance de Symfony.
PHP : version 8.3 recommandée pour Symfony 7.4 LTS (minimum 8.2). Symfony 8 exige PHP 8.4 : remplacez alors 8.3 par 8.4 dans les noms de paquets. Extensions requises : php8.3-cli, php8.3-fpm, php8.3-mbstring, php8.3-xml, php8.3-curl, php8.3-zip, php8.3-intl, php8.3-opcache. Ajoutez php8.3-redis si vous utilisez Redis, php8.3-pgsql pour PostgreSQL.
Composer : version 2.x obligatoire. Packagist.org ne sert plus les métadonnées de paquets à Composer 1.x.
Base de données : MySQL 8.0+ ou PostgreSQL 16+. Pour une app Symfony standard, 1 Go de RAM suffit généralement pour MySQL comme pour PostgreSQL.
Nginx : 1.24+ depuis les dépôts officiels Ubuntu ou nginx.org.
RAM minimale :
- 1 Go : développement ou app légère sans queues
- 2 Go : production avec Symfony Messenger et un transport Redis
- 4 Go : workers multiples, Redis, sessions haute fréquence
Ports à ouvrir : 80 (HTTP), 443 (HTTPS), 22 (SSH). Fermez tout le reste.
Installer PHP 8.3 et Symfony CLI sur VPS Ubuntu
Mettre à jour le système et installer les dépendances préalables
apt update && apt upgrade -y && apt install -y curl git unzip software-properties-commonAjouter le dépôt ondrej/php (Ubuntu 24.04 fournit déjà PHP 8.3 ; le dépôt est nécessaire pour PHP 8.4 ou plusieurs versions)
add-apt-repository ppa:ondrej/php && apt updateInstaller PHP 8.3-FPM et les extensions requises par Symfony
apt install -y php8.3-fpm php8.3-cli php8.3-mbstring php8.3-xml php8.3-curl php8.3-zip php8.3-intl php8.3-opcache php8.3-redis php8.3-mysqlVérifier la version installée et l'état du service FPM
php --version && systemctl status php8.3-fpmInstaller Composer 2.x globalement
curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer && composer --versionInstaller la Symfony CLI pour créer des projets et vérifier les prérequis
curl -1sLf 'https://dl.cloudsmith.io/public/symfony/stable/setup.deb.sh' | bash && apt install -y symfony-cliVérifier que tous les prérequis Symfony sont satisfaits
symfony check:requirementsInstaller nginx et Redis
apt install -y nginx redis-server && systemctl enable --now nginx redis-server
Déployer une app Symfony en production avec Deployer PHP
Dans votre projet Symfony local, ajouter Deployer comme dépendance de développement
composer require deployer/deployer --devInitialiser la configuration Deployer (choisir le recette `symfony` à la question)
./vendor/bin/dep initÉditer `deploy.php` à la racine du projet — adapter host, chemin de déploiement et branches
require 'recipe/symfony.php';
set('repository', '[email protected]:votre-org/votre-app.git');
host('votre-vps.example.com')
->set('remote_user', 'deploy')
->set('deploy_path', '/var/www/symfony-app');Sur le VPS, créer l'utilisateur de déploiement et lui donner accès à nginx
useradd -m -s /bin/bash deploy && usermod -aG www-data deployLancer le premier déploiement depuis votre machine locale
./vendor/bin/dep deploy votre-vps.example.comVérifier que le symlink `current/` pointe bien vers la dernière release
ls -la /var/www/symfony-app/En cas de problème, effectuer un rollback immédiat vers la release précédente
./vendor/bin/dep rollback votre-vps.example.com
Configuration nginx pour Symfony
Créez le fichier /etc/nginx/sites-available/symfony-app avec ce bloc server minimal, puis activez-le avec un symlink vers sites-enabled/. Seul index.php est transmis à PHP-FPM : un fichier .php déposé ailleurs dans public/ répond 404 au lieu d'être exécuté.
server {
listen 80;
server_name votre-domaine.com;
root /var/www/symfony-app/current/public;
index index.php;
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 $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
internal;
}
# tout autre fichier .php de public/ : 404, jamais exécuté
location ~ \.php$ {
return 404;
}
location ~ /\.ht {
deny all;
}
}Activez le site et testez la configuration avant de recharger nginx :ln -s /etc/nginx/sites-available/symfony-app /etc/nginx/sites-enabled/ && nginx -t && systemctl reload nginx
Pour HTTPS, utilisez Certbot : certbot --nginx -d votre-domaine.com. Certbot modifie automatiquement le bloc server pour rediriger HTTP vers HTTPS et configurer les certificats Let's Encrypt.
Variables d'environnement et sécurité
Symfony lit .env, puis .env.local, puis .env.prod.local. En production, toute valeur sensible doit être dans .env.local — jamais commitée dans le dépôt git.
Variables essentielles en production :
APP_ENV=prod
APP_DEBUG=false
APP_SECRET=<64-chars-hex-aléatoire>
DATABASE_URL="mysql://dbuser:[email protected]:3306/symfony_prod"
MAILER_DSN=smtp://user:[email protected]:587
MESSENGER_TRANSPORT_DSN=redis://127.0.0.1:6379/messagesAPP_SECRET doit être généré aléatoirement : openssl rand -hex 32. Ne jamais réutiliser la valeur par défaut du projet.
APP_DEBUG=false est critique : en mode debug, Symfony expose les traces de pile complètes, les variables de template et les requêtes SQL dans le navigateur — une fuite d'information sévère en production.
Avec le coffre de secrets de Symfony (config/secrets/prod/), les secrets chiffrés peuvent être committés sans risque ; seule la clé de déchiffrement (prod.decrypt.private.php, ou son contenu en base64 dans la variable d'environnement SYMFONY_DECRYPTION_SECRET) reste hors du dépôt, sur le serveur.
Queues asynchrones avec Symfony Messenger et Supervisor
Symfony Messenger permet de différer les tâches lourdes (envoi d'email, génération PDF, webhooks) dans une queue traitée de façon asynchrone. Sur VPS, Supervisor garantit que le worker tourne en permanence et redémarre après un crash.
Installez Supervisor :apt install -y supervisor
Créez /etc/supervisor/conf.d/symfony-messenger.conf :
[program:symfony-messenger]
command=/var/www/symfony-app/current/bin/console messenger:consume async --time-limit=3600
user=deploy
autostart=true
autorestart=true
startretries=3
redirect_stderr=true
stdout_logfile=/var/log/supervisor/symfony-messenger.log
stdout_logfile_maxbytes=10MB
stdout_logfile_backups=5L'option --time-limit=3600 force le worker à se relancer toutes les heures, libérant la mémoire accumulée. Supervisor s'occupe du redémarrage automatique.
Activez et démarrez le worker :supervisorctl reread && supervisorctl update && supervisorctl start symfony-messenger
Pour surveiller l'état : supervisorctl status symfony-messenger
Côté Symfony, configurez le transport Redis dans config/packages/messenger.yaml :
framework:
messenger:
transports:
async:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
retry_strategy:
max_retries: 3
delay: 1000Hardening en 3 commandes. Bloquez l'accès à .git/ dans nginx (ajoutez location ~ /\.git { deny all; } à votre bloc server). Installez fail2ban pour bloquer les scans SSH et les tentatives de connexion répétées : apt install -y fail2ban. Restreignez les permissions du répertoire de déploiement : chmod 750 /var/www/symfony-app && chown -R deploy:www-data /var/www/symfony-app. Enfin, configurez le cache OPcache dans /etc/php/8.3/fpm/conf.d/10-opcache.ini : opcache.enable=1, opcache.memory_consumption=256, opcache.validate_timestamps=0 (désactivez la validation en prod pour les performances maximales — invalidez manuellement après chaque déploiement avec cachetool opcache:reset).
Dépannage — les 4 erreurs les plus fréquentes
1. The environment variable "APP_SECRET" is not set.
APP_SECRET est absent du fichier .env.local sur le serveur. Vérifiez que le fichier existe dans {{deploy_path}}/shared/ et qu'il contient bien cette variable. Deployer crée un symlink vers ce fichier partagé à chaque release — si le symlink est cassé, Symfony ne lit pas .env.local.
2. Erreur 500 silencieuse — logs vides
Avec APP_DEBUG=false, Symfony n'affiche rien dans le navigateur et écrit dans var/log/prod.log. Lisez ce fichier en temps réel : tail -f /var/www/symfony-app/current/var/log/prod.log. Une permission refusée sur var/cache/ ou var/log/ est la cause la plus fréquente — corrigez avec chown -R deploy:www-data var/ depuis le répertoire de la release courante.
3. Connection refused sur la base de données
DATABASE_URL pointe vers 127.0.0.1 mais MySQL écoute sur localhost (socket Unix) — ou l'inverse. Testez la connexion directement : mysql -u dbuser -p -h 127.0.0.1 symfony_db. Si MySQL n'écoute pas sur TCP, ajoutez bind-address = 127.0.0.1 dans /etc/mysql/mysql.conf.d/mysqld.cnf et redémarrez.
4. Unable to write in the "cache" directory
Ce message signifie que Symfony ne peut pas écrire dans var/cache/. Le processus PHP-FPM tourne sous www-data, mais le répertoire appartient à deploy sans permissions d'écriture pour le groupe. Solution : chmod -R 775 var/cache var/log && chown -R deploy:www-data var/.
Déployer depuis le Marketplace ServOrbit
Si vous préférez éviter les heures de configuration manuelle, le template symfony-stack du Marketplace ServOrbit installe automatiquement le socle logiciel de ce guide sur Ubuntu 24.04 : PHP 8.3-FPM et ses extensions, Composer, nginx, PostgreSQL 16, Redis et Supervisor. Il n'y a pas d'interface web : vous disposez d'un accès SSH root pour configurer le bloc server nginx, le worker Supervisor et les permissions comme décrit plus haut.
Le template installe PostgreSQL et non MySQL : écrivez une DATABASE_URL en postgresql://… dans .env.local, avec un APP_SECRET généré par openssl rand -hex 32, ou installez MySQL et php8.3-mysql si votre application en dépend.
Vous gardez un contrôle total : le VPS vous appartient, il n'y a aucun lock-in, et vous pouvez utiliser Deployer PHP exactement comme décrit ci-dessus dès la première connexion SSH.