Skip to Content
ConfigNginx

Nginx — Guide pratique

Reverse proxy, serveur de fichiers statiques, SSL/TLS et bonne marge de config — condensé de ce qui sert au quotidien.

Nginx utilise un modèle event-driven avec un processus maître et des workers. La config se lit globalement de haut en bas : un location qui correspond en premier gagne.

Structure du fichier de config

Un bloc serveur typique :

server { listen 80; server_name exemple.com www.exemple.com; root /var/www/exemple.com/public; index index.html index.htm; location / { try_files $uri $uri/ /index.php$is_args$args; } }

Directives essentielles

DirectiveRôle
listenPort et optionnellement adresse/IP à écouter
server_nameDomaines associés à ce bloc (ou _ pour le default)
rootRépertoire racine des fichiers servis
indexFichiers recherchés par défaut (liste d’essai)
try_filesRecherche de fichiers, fallback optionnel

server_name _; (souligné) crée un serveur default qui reçoit toutes les requêtes dont le Host ne correspond à aucun autre bloc. Utile pour renvoyer du 444 (connexion fermée) aux scripts automatisés qui scannent les IP.

Reverse proxy

Rediriger le trafic vers un backend (Node, Python, Go…) :

server { listen 80; server_name api.exemple.com; location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 90s; proxy_connect_timeout 10s; } }

Websockets

Ajouter les en-têtes Upgrade et Connection ci-dessus est indispensable pour que Nginx transmette le handshake WebSocket correctement.

Proxy avec chemin différent

location /api/ { # Supprime "/api" avant de transmettre au backend rewrite ^/api/(.*)$ /$1 break; proxy_pass http://backend:8080; }

Oublier proxy_set_header Host $host; casse les URLs générées par le backend (liens, redirects) qui vont pointer vers l’IP interne au lieu du domaine public.

SSL / Let’s Encrypt

Installation de Certbot

# Avec le package Nginx installé sudo apt install certbot python3-certbot-nginx # Obtenir un certificat pour un domaine sudo certbot --nginx -d exemple.com -d www.exemple.com

Certbot modifie automatiquement le bloc Nginx existant : il ajoute un listen 443 ssl, les directives ssl_certificate, et un redirect HTTP → HTTPS.

Bloc SSL complet

server { listen 443 ssl http2; server_name exemple.com www.exemple.com; ssl_certificate /etc/letsencrypt/live/exemple.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/exemple.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; root /var/www/exemple.com/public; location / { try_files $uri $uri/ /index.php$is_args$args; } } # Redirect HTTP → HTTPS server { listen 80; server_name exemple.com www.exemple.com; return 301 https://$host$request_uri; }

Renouvellement automatique

# Test à blanc (ne modifie rien) sudo certbot renew --dry-run # Recharger Nginx après renouvellement sudo certbot renew --quiet --post-hook "systemctl reload nginx"

Ajouter dans le crontab (sudo crontab -e) :

0 3 * * * certbot renew --quiet --post-hook "systemctl reload nginx"

Let’s Encrypt émet des certificats de 90 jours. Le renouvellement se déclenche à partir de J-30. Tester chaque mois avec --dry-run pour s’assurer que le hook de reload fonctionne.

Locations courantes

Fichiers statiques avec cache

location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff2?)$ { expires 30d; add_header Cache-Control "public, no-transform"; access_log off; gzip_static on; }

Blocs spécifiques par API/Path

location /health { access_log off; return 200 "ok\n"; add_header Content-Type text/plain; } location /admin { allow 10.0.0.0/8; deny all; proxy_pass http://backend:8080; } location ~ /\. { deny all; access_log off; log_not_found off; }

Serveur de fichiers statiques pur (site Vue/React)

server { listen 80; server_name app.exemple.com; root /var/www/app/build; index index.html; location / { try_files $uri $uri/ /index.html; } location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff2?)$ { expires 1y; add_header Cache-Control "public, immutable"; access_log off; } }

Le pattern try_files $uri $uri/ /index.html; est incontournable pour les SPA : toute URL interne (par exemple /dashboard) renvoie index.html et le router client s’occupe du reste.

Bonnes pratiques

Headers de sécurité

add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'" always;
HeaderProtection
X-Frame-OptionsEmpêche l’inclusion dans un <iframe> (clickjacking)
X-Content-Type-OptionsEmpêche le MIME-sniffing du navigateur
X-XSS-ProtectionActive le filtre XSS du navigateur (legacy)
Referrer-PolicyContrôle l’envoi du referrer à des sites tiers
Content-Security-PolicyWhite-liste les sources de scripts/styles

L’attribut always est obligatoire sur add_header pour que les en-têtes soient envoyés même sur les réponses d’erreur (4xx, 5xx). Sans lui, les en-têtes personnalisés ne sont ajoutés qu’aux réponses 2xx/3xx — les en-têtes hérités des blocs parents sont écrasés dès qu’un add_header existe dans le bloc enfant.

Gzip

gzip on; gzip_vary on; gzip_proxied any; gzip_comp_level 6; gzip_min_length 256; gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml image/svg+xml;
DirectiveNote
gzip_comp_level1-9, le niveau 6 est un bon compromis CPU/taille
gzip_min_lengthNe gzippe pas les fichiers < cette taille (le gain est inférieur à l’overhead)
gzip_varyAjoute Vary: Accept-Encoding — obligatoire si un proxy cache sert les réponses

gzip_static on; sert directement les fichiers .gz pré-compressés si présents sur le disque. Idéalement, les builds CI produisent les assets déjà compressés. Évite le CPU au runtime.

Masquer la version

server_tokens off;

Retire la version de Nginx des en-têtes Server: et des pages d’erreur par défaut. Un petit geste anti-reconnaissance.

Pièges fréquents

  • Oublier try_files dans un proxy : sans try_files, Nginx renvoie du 404 sur les URLs SPA internes. Ajouter try_files $uri $uri/ /index.html; avant le location qui fait le proxy.

  • add_header qui écrase les parents : une directive add_header dans un bloc location supprime tous les add_header du bloc parent, pas seulement l’ajoute. Reproduire les headers au niveau souhaité.

  • Port 80 déjà occupé : un deuxième bloc server avec listen 80 et le même server_name provoque l’erreur nginx: [emerg] "server" directive is not allowed here. Vérifier les doublons de server_name.

  • TLSv1.0/TLSv1.1 activés par défaut : sur les anciennes versions de Nginx (avant 1.19.x), les protocoles obsolètes sont autorisés. Forcer ssl_protocols TLSv1.2 TLSv1.3; explicitement.

  • Certbot qui échoue en --nginx : si Nginx n’est pas dans le PATH de Certbot (certains environnements Docker minimalistes), utiliser --webroot -w /var/www/exemple.com -d exemple.com à la place.

  • $uri vs $request_uri : $uri est l’URI décodé et sans query-string ; $request_uri contient la requête brute telle que reçue. Utiliser $request_uri dans un redirect pour préserver les paramètres.

  • Recharger Nginx avec une config invalide : nginx -t doit passer avant systemctl reload nginx. Un reload sur une config invalide ne redémarre pas — les anciens workers continuent de servir l’ancienne config.


Référence : Documentation officielle Nginx . Tester chaque modification avec nginx -t avant de recharger.