Une procédure fiable pour mettre à niveau ou migrer Nginx sur Linux (Debian/Ubuntu et famille RHEL) repose sur un inventaire précis, un pilote étroit, une validation observable et un retour arrière testé. Ce guide couvre les mises à jour in-place mineures et majeures ainsi que la migration vers un nouvel hôte avec coupure DNS ou load balancer, en fournissant commandes exactes, critères d'acceptation et scénarios de reprise.
Prérequis, versions et hypothèses
Avant toute intervention, figez le contexte technique. Les commandes ci-dessous utilisent des marqueurs <...> à remplacer par vos valeurs réelles.
Système d'exploitation et variante Nginx
- Distributions supportées : Ubuntu 20.04/22.04/24.04, Debian 11/12, RHEL 8/9, AlmaLinux 8/9, Rocky Linux 8/9.
- Variante Nginx : dépôt OS (stable), dépôt officiel nginx.org stable ou mainline. Les builds mainline intègrent les modules récents (HTTP/2, HTTP/3, brotli) mais évoluent plus vite.
- Modules requis :
ngx_http_ssl_module,ngx_http_v2_module,ngx_http_realip_module, modules tiers (ex.ngx_brotli,ngx_http_js_module). Vérifiez leur présence vianginx -V 2>&1 | grep -oE '(ngx_http_[a-z_]+_module|ngx_stream_[a-z_]+_module)'.
Environnement d'exécution
- Gestionnaire de services : systemd (standard) ou SysVinit (hérité). Les exemples ciblent systemd.
- Droits : accès root ou sudo sans mot de passe pour les commandes de service, paquet, réseau et système de fichiers.
- Sécurité Linux : SELinux (Enforcing/Permissive/Disabled) sur famille RHEL ; AppArmor (enforced/complain) sur Debian/Ubuntu. Notez l'état courant :
getenforceouaa-status. - Connectivité : résolution DNS fonctionnelle, accès SSH entre anciens et nouveaux hôtes pour
rsync, ports 80/443 ouverts, port pilote (ex. 8081) autorisé dans le pare-feu et SELinux/AppArmor. - TLS : certificats gérés par Certbot (Let's Encrypt) ou fichiers statiques sous
/etc/sslou/etc/nginx/ssl. Les hooks de renouvellement Certbot doivent être répliqués sur le nouvel hôte.
Versions cibles
- Version actuelle :
<CURRENT_VERSION>(ex. 1.22.1 depuis dépôt Ubuntu 22.04). - Version cible :
<TARGET_VERSION>(ex. 1.26.0 depuis nginx.org mainline). - Remplacez systématiquement ces marqueurs dans les commandes ci-dessous par les valeurs lues via
nginx -vet la politique de paquet cible.
Architecture et mécanique de rechargement
Comprendre le modèle de processus Nginx évite les coupures inattendues lors du rechargement ou de la mise à niveau.
Modèle master/worker
- Un processus master unique lit la configuration, lie les sockets d'écoute et supervise les workers.
- Les workers traitent les connexions. Leur nombre est défini par
worker_processes(généralementauto= nombre de cœurs CPU). - La mémoire partagée (zones
limit_req_zone,proxy_cache,ssl_session_cache) réside dans des segments SHM accessibles à tous les workers.
Rechargement gracieux (SIGHUP vs SIGQUIT)
systemctl reload nginxounginx -s reloadenvoie SIGHUP au master : celui-ci relit la configuration, ouvre de nouveaux sockets si nécessaire, démarre de nouveaux workers avec la nouvelle configuration, puis demande aux anciens workers de se terminer gracieusement (worker_shutdown_timeout, défaut 10s). Les connexions en cours terminent leur traitement.nginx -s quitenvoie SIGQUIT : arrêt gracieux immédiat, les workers terminent les requêtes en cours puis s'arrêtent. Le master ferme les sockets d'écoute.nginx -s stopenvoie SIGTERM : arrêt brutal, connexions coupées.
Impact sur les connexions en vol
- Lors d'un rechargement, les nouveaux workers acceptent le trafic sur les mêmes sockets (héritage de descripteurs via
SO_REUSEPORTou transmission explicite). Les anciens workers drainent leurs connexions pendantworker_shutdown_timeout. - Si la nouvelle configuration change les paramètres d'écoute (ex. ajout
ssl, modification de ports), le master crée de nouveaux sockets. L'ancien socket reste ouvert pour le drainage. - Risque : une configuration invalide empêche le démarrage des nouveaux workers ; le master conserve l'ancienne configuration active.
nginx -tvalide la syntaxe mais ne teste pas la lisibilité des certificats/clés à l'exécution, la connectivité upstream, ni le chargement des modules dynamiques au runtime.
Configuration systemd pour drainage contrôlé Créez un drop-in pour allonger le temps d'arrêt et forcer le signal de rechargement :
systemctl edit nginx
Contenu :
[Service]
ExecReload=/usr/sbin/nginx -s reload
KillSignal=SIGQUIT
TimeoutStopSec=30
Rechargez la configuration systemd : systemctl daemon-reload.
Gestion des sources de dépôts (OS vs nginx.org)
Le choix du dépôt détermine la version, les modules inclus et la cadence de mise à jour. Vérifiez la source actuelle avant de changer.
Vérification de la source active
# Debian/Ubuntu
apt-cache policy nginx
# Affiche : Installed: <CURRENT_VERSION>, Candidate: <VERSION>, Version table avec origines
# RHEL/Fedora
dnf repoinfo nginx 2>/dev/null || yum repoinfo nginx 2>/dev/null
dnf list installed nginx\*
Épingler le dépôt officiel nginx.org (mainline) sur Ubuntu/Debian
# Installer prérequis
sudo apt-get update && sudo apt-get install -y curl gnupg2 lsb-release
# Importer la clé de signature nginx.org
curl -fsSL https://nginx.org/keys/nginx_signing.key | sudo gpg --dearmor -o /usr/share/keyrings/nginx-archive-keyring.gpg
# Ajouter le dépôt mainline pour votre codename
echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/mainline/ubuntu $(lsb_release -cs) nginx" | sudo tee /etc/apt/sources.list.d/nginx-mainline.list
# Priorité optionnelle (pinning) pour préférer nginx.org au dépôt OS
cat <<'EOF' | sudo tee /etc/apt/preferences.d/nginx-mainline
Package: nginx nginx-module-*
Pin: origin nginx.org
Pin-Priority: 900
EOF
sudo apt-get update
apt-cache policy nginx # Vérifier que Candidate provient de nginx.org
Épingler le dépôt officiel nginx.org (mainline) sur RHEL 9 / AlmaLinux 9 / Rocky 9
# Ajouter le dépôt via dnf config-manager
sudo dnf config-manager --add-repo=https://nginx.org/packages/mainline/rhel/9/x86_64/
# Importer la clé GPG
sudo rpm --import https://nginx.org/keys/nginx_signing.key
# Vérifier
dnf repoinfo nginx-mainline
dnf list --available nginx
Basculer du dépôt OS vers nginx.org (ou inverse) sans casser les dépendances
- Sur Debian/Ubuntu :
apt-get install nginx=<TARGET_VERSION>avec la version exacte listée parapt-cache policy nginx. - Sur RHEL :
dnf downgrade nginx-<TARGET_VERSION>oudnf upgrade nginx-<TARGET_VERSION>. - Les modules dynamiques (ex.
nginx-module-brotli,nginx-module-njs) doivent provenir du même dépôt que le paquetnginxprincipal pour correspondre à l'ABI. Mélanger les dépôts casse le chargement des modules.
Liste de contrôle de durcissement sécurité
Appliquez ces vérifications avant et après toute modification.
Configuration TLS
- Protocoles :
ssl_protocols TLSv1.2 TLSv1.3;(désactivez TLSv1, TLSv1.1). - Suites de chiffrement : privilégiez l'ordre serveur avec
ssl_prefer_server_ciphers on;et une liste moderne (ex.ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';). - HSTS :
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;(après validation complète). - OCSP Stapling :
ssl_stapling on; ssl_stapling_verify on; resolver 1.1.1.1 8.8.8.8 valid=300s; resolver_timeout 5s;. - Certificats et clés : permissions
640, propriétaireroot:nginx(ouroot:www-dataselon distro).chmod 640 /etc/letsencrypt/live/<YOUR_DOMAIN>/privkey.pem && chown root:nginx /etc/letsencrypt/live/<YOUR_DOMAIN>/privkey.pem. - Important :
nginx -tne valide pas la lisibilité des fichiers certificat/clé par le worker au runtime (droits, SELinux, chemins). Testez avecopenssl s_client(voir Validation).
Permissions et isolation
- Répertoires web :
755pour dossiers,644pour fichiers, propriétairewww-data:www-data(Debian/Ubuntu) ounginx:nginx(RHEL). - Fichiers de configuration :
644,root:root. Inclus sensibles (clés API, mots de passe upstream) :640,root:nginx. - Journaux :
/var/log/nginxaccessible en écriture par l'utilisateur worker (nginxouwww-data).
SELinux (famille RHEL)
- Booléen proxy :
setsebool -P httpd_can_network_connect 1si Nginx fait du reverse proxy vers backends locaux (autres ports) ou distants. - Ports non standards (ex. pilote 8081) :
semanage port -a -t http_port_t -p tcp 8081. - Contexte fichiers :
restorecon -Rv /etc/nginx /var/log/nginx /var/www /etc/letsencrypt. - Vérification :
sesstatus,ausearch -m avc -ts recentaprès tout échec de démarrage.
AppArmor (Debian/Ubuntu)
- Profil Nginx :
/etc/apparmor.d/usr.sbin.nginx(ounginxselon paquet). Après mise à niveau du binaire, rechargez :systemctl reload apparmorouapparmor_parser -r /etc/apparmor.d/usr.sbin.nginx. - Mode complain pour diagnostic :
aa-complain /etc/apparmor.d/usr.sbin.nginx; repassez en enforce après validation.
En-têtes de sécurité HTTP (couche application)
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=()" always;
# Content-Security-Policy selon votre application
Bases de performance et paramètres de réglage
Capturez une base de référence avant le changement pour détecter les régressions.
Métriques de base (capturez sur 10–15 min en conditions réelles ou charge synthétique)
- Latence p50, p95, p99 (ms) sur endpoint représentatif (
/healthz, asset statique, endpoint proxifié). - Débit (RPS - requêtes par seconde) soutenu.
- Taux d'erreurs 5xx (cible < 0,1 %).
- Utilisation CPU/mémoire par worker (
pidstat -p $(pgrep -d, nginx) 1), sockets en attente (ss -s), file d'attente d'acceptation (netstat -s | grep -i listen).
Paramètres clés à ajuster (dans http {} ou main {})
worker_processes auto;
worker_rlimit_nofile 65535; # Limite descripteurs par worker
events {
worker_connections 4096; # Connexions simultanées par worker
multi_accept on; # Accepter plusieurs connexions par notification
use epoll; # Linux (défaut) ; kqueue sur BSD
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 1000;
client_body_buffer_size 16k;
client_max_body_size 10m;
proxy_buffers 8 16k;
proxy_buffer_size 32k;
proxy_busy_buffers_size 32k;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
# Zones partagées dimensionnées selon trafic
limit_req_zone $binary_remote_addr zone=ratelimit:10m rate=100r/s;
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=cache:100m inactive=60m max_size=1g;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
}
Ajustement post-mise à niveau
- Comparez
nginx -Vavant/après pour détecter les changements de compilateur, options--with-cc-opt,--with-ld-optqui influencent la performance. - Si passage à mainline : HTTP/3 (QUIC) peut être disponible via
--with-http_v3_module(expérimental). N'activez qu'après tests de charge.
Pannes courantes (étendues)
Au-delà des cas classiques, anticipez ces scénarios fréquents lors de sauts de version ou changement de dépôt.
Modules dynamiques manquants
- Symptôme :
nginx: [emerg] module "ngx_http_brotli_filter_module.so" not foundau démarrage ouunknown directive "brotli"dans error.log. - Cause : le paquet de module (
nginx-module-brotli) n'est pas installé, version ABI incompatible (dépôt OS vs nginx.org), ou directiveload_moduleabsente/incorrecte. - Résolution : installez le module depuis le même dépôt que
nginx. Ajoutez en tout début denginx.conf(contexte main) :
load_module modules/ngx_http_brotli_filter_module.so;
load_module modules/ngx_http_brotli_static_module.so;
Vérifiez le chemin réel : ls /usr/lib/nginx/modules/ | grep brotli.
Directives dépréciées ou supprimées
ssl on;(déprécié depuis 1.15, supprimé dans mainline récentes) → utilisezlisten 443 ssl;.ssl_certificate/ssl_certificate_keyen double dans blocsserverimbriqués → consolidez.add_headerdans blocif: comportement modifié (héritage). Préférezmapou blocserverdédié.- Vérifiez :
nginx -T 2>&1 | grep -nE '(ssl on|add_header.*if)'.
Changements de chemins d'inclusion
- Les paquets OS et nginx.org peuvent organiser
/etc/nginxdifféremment (ex.sites-available/sites-enabledvsconf.d/*.conf). - Après installation depuis nginx.org, recréez la structure attendue ou adaptez
includedansnginx.conf:
include /etc/nginx/conf.d/*.conf;
# ou
include /etc/nginx/sites-enabled/*;
Modifications d'unité systemd
- Le paquet nginx.org fournit son propre fichier d'unité (souvent
/usr/lib/systemd/system/nginx.service). Les drop-ins personnalisés (systemctl edit nginx) persistent maisExecReloadouKillSignalpeuvent diverger. - Vérifiez :
systemctl cat nginx | grep -E '(ExecReload|KillSignal|TimeoutStopSec)'.
Rotation des journaux cassée
- Logrotate dépend du signal
USR1pour réouvrir les fichiers (nginx -s reopen). Si le chemin du PID change ou si l'unité systemd ne transmet pas le signal, les logs ne rotent plus. - Testez :
nginx -s reopenpuis vérifiez que de nouveaux descripteurs pointent vers les fichiers rotés (lsof -p $(cat /run/nginx.pid) | grep log).
Liaison IPv6 listen [::]:80
- Sur systèmes sans IPv6 activé ou avec
net.ipv6.bindv6only=1, le socket[::]:80peut échouer ou ne pas lier0.0.0.0:80. - Solution : séparez explicitement
listen 80;etlisten [::]:80 ipv6only=on;ou désactivez IPv6 dans Nginx si non utilisé :listen 80;seul.
Diagnostic approfondi
Lorsque la validation standard échoue, utilisez ces outils pour isoler la cause.
État système et journaux
# État complet du service (sortie longue)
systemctl status nginx -l
# Derniers 100 lignes du journal systemd pour l'unité nginx
journalctl -u nginx -n 100 --no-pager
# Suivi temps réel
journalctl -u nginx -f
Traçage système (strace) sur le master Identifiez les appels système bloquants ou échoués (bind, open, read, signal) :
# Attacher au master PID (nécessite root)
strace -f -e trace=network,signal,file,process -p $(cat /run/nginx.pid) 2>&1 | head -200
Filtrez sur bind, openat, accept, rt_sigaction pour voir la gestion des sockets et signaux.
Localiser l'origine d'une directive
# Affiche la configuration effective avec numéros de ligne et fichiers sources
nginx -T 2>&1 | grep -n '<directive>'
# Exemple : nginx -T 2>&1 | grep -n 'ssl_protocols'
Vérification modules chargés au runtime
# Via /proc/<pid>/maps pour les .so mappés
grep '\.so' /proc/$(cat /run/nginx.pid)/maps | sort -u
Test de connectivité upstream (reverse proxy)
# Depuis l'hôte Nginx, vers le backend
curl -v --max-time 5 http://<UPSTREAM_HOST>:<PORT>/healthz
# Ou via socket Unix
curl -v --unix-socket /var/run/php-fpm.sock http://localhost/healthz
Validation et critères d'acceptation
La validation ne se limite pas à nginx -t. Exécutez cette batterie avant et après le changement, comparez les résultats.
Diff de configuration effective
# Avant changement
sudo nginx -T > /var/tmp/nginx-effective-pre.conf
# Après changement
sudo nginx -T > /var/tmp/nginx-effective-post.conf
diff -u /var/tmp/nginx-effective-pre.conf /var/tmp/nginx-effective-post.conf | head -200
Vérifiez que seules les modifications intentionnelles apparaissent.
Transactions synthétiques (script bash) Créez /usr/local/bin/nginx-smoke-test.sh :
#!/usr/bin/env bash
set -euo pipefail
TARGET_HOST="${1:-127.0.0.1}"
DOMAIN="${2:-<YOUR_DOMAIN>}"
DURATION="${3:-60}"
ENDPOINTS=(
"http://${TARGET_HOST}/healthz"
"https://${TARGET_HOST}/healthz"
"http://${TARGET_HOST}/static/logo.png"
"https://${TARGET_HOST}/api/ping"
)
echo "Starting smoke test for ${DURATION}s against ${TARGET_HOST} (Host: ${DOMAIN})"
for ep in "${ENDPOINTS[@]}"; do
echo "--- Testing ${ep} ---"
seq 1 ${DURATION} | xargs -n1 -P10 -I{} curl -s -o /dev/null -w "http_code=%{http_code} time_total=%{time_total}\n" -H "Host: ${DOMAIN}" "${ep}" 2>/dev/null | \
awk -F'[ =]' '
/http_code=200/ {ok++; sum+=$4; sumsq+=$4*$4; arr[NR]=$4}
/http_code=5[0-9][0-9]/ {err++}
END {
if (NR>0) {
n=NR; mean=sum/n;
# p50/p95 approx via sort (simplified)
# For exact percentiles, pipe to sort | awk
print "Requests:", n, "OK:", ok, "5xx:", err, "Avg latency:", mean
}
}'
done
Rendez-le exécutable : chmod +x /usr/local/bin/nginx-smoke-test.sh. Exécutez : /usr/local/bin/nginx-smoke-test.sh <TARGET_IP> <YOUR_DOMAIN> 120.
Mesure de latence avec curl --write-out (une ligne)
for i in {1..100}; do curl -s -o /dev/null -w "%{http_code} %{time_total}\n" -H "Host: <YOUR_DOMAIN>" https://<TARGET_IP>/healthz; done | awk '$1==200 {print $2}' | sort -n | awk '{a[NR]=$1} END {print "p50:", a[int(NR*0.5)]; print "p95:", a[int(NR*0.95)]; print "p99:", a[int(NR*0.99)]}'
Test de charge court (wrk ou ab)
# wrk (si installé) : 50 RPS pendant 60s, 10 connexions
wrk -t2 -c10 -d60s -R50 -H "Host: <YOUR_DOMAIN>" http://<TARGET_IP>/healthz
# ab (ApacheBench) : 1000 requêtes, 10 simultanées
ab -n 1000 -c 10 -H "Host: <YOUR_DOMAIN>" http://<TARGET_IP>/healthz
Capturez : Requests/sec, Latency p95, Non-2xx responses.
Analyse des journaux d'accès (5xx et latence)
# Comptage codes de réponse sur les 10 dernières minutes
sudo awk '$4 >= "[$(date -d "10 min ago" "+%d/%b/%Y:%H:%M")" && $9 ~ /^5[0-9][0-9]$/ {count[$9]++} END {for (c in count) print c, count[c]}' /var/log/nginx/access.log
# Latence upstream (si log_format inclut $upstream_response_time)
sudo awk '$11 ~ /^[0-9.]+$/ {sum+=$11; cnt++; arr[cnt]=$11} END {if(cnt>0) {asort(arr); print "p50:", arr[int(cnt*0.5)]; print "p95:", arr[int(cnt*0.95)]; print "avg:", sum/cnt}}' /var/log/nginx/access.log
Validation TLS 1.3 et certificat
# Test handshake complet avec SNI
openssl s_client -connect <TARGET_IP>:443 -servername <YOUR_DOMAIN> -tls1_3 -brief < /dev/null
# Résultat attendu : "Protocol : TLSv1.3", "Cipher : TLS_AES_256_GCM_SHA384" (ou équivalent), chaîne de certificat valide, pas d'erreur "verify error".
# Vérification OCSP Stapling
openssl s_client -connect <TARGET_IP>:443 -servername <YOUR_DOMAIN> -status -tls1_3 < /dev/null | grep -A 10 "OCSP Response"
Snapshot d'observabilité (Prometheus/Grafana ou GoAccess)
- Si Prometheus : requête
rate(nginx_http_requests_total{status=~"5.."}[5m])< 0.001 ;histogram_quantile(0.95, rate(nginx_http_request_duration_seconds_bucket[5m]))dans la fourchette baseline ±10 %. - Sinon, rapport GoAccess rapide :
goaccess /var/log/nginx/access.log --log-format=COMBINED -o /tmp/report.html --date-format=%d/%b/%Y --time-format=%H:%M:%S.
Procédures de retour arrière (par stratégie)
Chaque stratégie a son rollback dédié. Préparez les commandes avant l'exécution.
Stratégie 1 : Mise à niveau mineure in-place (dépôt OS)
Déclencheur : service ne démarre pas, erreurs 5xx > seuil, directive inconnue non corrigeable en < 5 min. Procédure :
# 1. Arrêter le service (drain gracieux)
sudo nginx -s quit
# 2. Downgrade paquet vers version exacte précédente
# Debian/Ubuntu
sudo apt-get install -y nginx=<CURRENT_VERSION>
# RHEL
sudo dnf downgrade -y nginx-<CURRENT_VERSION>
# 3. Restaurer la configuration depuis sauvegarde horodatée
sudo tar -C / -xzf /var/backups/nginx-etc-<BACKUP_DATE>.tgz
# 4. Valider et redémarrer
sudo nginx -t && sudo systemctl start nginx
# 5. Vérifier
systemctl status nginx -l && curl -sI -H 'Host: <YOUR_DOMAIN>' http://127.0.0.1/
Délai visé : < 5 minutes.
Stratégie 2 : Mise à niveau majeure in-place avec canary (port pilote)
Déclencheur : pilote sur port 8081 échoue, ou erreurs critiques après bascule du trafic principal. Procédure :
# 1. Désactiver le vhost/port canary
sudo rm /etc/nginx/sites-enabled/example-8081.conf
# 2. Downgrade paquet (même commandes que Stratégie 1)
# 3. Restaurer configuration principale si modifiée
sudo tar -C / -xzf /var/backups/nginx-etc-<BACKUP_DATE>.tgz
# 4. Recharger configuration d'origine
sudo nginx -t && sudo systemctl reload nginx
# 5. Vérifier ports principaux
sudo ss -ltnp | grep -E ':80|:443'
Délai visé : < 10 minutes (inclut downgrade paquet).
Stratégie 3 : Migration vers nouvel hôte (bascule DNS/LB)
Déclencheur : validation post-bascule échoue (5xx, TLS, latence), ou incident applicatif sur nouvel hôte. Procédure :
# 1. Bascule immédiate du trafic vers l'ancien hôte
# Option A : DNS (si TTL faible, ex. 60s)
# Mettre à jour l'enregistrement A/AAAA vers <OLD_HOST_IP> chez votre registrar/DNS provider
# Option B : Load Balancer (instantané)
# Modifier les poids : ancien hôte 100%, nouvel hôte 0% (via API LB ou console)
# 2. Laisser l'ancien hôte en service (il n'a pas été modifié)
# 3. Sur le nouvel hôte : arrêter Nginx pour libérer ressources
ssh <NEW_HOST> "sudo nginx -s quit"
# 4. Investiguer hors ligne sur le nouvel hôte (logs, config, modules)
# 5. Corriger et re-valider avant nouvelle tentative
Délai visé : < 2 minutes pour bascule LB ; < TTL DNS (ex. 60s) pour DNS. Gardez l'ancien hôte intact jusqu'à validation complète (24–48h recommandées).
Arrêt gracieux vs brutal (choix au moment du rollback)
nginx -s quit: drain connexions en cours (respecteworker_shutdown_timeout). Utilisez pour rollback planifié.nginx -s stop: coupure immédiate. Réservé aux cas où le processus est bloqué et ne répond plus aux signaux.
Scénario technique réaliste (parcours bout en bout)
Contexte
- Actuel : Ubuntu 22.04 LTS, Nginx 1.22.1 (dépôt OS
ubuntu/jammy), HTTP/2, TLS 1.2/1.3, module tiersngx_brotlicompilé manuellement (non packagé). - Cible : Nginx 1.26.0 (nginx.org mainline), HTTP/2, TLS 1.3,
ngx_brotlidepuis paquet officielnginx-module-brotli(nginx.org). - Migration : Nouvel hôte
<NEW_HOST>(Ubuntu 24.04), bascule DNS avec TTL 60s, validation charge synthétique 50 RPS pendant 10 min. - Seuil de rollback : > 0,5 % de réponses 5xx sur 5 min consécutives, ou toute erreur TLS handshake.
Étape 1 – Inventaire et sauvegarde (hôte actuel <OLD_HOST>)
# Versions et modules
nginx -V 2>&1 | tee /var/tmp/nginx-build-info.txt
grep -oE 'ngx_http_[a-z_]+_module|ngx_stream_[a-z_]+_module' /var/tmp/nginx-build-info.txt | sort
# Configuration effective
sudo nginx -T > /var/tmp/nginx-effective-$(date +%F).conf
# Sauvegardes
sudo tar -C /etc -czf /var/backups/nginx-etc-$(date +%F).tgz nginx
sudo tar -C /var -czf /var/backups/nginx-web-ssl-$(date +%F).tgz www nginx ssl letsencrypt 2>/dev/null || true
# Baseline performance (10 min)
/usr/local/bin/nginx-smoke-test.sh 127.0.0.1 <YOUR_DOMAIN> 600 > /var/tmp/baseline-$(date +%F).log
Étape 2 – Préparation du nouvel hôte <NEW_HOST>
# Sur NEW_HOST : installer nginx.org mainline + module brotli
curl -fsSL https://nginx.org/keys/nginx_signing.key | sudo gpg --dearmor -o /usr/share/keyrings/nginx-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/mainline/ubuntu noble nginx" | sudo tee /etc/apt/sources.list.d/nginx-mainline.list
sudo apt-get update
sudo apt-get install -y nginx nginx-module-brotli
# Vérifier modules
nginx -V 2>&1 | grep -E 'brotli|ngx_http_v2_module|ngx_http_ssl_module'
Étape 3 – Transfert configuration et TLS
# Depuis OLD_HOST vers NEW_HOST (exécuter sur OLD_HOST)
sudo rsync -a --delete /etc/nginx/ <NEW_HOST>:/etc/nginx/
sudo rsync -a --delete /etc/letsencrypt/ <NEW_HOST>:/etc/letsencrypt/
sudo rsync -a --delete /var/www/ <NEW_HOST>:/var/www/
# Sur NEW_HOST : corriger load_module pour brotli packagé
sudo sed -i '1i load_module modules/ngx_http_brotli_filter_module.so;\nload_module modules/ngx_http_brotli_static_module.so;' /etc/nginx/nginx.conf
# Supprimer d'éventuels anciens load_module manuels
sudo nginx -t
Étape 4 – Validation parallèle sur NEW_HOST (sans trafic production)
# Sur NEW_HOST
sudo systemctl enable --now nginx
sudo ss -ltnp | grep -E ':80|:443'
# Tests locaux
curl -sI http://127.0.0.1/healthz
curl -sI -k https://127.0.0.1/healthz
# Tests avec résolution forcée (depuis OLD_HOST ou poste admin)
curl -sI --resolve <YOUR_DOMAIN>:80:<NEW_HOST_IP> http://<YOUR_DOMAIN>/
curl -sI -k --resolve <YOUR_DOMAIN>:443:<NEW_HOST_IP> https://<YOUR_DOMAIN>/
# Test TLS 1.3
openssl s_client -connect <NEW_HOST_IP>:443 -servername <YOUR_DOMAIN> -tls1_3 -brief < /dev/null
# Charge synthétique 50 RPS / 10 min
/usr/local/bin/nginx-smoke-test.sh <NEW_HOST_IP> <YOUR_DOMAIN> 600
# Vérifier : 0 % 5xx, p95 latency dans ±10 % du baseline
Étape 5 – Bascule DNS (TTL 60s pré-réglé 24h avant)
# Chez votre fournisseur DNS : modifier l'enregistrement A/AAAA de <YOUR_DOMAIN> vers <NEW_HOST_IP>
# Attendre ~1-2 minutes (TTL 60s + marge)
Étape 6 – Surveillance post-bascule (10 min minimum)
# Sur NEW_HOST
sudo tail -f /var/log/nginx/error.log &
# Dans un autre terminal
watch -n 5 'awk '\''$9 ~ /^5[0-9][0-9]$/ {err++} END {print "5xx:", err+0, "/", NR}'\'' /var/log/nginx/access.log'
# Métriques Prometheus/Grafana ou GoAccess snapshot
goaccess /var/log/nginx/access.log --log-format=COMBINED -o /tmp/post-cutover.html --date-format=%d/%b/%Y --time-format=%H:%M:%S
Étape 7 – Décision Go/No-Go
- Go : 5xx < 0,5 %, p95 stable, error.log propre, TLS 1.3 opérationnel, renouvellement Certbot testé (
certbot renew --dry-runsur NEW_HOST). - No-Go : bascule DNS vers
<OLD_HOST_IP>(rollback Stratégie 3), investigation sur NEW_HOST.
Étape 8 – Décommissionnement (après 48h de stabilité)
# Sur OLD_HOST : arrêter Nginx, conserver sauvegardes
sudo nginx -s quit
sudo systemctl disable nginx
# Archiver /var/backups/ vers stockage durable
Référence rapide (commandes essentielles)
Inventaire
nginx -V 2>&1 | grep -oE 'ngx_http_[a-z_]+_module|ngx_stream_[a-z_]+_module' # Modules
sudo nginx -T > /var/tmp/nginx-effective.conf # Config effective
sudo ss -ltnp | grep nginx # Sockets
apt-cache policy nginx / dnf repoinfo nginx # Source paquet
Pilote (port canary 8081)
sudo cp /etc/nginx/sites-available/<SITE>.conf /etc/nginx/sites-available/<SITE>-8081.conf
sudo sed -i 's/listen 80;/listen 8081;/' /etc/nginx/sites-available/<SITE>-8081.conf
sudo ln -sf /etc/nginx/sites-available/<SITE>-8081.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
curl -sI http://127.0.0.1:8081/
Validation
sudo nginx -t # Syntaxe
systemctl status nginx --no-pager # Service
curl -sI -H 'Host: <YOUR_DOMAIN>' http://127.0.0.1/ # HTTP
curl -sI -k --resolve <YOUR_DOMAIN>:443:127.0.0.1 https://<YOUR_DOMAIN>/ # HTTPS
openssl s_client -connect 127.0.0.1:443 -servername <YOUR_DOMAIN> -tls1_3 -brief # TLS 1.3
/usr/local/bin/nginx-smoke-test.sh <TARGET_IP> <YOUR_DOMAIN> 60 # Synthétique 60s
Rollback in-place (mineur/majeur)
sudo nginx -s quit
# Debian/Ubuntu
sudo apt-get install -y nginx=<CURRENT_VERSION>
# RHEL
sudo dnf downgrade -y nginx-<CURRENT_VERSION>
sudo tar -C / -xzf /var/backups/nginx-etc-<BACKUP_DATE>.tgz
sudo nginx -t && sudo systemctl start nginx
Rollback migration (DNS/LB)
# DNS : mettre à jour A/AAAA vers <OLD_HOST_IP>
# LB : poids 100% OLD_HOST, 0% NEW_HOST
ssh <NEW_HOST> "sudo nginx -s quit"
Dépannage
systemctl status nginx -l
journalctl -u nginx -n 100 --no-pager
strace -f -e trace=network,signal -p $(cat /run/nginx.pid)
nginx -T 2>&1 | grep -n '<directive>'
Conclusion
Une mise à niveau ou migration Nginx maîtrisée repose sur la discipline d'un inventaire complet, le choix d'une stratégie adaptée au niveau de risque acceptable, l'exécution d'un pilote observable en isolation, et la préparation de procédures de retour arrière testées et chronométrées. En capturant une base de performance avant le changement, en validant la configuration effective (nginx -T), les modules dynamiques, la lisibilité des secrets TLS et le comportement sous charge synthétique, vous transformez une opération risquée en séquence contrôlée. Conservez l'ancien hôte ou la version précédente jusqu'à ce que la nouvelle configuration ait prouvé sa stabilité sur au moins un cycle de rotation de journaux et un TTL DNS. Documentez chaque itération : versions, commandes, métriques, décisions. C'est ainsi que les mises à niveau Nginx deviennent une routine opérationnelle fiable, non une source d'incidents.