Déployer une application en production
Ce guide décrit la méthode pour mettre en ligne une application PHP/Symfony sur un serveur Linux (Ubuntu récent) derrière un serveur web (Apache) avec PHP-FPM, TLS et un durcissement de base. Il s'adresse à un lecteur qui sait déjà administrer un serveur : ce sont les étapes et les points d'attention, pas un cours pas-à -pas.
Convention : partout où apparaît
<VOTRE_DOMAINE>, remplacez par votre domaine réel. Les valeurs d'accès (ports SSH, comptes, chemins d'hébergement) dépendent de votre infrastructure et ne doivent jamais être publiées.
Prérequis
- Une distribution Linux à jour (Ubuntu LTS ou récent).
- Un accès
sudo. - Un nom de domaine configuré et pointant vers le serveur.
1) Durcir le serveur
Mettez le système à jour, puis fermez la surface d'attaque.
sudo apt update && sudo apt upgrade -y
Pare-feu (UFW) : n'autorisez que le web et SSH.
sudo apt install ufw sudo ufw allow 'Apache Full' sudo ufw allow ssh # adaptez si vous avez déplacé le port SSH sudo ufw enable sudo ufw status verbose
Protection contre le brute-force SSH (Fail2ban) :
sudo apt install fail2ban
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Activez la prison sshd dans jail.local :
[sshd] enabled = true
sudo systemctl restart fail2ban
2) Installer la stack
Serveur web, PHP et base de données :
sudo apt install -y apache2 php libapache2-mod-php mysql-server php-mysql sudo apt install -y php-pdo php-mysql php-zip php-gd php-mbstring \ php-curl php-xml php-bcmath php-intl php-dom php-simplexml
Activez les modules Apache nécessaires :
sudo a2enmod ssl headers rewrite http2 proxy_fcgi setenvif
Passez en PHP-FPM + MPM event (recommandé en prod : meilleure isolation et
concurrence que mod_php + prefork). Remplacez X.Y par votre version de PHP.
sudo a2dismod phpX.Y mpm_prefork sudo a2enmod mpm_event sudo apt install phpX.Y-fpm sudo a2enconf phpX.Y-fpm sudo systemctl restart apache2 phpX.Y-fpm
3) Configurer le vhost HTTPS
Redirigez tout le trafic HTTP vers HTTPS et déléguez le PHP à FPM via socket.
<VirtualHost *:80> ServerName <VOTRE_DOMAINE> RewriteEngine On RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] </VirtualHost> <VirtualHost *:443> ServerName <VOTRE_DOMAINE> Protocols h2 http/1.1 DocumentRoot /var/www/<VOTRE_APP>/public <Directory /var/www/<VOTRE_APP>/public> AllowOverride None Require all granted FallbackResource /index.php </Directory> <FilesMatch \.php$> SetHandler proxy:unix:/var/run/php/phpX.Y-fpm.sock|fcgi://<VOTRE_DOMAINE> </FilesMatch> SSLEngine on SSLCertificateFile /etc/letsencrypt/live/<VOTRE_DOMAINE>/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/<VOTRE_DOMAINE>/privkey.pem ErrorLog ${APACHE_LOG_DIR}/<VOTRE_DOMAINE>-error.log CustomLog ${APACHE_LOG_DIR}/<VOTRE_DOMAINE>-access.log combined </VirtualHost>
Activez le site et rechargez :
sudo a2ensite <VOTRE_DOMAINE>.conf sudo systemctl reload apache2
4) Obtenir le certificat TLS
Pour un certificat wildcard (<VOTRE_DOMAINE> + *.<VOTRE_DOMAINE>), la
validation DNS-01 est nĂ©cessaire ; elle passe par un plugin certbot adaptĂ© Ă
votre fournisseur DNS.
sudo apt install certbot # + le plugin DNS de votre fournisseur
Renseignez les credentials DNS dans un fichier lisible uniquement par root :
sudo chmod 600 /etc/letsencrypt/dns-credentials.ini
Demandez le certificat (adaptez le plugin --dns-... Ă votre fournisseur) :
sudo certbot certonly --dns-<fournisseur> \ --dns-<fournisseur>-credentials /etc/letsencrypt/dns-credentials.ini \ -d <VOTRE_DOMAINE> -d "*.<VOTRE_DOMAINE>"
Vérifiez le renouvellement automatique :
sudo certbot renew --dry-run
La validation DNS-01 fonctionne sans port 80 ouvert, mais gardez-le pour la redirection HTTP → HTTPS.
5) Déployer le code
Posez des permissions saines : le propriétaire déploie, le groupe du serveur web lit (et écrit là où l'app en a besoin).
sudo chown -R <deployeur>:www-data /var/www/<VOTRE_APP> sudo find /var/www/<VOTRE_APP> -type d -exec chmod 770 {} \; sudo find /var/www/<VOTRE_APP> -type f -exec chmod 660 {} \; sudo find /var/www/<VOTRE_APP> -type d -exec chmod g+s {} \; # setgid : héritage de groupe
Installez les dépendances en mode production :
cd /var/www/<VOTRE_APP> composer install --no-dev --optimize-autoloader
Configurez l'environnement (.env.local). Ne committez jamais ce fichier :
il contient des secrets. Générez APP_SECRET et le mot de passe de base
localement.
APP_ENV=prod APP_DEBUG=false APP_SECRET=<GENERER_UN_SECRET> DATABASE_URL="mysql://<user>:<motdepasse>@127.0.0.1:3306/<base>?charset=utf8mb4" MAILER_DSN=sendmail://default
Appliquez les migrations et préparez le cache :
php bin/console doctrine:migrations:migrate --no-interaction
php bin/console cache:clear
php bin/console cache:warmup
Le piège OPcache (à retenir absolument)
En production, opcache.validate_timestamps=0 est recommandé pour la
performance, mais il fige le code en mémoire : PHP-FPM continue de servir
l'ancien code tant qu'il n'est pas rechargé. Symptôme classique après un déploiement
avec changement de schéma : le CLI et les tests sont verts, mais le web plante
(par ex. une erreur SQL sur une colonne qui vient d'être supprimée).
Ordre sûr de déploiement :
git pull php bin/console doctrine:migrations:migrate --no-interaction php bin/console cache:clear sudo systemctl reload phpX.Y-fpm # indispensable : purge l'OPcache
6) Diagnostics rapides
sudo apachectl -t -D DUMP_VHOSTS # vhosts chargés sudo apache2ctl -t # syntaxe de conf curl -I --http2 https://<VOTRE_DOMAINE>
7) Durcissement HTTP et TLS (aller plus loin)
Une fois l'app en ligne, renforcez la couche HTTP.
En-têtes de sécurité : appliquez une politique standardisée côté serveur web
(HSTS, CSP, X-Content-Type-Options, etc.) via mod_headers. Traitez ce point
dans le guide dédié : Durcir les en-têtes HTTP de sécurité.
OCSP Stapling : réduit la latence TLS côté client en servant la réponse OCSP depuis le serveur.
SSLUseStapling On
SSLStaplingCache shmcb:${APACHE_LOG_DIR}/stapling_cache(128000)
SSLStaplingReturnResponderErrors off
SSLStaplingResponderTimeout 5
openssl s_client -connect <VOTRE_DOMAINE>:443 -servername <VOTRE_DOMAINE> \ -status < /dev/null | grep -A3 "OCSP Response Status" # Attendu : OCSP Response Status: successful
Compression (Brotli + Gzip en repli) :
<IfModule mod_brotli.c> AddOutputFilterByType BROTLI_COMPRESS \ text/html text/plain text/css application/javascript application/json image/svg+xml BrotliCompressionQuality 5 </IfModule> <IfModule mod_deflate.c> AddOutputFilterByType DEFLATE \ text/html text/plain text/css application/javascript application/json image/svg+xml </IfModule>
curl -sI -H "Accept-Encoding: br" https://<VOTRE_DOMAINE> | grep -i content-encoding curl -sI -H "Accept-Encoding: gzip" https://<VOTRE_DOMAINE> | grep -i content-encoding
Cache navigateur des assets versionnés : sur des fichiers dont le nom porte un hash, un cache long est sûr.
<IfModule mod_headers.c> <FilesMatch "\.(css|js|svg)$"> Header set Cache-Control "public, max-age=31536000, immutable" </FilesMatch> </IfModule>
ETAG=$(curl -sI https://<VOTRE_DOMAINE>/favicon.svg | awk -F': ' '/^etag:/ {print $2}') curl -I -H "If-None-Match: $ETAG" https://<VOTRE_DOMAINE>/favicon.svg # 304 si inchangé
Points d'attention transverses
- Rechargez PHP-FPM après tout changement de code ou de schéma — sinon l'OPcache sert du code obsolète (cf. §5).
- Aucun secret dans le dépôt :
.env.local, credentials DNS, clés TLS et clés de déploiement restent hors versionnement, avec des permissions restrictives (600). - BOM UTF-8 en tête d'un fichier
.phpest fatal sous PHP récent (declare(strict_types=1)doit être la toute première instruction) : enregistrez vos.phpsans BOM. - Dossiers inscriptibles partagés (cache, index, fichiers générés) : quand
un process (déploiement) écrit et qu'un autre (serveur web,
www-data) lit et écrit, utilisez un groupe commun +setgidpour que les fichiers créés héritent du bon groupe, ou lancez la génération directement sous le compte du serveur web.