Cette checklist fournit un cadre opérationnel prêt pour la production, couvrant l'inventaire, les changements de configuration sûrs, la vérification, les modes de défaillance avec chemins de reprise, et une liste consolidée. Chaque étape est liée à des versions Nginx spécifiques (1.20.x–1.26.x stable/mainline), aux topologies de déploiement (bare metal systemd, Docker image officielle, Kubernetes NGINX Ingress Controller), et à des commandes observables avec sorties attendues, signaux d'échec et procédures de retour arrière.
1. Prérequis et hypothèses
Plage de versions et interprétation de nginx -V
Cet article couvre Nginx 1.20.x à 1.26.x (stable et mainline au 2024). Les différences majeures incluent :
- 1.20+ : Support natif de
grpc_pass, variables dansproxy_passsans résolveur explicite pour les noms statiques. - 1.23+ :
nginx -Taffiche les fichiers inclus dans l'ordre de chargement ; modules dynamiques chargés viaload_moduleau niveaumain. - 1.25+ : Support expérimental QUIC/HTTP3 (nécessite compilation avec
--with-http_v3_moduleet BoringSSL/QuicTLS ; absent des binaires officiels standards). - Plus vs OSS : La directive
health_checkdans un blocmatchest exclusive à NGINX Plus. L'OSS utiliseproxy_next_upstreamcombiné à des vérifications externes (ex:nginx_upstream_check_moduletiers ou sondes Kubernetes).
Exécutez nginx -V 2>&1 | head -20 pour capturer les arguments de compilation. Sortie annotée attendue :
nginx version: nginx/1.24.0 (nginx.org)
built by gcc 11.4.0 (Ubuntu 11.4.0-1ubuntu1~22.04)
built with OpenSSL 3.0.2 15 Mar 2022 (TLS 1.3 support)
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx \
--modules-path=/usr/lib/nginx/modules --conf-path=/etc/nginx/nginx.conf \
--error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log \
--with-http_ssl_module --with-http_v2_module --with-http_realip_module \
--with-http_stub_status_module --with-http_gzip_static_module \
--with-threads --with-file-aio --with-cc-opt='-g -O2 -ffile-prefix-map=...' \
--with-ld-opt='-Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now'
Signaux d'échec : nginx: command not found (binaire absent du PATH), OpenSSL < 1.1.1 (pas de TLS 1.3), modules attendus manquants (ex: --with-http_v2_module absent).
Matrice de topologie de déploiement
- Topologie — Gestion processus — Chemin config typique — Commande rechargement — Particularités
- Bare metal (systemd) — Master/worker via systemd —
/etc/nginx/—systemctl reload nginx—CAP_NET_BIND_SERVICErequis pour ports 80/443 sans root ;LimitNOFILEdans unit file - Docker (image officielle
nginx:1.24-alpine) — Master/worker dans conteneur (PID 1) —/etc/nginx/(volume monté) —docker kill -s HUP <container>ounginx -s reloadviadocker exec— Lecture seule possible sur/etc/nginx;docker inspectpour vérifier mounts - Kubernetes (NGINX Ingress Controller v1.9.x → Nginx 1.25.x) — Déploiement
Deployment/DaemonSet— ConfigMapnginx-configuration+ TLS Secrets —kubectl rollout restart deployment/ingress-nginx-controller -n ingress-nginx— Configuration pilotée par ConfigMap/Annotations ; pas d'accès direct aux fichiers ;kubectl execpournginx -t
Outils requis
- Bare metal :
nginx,systemctl,openssl,curl,jq,ss(iproute2),audit2allow(SELinux),logrotate. - Docker :
dockerCLI,docker exec,docker logs. - Kubernetes :
kubectl(version compatible cluster),kubectl exec,kubectl logs,kubectl describe. - Commun : Accès lecture
/var/log/nginx/(ou stdout conteneur), droitssudopour écriture config/reload,ulimit -n≥ 65535 pour workers.
Base de sécurité (baseline)
- TLS : 1.2+ uniquement (
ssl_protocols TLSv1.2 TLSv1.3;), suites fortes (ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:...),ssl_prefer_server_ciphers off(TLS 1.3). - En-têtes :
server_tokens off;,add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;,add_header X-Frame-Options SAMEORIGIN;. - Rate limiting : Définir zones
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;au niveauhttp. - Droits fichiers : Certificats/clés lisibles par l'utilisateur
worker(ex:www-data:www-data, mode 640 pour clés).
2. Inventaire version et environnement
Commandes de capture d'état (lecture seule)
Exécutez dans l'ordre, horodatez chaque sortie (date -u +%Y-%m-%dT%H:%M:%SZ).
# 1. Horodatage référence
date -u +%Y-%m-%dT%H:%M:%SZ
# 2. Version courte
nginx -v
# Attendu : nginx version: nginx/1.24.0
# Échec : nginx: command not found
# 3. Version détaillée (modules, OpenSSL, compilateur)
nginx -V 2>&1 | tee /tmp/nginx_V_$(date -u +%Y%m%dT%H%M%SZ).log
# 4. Dump configuration complète testée (syntaxe + includes résolus)
nginx -T 2>&1 | tee /tmp/nginx_T_$(date -u +%Y%m%dT%H%M%SZ).log
# Attendu : Sortie massive commençant par "nginx: the configuration file /etc/nginx/nginx.conf syntax is ok"
# Échec : "nginx: [emerg] ..." → syntaxe invalide, ne pas recharger
# 5. Statut service selon topologie
# Bare metal
systemctl status nginx --no-pager
# Attendu : "Active: active (running)" ; PID master visible
# Docker
docker ps --filter "name=nginx" --format "table {{.Names}}\t{{.Status}}\t{{.Image}}"
# Kubernetes
kubectl get pods -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx -o wide
kubectl describe pod -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx | head -40
Métriques système à capturer
# Utilisateur worker et limites
ps auxf | grep -E 'nginx: (master|worker)' | grep -v grep
# Attendu : 1 master (root), N workers (www-data/nginx) ; N == worker_processes (ou auto = nb CPU)
# Limites fichiers ouverts (appliquées au master)
cat /proc/$(pgrep -o nginx)/limits | grep "Max open files"
# Attendu : Soft/Hard ≥ 65535 ; sinon ajuster `worker_rlimit_nofile` et systemd `LimitNOFILE`
# Répertoires et permissions
ls -ld /etc/nginx /var/log/nginx /var/cache/nginx
# Attendu : /etc/nginx accessible en lecture par worker ; /var/log/nginx écriture par worker
# SELinux/AppArmor
getenforce 2>/dev/null || aa-status 2>/dev/null | head -5
# Si Enforcing : vérifier `ausearch -m avc -c nginx` pour dénis
Signaux d'échec critiques : nginx -T non-zéro (config cassée), master absent, workers < attendus, limites fichiers < 1024, dénis SELinux avc: denied sur name_bind ou read config.
3. Chemin de configuration sûr
Flux de travail standardisé (zéro temps d'arrêt)
Principe : Observer → Sauvegarder → Modifier (scope minimal) → Tester → Diff → Recharger gracieux → Vérifier → Documenter.
# 1. Horodatage pré-changement
TS=$(date -u +%Y%m%dT%H%M%SZ)
echo "CHANGE_START $TS" | tee -a /var/log/nginx/ops-changelog.log
# 2. Sauvegarde atomique arborescence complète
sudo cp -a /etc/nginx /etc/nginx.bak.$TS
# Alternative Docker : docker cp <container>:/etc/nginx ./nginx.bak.$TS
# Alternative K8s : kubectl get configmap nginx-configuration -n ingress-nginx -o yaml > nginx-cm.bak.$TS.yaml
# 3. Édition ciblée (exemple : timeout upstream API unique)
# Fichier : /etc/nginx/conf.d/api-upstream.conf (scope : un seul location block)
# AVANT
location /api/ {
proxy_pass http://backend-api;
proxy_set_header Host $host;
}
# APRÈS (diff -u)
--- /etc/nginx/conf.d/api-upstream.conf.bak.20240115T100000Z 2024-01-15 10:00:00
+++ /etc/nginx/conf.d/api-upstream.conf 2024-01-15 10:05:00
@@ -3,6 +3,7 @@
proxy_pass http://backend-api;
proxy_set_header Host $host;
+ proxy_read_timeout 60s;
+ proxy_send_timeout 60s;
}
Points de vigilance par version
includeordering : Les fichiers dansconf.d/sont inclus par ordre alphabétique (glob*.conf). Préfixez par00-,10-pour forcer l'ordre.- Modules dynamiques :
load_module modules/ngx_http_js_module.so;doit précéderhttp {}dansnginx.conf(niveaumain). Chemin relatif à--modules-path(nginx -V). - Certificats TLS :
ssl_certificate /etc/nginx/ssl/app.example.com.pem;etssl_certificate_key /etc/nginx/ssl/app.example.com.key;doivent être lisibles par l'utilisateurworker(chown www-data:www-data,chmod 640clé). - Résolution DNS upstream : Si
proxy_pass http://backend.internal;utilise un nom DNS, ajoutezresolver 127.0.0.11 valid=10s ipv6=off;(Docker) ou IP DNS interne +resolver_timeout 5s;dans le bloclocationouserver.
Vérification post-édition et activation
# 1. Test syntaxe (ne change PAS l'état)
sudo nginx -t
# Attendu : "syntax is ok" + "test is successful"
# Échec : "nginx: [emerg] ..." → CORRIGER avant d'aller plus loin
# 2. Diff configuration active vs sauvegarde (valide que SEUL le changement prévu est présent)
sudo nginx -T 2>/dev/null | diff -u /etc/nginx.bak.$TS/nginx.conf - | head -50
# Attendu : Diff ne montre QUE l'ajout de proxy_read_timeout 60s;
# 3. Rechargement gracieux (zero-downtime)
# Bare metal
sudo systemctl reload nginx
# Docker
docker kill -s HUP <container_id>
# Kubernetes (ConfigMap changée)
kubectl rollout restart deployment/ingress-nginx-controller -n ingress-nginx --timeout=60s
# 4. Vérification fonctionnelle immédiate
curl -sS -o /dev/null -w "%{http_code} %{time_total}\n" -H "Host: app.example.com" http://127.0.0.1/api/health
# Attendu : "200 0.045" (code 2xx, latence < seuil)
# Vérification en-tête réponse (si ajouté)
curl -sI -H "Host: app.example.com" http://127.0.0.1/api/health | grep -i "x-op-check"
Retour arrière (rollback) validé
# Si vérification échoue (code 5xx, timeout, config non prise en compte)
echo "ROLLBACK_START $(date -u +%Y%m%dT%H%M%SZ)" | tee -a /var/log/nginx/ops-changelog.log
# 1. Restaurer arborescence
sudo mv /etc/nginx /etc/nginx.failed.$TS
sudo mv /etc/nginx.bak.$TS /etc/nginx
# 2. Valider syntaxe restaurée
sudo nginx -t || { echo "CRITICAL: Rollback config also broken"; exit 1; }
# 3. Recharger config restaurée
sudo systemctl reload nginx
# 4. Confirmer service sain
curl -sS -o /dev/null -w "%{http_code}\n" -H "Host: app.example.com" http://127.0.0.1/api/health
# Attendu : 200
echo "ROLLBACK_COMPLETE $(date -u +%Y%m%dT%H%M%SZ)" | tee -a /var/log/nginx/ops-changelog.log
4. Vérification et diagnostics
Contrôles de santé (Health Checks)
# 1. HTTP/HTTPS avec résolution forcée (bypass DNS, test direct IP)
# Remplacez 192.0.2.10 par l'IP réelle du serveur Nginx (TEST-NET-1)
curl -v --resolve app.example.com:443:192.0.2.10 https://app.example.com/healthz 2>&1 | head -30
# Attendu :
# * Connected to 192.0.2.10 port 443
# * TLSv1.3, cipher TLS_AES_256_GCM_SHA384
# > GET /healthz HTTP/2
# < HTTP/2 200
# < server: nginx
# < x-op-check: ok
# < content-length: 0
# 2. Test syntaxe + statut service
nginx -t && systemctl is-active nginx
# Attendu : "active" (exit 0)
# 3. Ports à l'écoute (master process)
ss -ltnp | grep -E ':(80|443)\s'
# Attendu : LISTEN 0 511 *:80 *:* users:(("nginx",pid=1234,fd=6)) ; pid = master PID
Métriques et observabilité
Module stub_status (OSS, basique) — Activer dans un server block protégé :
location /nginx_status {
stub_status on;
allow 192.0.2.0/24; # Monitoring network only
deny all;
}
curl -s http://192.0.2.10/nginx_status
# Sortie attendue :
# Active connections: 43
# server accepts handled requests
# 12345 12345 56789
# Reading: 6 Writing: 7 Waiting: 30
Exporteur Prometheus (nginx-prometheus-exporter) — Scrape http://exporter:9113/metrics pour nginx_http_requests_total, nginx_upstream_latency_seconds.
Analyse logs accès (latence upstream) :
# Top 10 requêtes les plus lentes (dernières 1000 lignes)
awk '{print $NF, $0}' /var/log/nginx/access.log | tail -1000 | sort -nr | head -10
# Format log recommandé (log_format main_ext) inclut $request_time $upstream_response_time
Validation TLS complète
# Chaîne, expiration, OCSP, SNI
openssl s_client -connect app.example.com:443 -servername app.example.com -verify_return_error < /dev/null 2>&1 | tee /tmp/tls_check.log
# Extraire dates certificat feuille
openssl s_client -connect app.example.com:443 -servername app.example.com < /dev/null 2>/dev/null \
- openssl x509 -noout -dates -subject -issuer -ext subjectAltName
# Attendu :
# notBefore=Jan 15 00:00:00 2024 GMT
# notAfter=Apr 14 23:59:59 2024 GMT
# subject=CN = app.example.com
# issuer=CN = R3, O = Let's Encrypt
# X509v3 Subject Alternative Name: DNS:app.example.com, DNS:www.app.example.com
# Vérifier OCSP Stapling (réponse dans handshake)
openssl s_client -connect app.example.com:443 -servername app.example.com -status < /dev/null 2>/dev/null | grep -A 17 "OCSP Response"
# Attendu : "OCSP Response Status: successful (0x0)" + "Cert Status: good"
Santé workers et corrélation logs
# 1. Cohérence master/workers
ps auxf | grep -E 'nginx: (master|worker)' | grep -v grep
# Attendu : 1 master (root), N workers (www-data) ; N == worker_processes
# 2. Erreurs récentes (alert/emerg/crit)
grep -E '\[(alert|emerg|crit)\]' /var/log/nginx/error.log | tail -20
# Signal échec : "connect() failed (111: Connection refused) while connecting to upstream"
# 3. Codes 5xx dans accès (dernière heure)
awk '$9 ~ /^5[0-9]{2}$/ && $4 > "['$(date -d '1 hour ago' +'%d/%b/%Y:%H')'"' /var/log/nginx/access.log | wc -l
# Seuil alerte : > 1% du trafic ou > 10/min
Signaux d'échec diagnostiques :
- Healthz non-2xx +
upstream timed outdans error.log → Problème upstream/réseau. alert/emergau rechargement → Config invalide non détectée parnginx -t(rare, ex: limite RL partagée épuisée).- Worker crash :
SIGSEGVdans error.log +coredump→ Bug module tiers ou binaire corrompu.
5. Modes de défaillance et récupération
5.1 Erreur de syntaxe sur rechargement
- Signal — Cause typique — Récupération
nginx -téchoue ([emerg]) — Point-virgule manquant, directive inconnue,includefichier manquant — Ne pas recharger. Corriger fichier →nginx -tOK → recharger.nginx -tOK maisreloadéchoue (logs) — Limite système (ulimit), SELinux, port déjà utilisé — Voir sections spécifiques ci-dessous.
5.2 Certificat/clé TLS mismatch ou expiré
# Vérifier correspondance modulus (doit être identique)
openssl x509 -noout -modulus -in /etc/nginx/ssl/app.example.com.pem | openssl md5
openssl rsa -noout -modulus -in /etc/nginx/ssl/app.example.com.key | openssl md5
# Si différents → régénérer paire ou retrouver clé correspondante.
# Remplacement atomique (zéro downtime)
TS=$(date -u +%Y%m%dT%H%M%SZ)
sudo cp /etc/nginx/ssl/app.example.com.pem /etc/nginx/ssl/app.example.com.pem.bak.$TS
sudo cp /etc/nginx/ssl/app.example.com.key /etc/nginx/ssl/app.example.com.key.bak.$TS
# Déployer nouveaux fichiers (certbot, script, manuel) avec bons droits
sudo chown www-data:www-data /etc/nginx/ssl/app.example.com.{pem,key}
sudo chmod 640 /etc/nginx/ssl/app.example.com.key
sudo nginx -t && sudo systemctl reload nginx
# Reverifier
openssl s_client -connect app.example.com:443 -servername app.example.com < /dev/null 2>/dev/null | openssl x509 -noout -dates
5.3 Upstream indisponible (502/504)
# 1. Diagnostic connectivité
# Résolution DNS (si upstream par nom)
dig +short backend-api.internal
# Test TCP direct
nc -zv backend-api.internal 80
# Ou via resolver Nginx (dans location) : vérifier directive `resolver` valide
# 2. Vérifier health_checks (Plus) ou proxy_next_upstream (OSS)
# OSS : proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
# Plus : match block + health_check dans upstream
# 3. Atténuation immédiate : retirer upstream défaillant
# Créer /etc/nginx/conf.d/upstream-disable-bad.conf
upstream backend-api {
# server backend-api.internal:80; # COMMENTÉ
server backup-backend.internal:80 backup; # Si backup défini
}
sudo nginx -t && sudo systemctl reload nginx
# 4. Rétablir dès retour normal (procédure inverse)
5.4 Épuisement ressources (OOM / trop fichiers ouverts)
- Signal — Diagnostic — Action
alert"too many open files" dans error.log —ulimit -nmaster <worker_connections* workers — 1.sudo systemctl edit nginx→ ajouterLimitNOFILE=655352.nginx.conf:worker_rlimit_nofile 65535;3. Redémarrage requis (systemctl restart nginx) carworker_rlimit_nofilene s'applique qu'au démarrage master.- OOM Killer (dmesg:
Kill process nginx) —worker_processestrop élevé ou fuite mémoire module tiers — Réduireworker_processesàautoou nb CPU ; auditer modules tiers ; ajouter swap si absent.
5.5 Échec mise à jour binaire (live upgrade)
Procédure standard Nginx (nécessite binaire remplacé sur disque, ex: /usr/sbin/nginx.new) :
# 1. Démarrer nouveau master (ancien garde workers)
kill -USR2 $(cat /var/run/nginx.pid)
# Attendu : nouveau master PID dans /var/run/nginx.pid.newbin ; anciens workers toujours actifs
# 2. Vérifier nouveau master sain
sleep 2
nginx -t # Test avec NOUVEAU binaire (PATH mis à jour)
curl -sI http://127.0.0.1/healthz # Via nouveau master (même port)
# 3. Arrêt gracieux anciens workers (finissent requêtes)
kill -WINCH $(cat /var/run/nginx.pid.oldbin)
# Attendre vidange (surveiller `ss -ltnp` + logs)
# 4. Arrêt ancien master
kill -QUIT $(cat /var/run/nginx.pid.oldbin)
# ROLLBACK si nouveau master défaillant (étape 2 échoue) :
kill -HUP $(cat /var/run/nginx.pid.oldbin) # Réveille ancien master
kill -QUIT $(cat /var/run/nginx.pid) # Tue nouveau master
5.6 Déni SELinux / AppArmor
# Identifier déni récent
sudo ausearch -m avc -c nginx -ts recent | head -30
# Ou AppArmor
sudo dmesg -T | grep -i nginx | grep -i denied
# Générer module local (SELinux)
sudo audit2allow -M nginx_local < /var/log/audit/audit.log
sudo semodule -i nginx_local.pp
# Vérifier
sudo systemctl reload nginx
6. Checklist d'opérations consolidée
- Phase — Action — Commande — Signal attendu — Signal échec — Rollback / Récupération — Rayon d'impact — Fréquence
- Inventory — Capture version + args compile —
nginx -V 2>&1 \| tee /tmp/inv_V.log— OpenSSL ≥ 1.1.1, modules requis présents —command not found, module manquant — Vérifier installation / PATH — Global (lecture) — Hebdo / Ad-hoc - Inventory — Dump config active testée —
nginx -T 2>&1 \| tee /tmp/inv_T.log— "syntax is ok", includes résolus —[emerg]syntaxe — Ne pas changer config ; auditer diff vs git — Global (lecture) — Quotidien / Ad-hoc - Inventory — Vérification limites workers —
cat /proc/$(pgrep -o nginx)/limits \| grep "open files"— Soft=Hard=65535+ — < 1024 —systemctl edit nginx+LimitNOFILE+ restart — Global — Mensuel - Config — Sauvegarde pré-changement —
sudo cp -a /etc/nginx /etc/nginx.bak.$(date -u +%Y%m%dT%H%M%SZ)— Copie complète, permissions préservées —cp: cannot stat— N/A (pré-requis) — Global (fs) — À chaque changement - Config — Test syntaxe —
sudo nginx -t— "syntax is ok" + "test is successful" —[emerg]+ fichier:ligne — Ne pas recharger ; corriger fichier — Global (validation) — À chaque changement - Config — Diff config vs backup —
nginx -T \| diff -u /etc/nginx.bak.$TS/nginx.conf -— Uniquement changements prévus — Lignes inattendues —mv /etc/nginx.bak.$TS /etc/nginx+ reload — Global — À chaque changement - Config — Rechargement gracieux —
sudo systemctl reload nginx—systemctl is-active nginx= active ; master PID inchangé —failed+ logs[alert]— Rollback config (voir ligne suivante) — Global (workers recyclés) — À chaque changement - Config — Rollback config —
sudo mv /etc/nginx /etc/nginx.failed.$TS && sudo mv /etc/nginx.bak.$TS /etc/nginx && sudo nginx -t && sudo systemctl reload nginx— Healthz 200, config active = backup — Rollback lui-même échoue — Redémarrer service (systemctl restart nginx) — Global — Urgence - Verify — Health check HTTPS (resolve) —
curl -v --resolve app.example.com:443:192.0.2.10 https://app.example.com/healthz— HTTP/2 200,server: nginx, TLS 1.3 — Non-2xx, timeout, TLS error — Vérifier upstream + logs ; rollback config si récent — Unique vhost — Quotidien / Post-changement - Verify — Validation TLS certificat —
openssl s_client -connect app.example.com:443 -servername app.example.com < /dev/null \| openssl x509 -noout -dates—notAfter> 30 jours — Expiré ou < 14 jours — Renouveler cert (certbot) + déployer + reload — Unique vhost — Quotidien (auto) / Hebdo (manuel) - Verify — Analyse erreurs 5xx —
awk '\$9 ~ /^5[0-9]{2}$/' /var/log/nginx/access.log \| tail -100— < 1% requêtes / 5min — Pic > 5% ou > 50/min — Corréler error.log upstream ; désactiver upstream défaillant — Upstream concerné — Continu (alerting) - Recovery — Upstream 502 mitigation — Créer conf upstream sans serveur défaillant →
nginx -t→reload— 502 stop, trafic vers backups/sains — Toujours 502 — Vérifier firewall/SG/DNS upstream ; restaurer conf upstream — Upstream unique — Ad-hoc incident - Recovery — Certificat expiré urgence — Déployer nouveau cert/key (bons droits) →
nginx -t→reload→openssl verify— TLS handshake OK,notAftervalide — Mismatch modulus, droits fichiers — Restaurer ancien cert/key (backup) + reload — Unique vhost — Ad-hoc incident - Recovery — Limite fichiers atteinte —
systemctl edit nginx→LimitNOFILE=65535+nginx.conf: worker_rlimit_nofile 65535;→systemctl restart nginx—cat /proc/<pid>/limits= 65535 — Restart échoue (config) — Revenir unit file + config précédent + restart — Global (restart = drop connections) — Rare (capacity planning)
Liens documentation officiels (placeholders) :
- Core modules :
http://nginx.org/en/docs/ngx_core_module.html - HTTP modules :
http://nginx.org/en/docs/http/ngx_http_core_module.html - SSL/TLS :
http://nginx.org/en/docs/http/ngx_http_ssl_module.html - Upstream :
http://nginx.org/en/docs/http/ngx_http_upstream_module.html - Control signals :
http://nginx.org/en/docs/control.html - Ingress Controller :
https://github.com/kubernetes/ingress-nginx/blob/main/docs/user-guide/nginx-configuration/configmap.md
7. Scénario technique réaliste : Rotation certificat Let's Encrypt zéro-downtime
Contexte : app.example.com servi par Nginx 1.24 sur bare metal (Ubuntu 22.04, systemd). Certificat géré par certbot (standalone ou webroot). Renouvellement automatique via systemd timer mais déploiement manuel contrôlé.
Étapes opérationnelles
# 0. Prérequis : certbot installé, domaine validé, Nginx config existante avec ssl_certificate /etc/nginx/ssl/app.example.com.pem
TS=$(date -u +%Y%m%dT%H%M%SZ)
echo "CERT_ROTATE_START $TS app.example.com" | tee -a /var/log/nginx/ops-changelog.log
# 1. Renouvellement certbot (dry-run d'abord)
sudo certbot renew --dry-run --nginx
# Attendu : "The dry run was successful."
# 2. Renouvellement réel (produit nouveaux fichiers dans /etc/letsencrypt/live/app.example.com/)
sudo certbot renew --nginx --quiet
# Attendu : "Successfully renewed certificate."
# 3. Déploiement atomique vers emplacement Nginx (droits corrects)
sudo cp -L /etc/letsencrypt/live/app.example.com/fullchain.pem /etc/nginx/ssl/app.example.com.pem.new
sudo cp -L /etc/letsencrypt/live/app.example.com/privkey.pem /etc/nginx/ssl/app.example.com.key.new
sudo chown www-data:www-data /etc/nginx/ssl/app.example.com.{pem,key}.new
sudo chmod 644 /etc/nginx/ssl/app.example.com.pem.new
sudo chmod 640 /etc/nginx/ssl/app.example.com.key.new
# 4. Bascule atomique (mv est atomique sur même FS)
sudo mv /etc/nginx/ssl/app.example.com.pem.new /etc/nginx/ssl/app.example.com.pem
sudo mv /etc/nginx/ssl/app.example.com.key.new /etc/nginx/ssl/app.example.com.key
# 5. Validation syntaxe + diff
sudo nginx -t
# Attendu : OK
sudo nginx -T 2>/dev/null | diff -u /etc/nginx.bak.$TS/nginx.conf - | grep -E 'ssl_certificate|ssl_certificate_key'
# Attendu : Aucun diff (chemins identiques) ; seul contenu fichiers a changé
# 6. Rechargement gracieux
sudo systemctl reload nginx
# Attendu : Active (running), master PID stable, nouveaux workers
# 7. Vérification TLS post-reload (FORCER IP pour bypass cache DNS local)
curl -v --resolve app.example.com:443:192.0.2.10 https://app.example.com/healthz 2>&1 | grep -E "HTTP/|server:|TLSv"
# Attendu : HTTP/2 200, server: nginx, TLSv1.3
openssl s_client -connect app.example.com:443 -servername app.example.com < /dev/null 2>/dev/null \
- openssl x509 -noout -dates -subject
# Attendu : notAfter = date future (> 60 jours), subject = CN=app.example.com
# 8. Vérification logs (aucune erreur SSL)
grep -i ssl /var/log/nginx/error.log | tail -5
# Attendu : Aucune ligne récente (dernières 2 min)
echo "CERT_ROTATE_SUCCESS $TS" | tee -a /var/log/nginx/ops-changelog.log
Plan de retour arrière (si étape 7 ou 8 échoue)
echo "CERT_ROTATE_ROLLBACK $(date -u +%Y%m%dT%H%M%SZ)" | tee -a /var/log/nginx/ops-changelog.log
# 1. Restaurer anciens fichiers (certbot garde archives dans /etc/letsencrypt/archive/)
PREV=$(ls -1t /etc/letsencrypt/archive/app.example.com/fullchain*.pem | head -2 | tail -1)
PREV_KEY=$(ls -1t /etc/letsencrypt/archive/app.example.com/privkey*.pem | head -2 | tail -1)
sudo cp -L "$PREV" /etc/nginx/ssl/app.example.com.pem
sudo cp -L "$PREV_KEY" /etc/nginx/ssl/app.example.com.key
sudo chown www-data:www-data /etc/nginx/ssl/app.example.com.{pem,key}
sudo chmod 644 /etc/nginx/ssl/app.example.com.pem
sudo chmod 640 /etc/nginx/ssl/app.example.com.key
# 2. Valider + recharger
sudo nginx -t && sudo systemctl reload nginx
# 3. Reverifier
curl -sS -o /dev/null -w "%{http_code}\n" --resolve app.example.com:443:192.0.2.10 https://app.example.com/healthz
# Attendu : 200
echo "CERT_ROTATE_ROLLBACK_COMPLETE $(date -u +%Y%m%dT%H%M%SZ)" | tee -a /var/log/nginx/ops-changelog.log
Conclusion
Une checklist d'exploitation Nginx production n'a de valeur que si chaque recommandation est liée à une version spécifique, observable par des commandes standard, et réversible via une procédure testée. Copier une commande sans valider les prérequis (version, topologie, permissions, limites système) et sans comparer la sortie réelle au signal attendu n'est pas une procédure d'exploitation — c'est une source d'incidents.
Le flux de travail fiable suit une boucle fermée : Inventorier (état horodaté) → Préparer (sauvegarde + scope minimal) → Valider (syntaxe + diff) → Activer (rechargement gracieux) → Vérifier (healthz + TLS + logs) → Documenter (changelog + rollback testé). Chaque étape définit explicitement son signal de succès, son signal d'échec, et son chemin de récupération avant que l'opérateur n'exécute la commande suivante.
Prochaine action concrète : sélectionnez une vérification à faible risque (ex: nginx -T diff vs backup hebdomadaire, ou healthz --resolve sur un vhost non critique), enregistrez l'état actuel dans un terminal horodaté, exécutez la commande documentée, comparez la sortie au tableau des signaux attendus, et consignez le résultat. C'est ainsi que l'on transforme une checklist en mémoire opérationnelle d'équipe.