đ Nginx â Reverse Proxy, DĂ©ploiement Django & SĂ©curitĂ©
Guide complet IDEOâLab : Serveur web, reverse proxy (Gunicorn), SSL & directives.
Vue d'ensemble
Serveur web, Reverse Proxy, asynchrone.
Reverse Proxy Serveur WebArchitecture
Event-driven, non-bloquant, Master/Worker.
Event-driven C10kInstallation (Linux)
apt (Debian/Ubuntu) & dnf (RHEL/Rocky).
Structure Fichiers
nginx.conf, sites-available vs conf.d.
Commandes (CLI)
nginx -t, systemctl reload/restart.
Contextes (http, server)
Hiérarchie http, server, location.
Matching location
Priorité : =, ^~, ~, / (Préfixe).
Directives (root vs alias)
root (ajoute path), alias (remplace path), try_files.
Directives Proxy (Gunicorn)
proxy_pass, proxy_set_header, proxy_params.
Gunicorn & Systemd
Socket UNIX vs Port TCP, gunicorn.service.
Config Django (ComplĂšte)
Fichier sites-available complet (static, media, proxy).
HTTPS (SSL/Certbot)
certbot --nginx, listen 443, HSTS.
Load Balancing (upstream)
upstream, least_conn, ip_hash.
Caching (proxy_cache)
proxy_cache_path, proxy_cache, proxy_cache_valid.
Sécurité (Headers & Limits)
HSTS, X-Frame-Options, limit_req_zone.
Debug: Logs (502, 403)
error.log, access.log, 502 (Gunicorn down), 403 (Perms).
Cheat-sheet Nginx
Commandes CLI & Directives clés.
cheat nginx -tNginx (Engine-X)
Nginx est un serveur web haute performance, open-source. Il est connu pour sa stabilité, sa configuration simple, et sa **trÚs faible consommation de ressources**.
Il domine le web moderne et peut agir comme :
- Serveur Web : Servir des fichiers statiques (HTML, CSS, JS, images).
- Reverse Proxy (Proxy Inverse) : Le cas d'usage le plus courant. Recevoir les requĂȘtes et les transmettre Ă un autre serveur (ex: Gunicorn, Node.js, Apache).
- Load Balancer (Répartiteur de charge) : Répartir le trafic entre plusieurs serveurs d'application.
- Cache HTTP : Mettre en cache les réponses pour accélérer le site.
Nginx vs. Apache (ModĂšle de connexion)
| CritĂšre | Nginx | Apache (worker/prefork) |
|---|---|---|
| Architecture | Asynchrone (Event-Loop) | Synchrone (Processus/Thread par requĂȘte) |
| Connexions | 1 worker gĂšre 1000s de connexions. | 1 worker gĂšre 1 connexion Ă la fois. |
| RAM | TrĂšs faible et prĂ©visible. | ĂlevĂ©e (augmente avec les connexions). |
| Fichiers statiques | ExtrĂȘmement rapide. | Rapide, mais moins que Nginx. |
| Config | Logique (.conf). | Complexe (.htaccess, modules). |
Analogie (Déploiement Django)
Pensez à votre déploiement comme à un restaurant de luxe :
- Django (Le Chef) : S'occupe de la logique compliquée (préparer les plats, parler à la BDD). Ne parle pas aux clients.
- Gunicorn (Le Gérant de Cuisine) : GÚre les "Chefs" (processus Django), s'assure qu'ils travaillent, et les redémarre s'ils échouent. Parle uniquement au Maßtre d'HÎtel.
- Nginx (Le MaĂźtre d'HĂŽtel) : Se tient Ă l'entrĂ©e (Port 80/443). Il accueille le client, gĂšre le vestiaire (SSL), sert les boissons (fichiers statiques), et transmet la commande de plat (requĂȘte dynamique) Ă la cuisine (Gunicorn).
ModĂšle Master / Worker
Nginx utilise un processus Master (lancé en tant que root pour se lier aux ports 80/443) qui lit la configuration et lance plusieurs processus Worker (non-privilégiés, ex: www-data).
ModĂšle Asynchrone (Event-driven)
Chaque Worker est mono-thread et gÚre une boucle d'événements (event loop), un peu comme Node.js. Il peut gérer des milliers de connexions simultanément de maniÚre non-bloquante.
Schéma (Worker Nginx)
RequĂȘte 1 (Client A) ->|
RequĂȘte 2 (Client B) ->| +----------------+
RequĂȘte 3 (Client C) ->| | Worker (1 CPU) |
... (1000s) ->| | (Event Loop) |
+----------------+
| | |
| | +-> (Lit Fichier C, non-bloquant)
| +---> (Attend Proxy B, non-bloquant)
+-----> (Ăcrit Fichier A, non-bloquant)
Ubuntu / Debian
sudo apt update sudo apt install -y nginx
RHEL / Rocky / AlmaLinux
Nginx est dans le dépÎt EPEL (Extra Packages for Enterprise Linux).
# 1. Installer EPEL sudo dnf install -y epel-release # 2. Installer Nginx sudo dnf install -y nginx
Post-Installation (systemd & Firewall)
# Démarrer et activer sudo systemctl enable --now nginx # Vérifier le statut sudo systemctl status nginx # --- Firewall (UFW - Ubuntu) --- sudo ufw allow 'Nginx Full' # (Port 80 & 443) sudo ufw enable # --- Firewall (firewalld - RHEL) --- sudo firewall-cmd --add-service=http --permanent sudo firewall-cmd --add-service=https --permanent sudo firewall-cmd --reload
Structure Debian (sites-available)
Cette structure est la plus propre pour gérer plusieurs sites (Virtual Hosts).
/etc/nginx/
âââ nginx.conf (Fichier global, charge http { ... })
â
âââ sites-available/ (Dossier: Tous vos fichiers de config)
â âââ default
â âââ django_projet
â
âââ sites-enabled/ (Dossier: Configs activĂ©es)
âââ django_projet (Lien symbolique vers ../sites-available/)Flux de travail (Activation d'un site)
# 1. Créer le fichier sudo nano /etc/nginx/sites-available/django_projet # 2. Créer le lien symbolique sudo ln -s /etc/nginx/sites-available/django_projet /etc/nginx/sites-enabled/ # 3. (Désactiver le 'default' s'il existe) sudo rm /etc/nginx/sites-enabled/default # 4. Tester & Recharger sudo nginx -t sudo systemctl reload nginx
Structure RHEL (conf.d)
RHEL/Rocky utilise une structure plus simple. nginx.conf charge tous les fichiers .conf dans /etc/nginx/conf.d/.
/etc/nginx/
âââ nginx.conf (Fichier global, charge conf.d)
â
âââ conf.d/
âââ django_projet.conf
âââ autre_site.confPour dĂ©sactiver un site, il faut renommer le fichier (ex: django_projet.conf.disabled) et recharger Nginx.
nginx -t (Tester)
La commande la plus importante. Ne *jamais* recharger Nginx sans tester la configuration d'abord.
nginx -t (Test) va parser tous vos fichiers (nginx.conf, sites-enabled/*) et vérifier les erreurs de syntaxe (ex: } manquant, directive inconnue).
$ sudo nginx -t nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
reload (Gracieux) vs restart (Coupure)
| Commande | Action | Usage |
|---|---|---|
sudo systemctl reload nginx | (HUP) Charge la nouvelle config, dĂ©marre de nouveaux workers, et arrĂȘte gracieusement les anciens. ZĂ©ro downtime. | 99% du temps (changer un server). |
sudo systemctl restart nginx | (Stop/Start) Tue le processus master. Provoque un downtime. | Rare. (Changer nginx.conf global, user, ports). |
La configuration Nginx est hiérarchique. Les directives sont héritées (Parent -> Enfant).
# (Contexte "main", implicite)
events {
# Contexte 'events'
worker_connections 1024;
}
http {
# Contexte 'http' (s'applique Ă tous les sites)
gzip on;
include /etc/nginx/mime.types;
upstream mon_pool {
# Contexte 'upstream'
server 127.0.0.1:8000;
}
server {
# Contexte 'server' (un site web)
listen 80;
server_name example.com;
location / {
# Contexte 'location' (une route)
proxy_pass http://mon_pool;
}
location /static/ {
# Un autre 'location'
root /var/www/static;
}
}
}Comprendre comment Nginx choisit un bloc location est essentiel. C'est testé dans cet ordre :
| Priorité | Modificateur | Description | Exemple |
|---|---|---|---|
| 1 (Max) | = | Match Exact. Si ça correspond, Nginx s'arrĂȘte ici. | location = /login |
| 2 | ^~ | Match PrĂ©fixe (Long). Si ça correspond, Nginx s'arrĂȘte (ne teste pas les Regex). | location ^~ /static/ |
| 3 | ~ ou ~* | Regex (~ = sensible casse, ~* = insensible). Testés dans l'ordre du fichier. | location ~* \.(jpg|png)$ |
| 4 (Min) | (Aucun) | Match Préfixe (le plus long gagne). | location / |
Exemple (PiĂšge pour Django)
# RequĂȘte: /static/css/style.css
server {
# ...
# 1. Correspond ? Non.
location = / { ... }
# 2. Correspond (Préfixe) ? Oui. Nginx le mémorise.
location /static/ {
alias /var/www/static_root/;
}
# 3. Correspond (Préfixe) ? Oui, mais /static/ est plus long.
location / {
proxy_pass http://gunicorn_socket;
}
}
# Résultat: Le bloc '/static/' est utilisé. (Correct)Objectif : Servir /static/css/style.css depuis le dossier /var/www/projet/static_root/css/style.css (aprÚs collectstatic).
root (Mauvais pour /static/)
root **ajoute** le path de la location au chemin.
location /static/ {
root /var/www/projet/static_root;
}
# RequĂȘte: /static/css/style.css
# Nginx cherche: root + location + URI
# /var/www/projet/static_root + /static + /css/style.css
# Résultat: /var/www/projet/static_root/static/css/style.css (404 Not Found)alias (Correct pour /static/)
alias **remplace** le path de la location par le chemin.
location /static/ {
alias /var/www/projet/static_root/; # (Le '/' final est important)
}
# RequĂȘte: /static/css/style.css
# Nginx remplace '/static/' par l'alias
# Résultat: /var/www/projet/static_root/css/style.css (200 OK)try_files (Le Front-Controller)
Utilisé par les frameworks JS (React, Vue, Angular) ou les CMS (WordPress). Il essaie de trouver un fichier/dossier, et s'il échoue, il renvoie vers index.html ou index.php.
Exemple (React/Angular)
location / {
root /var/www/react-app/dist;
# 1. Cherche le fichier exact (ex: /logo.png)
# 2. Cherche le dossier (ex: /dossier/)
# 3. Si échec, renvoie vers /index.html (le routeur React)
try_files $uri $uri/ /index.html;
}Exemple (PHP/WordPress)
location / {
try_files $uri $uri/ /index.php?$query_string;
}proxy_pass
La directive proxy_pass est le cĆur du reverse proxy. Elle dit Ă Nginx : "ArrĂȘte de chercher un fichier, et passe cette requĂȘte Ă ce serveur backend (upstream)".
C'est la directive utilisĂ©e pour toutes les requĂȘtes dynamiques (Django, Node.js, FastAPI, etc.).
Les Headers Essentiels (proxy_params)
Quand Nginx transfĂšre la requĂȘte Ă Gunicorn, Gunicorn ne voit que Nginx (127.0.0.1). Il ne voit ni le vrai client (80.1.2.3), ni le vrai nom d'hĂŽte (ideolab.com).
On doit recopier ces en-tĂȘtes (headers) pour Django (ALLOWED_HOSTS, logs, etc.).
location / {
# 1. TransfĂ©rer la requĂȘte
proxy_pass http://unix:/run/gunicorn.sock;
# 2. Recopier les headers
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;
}| Header | Variable Nginx | Info transmise Ă Django |
|---|---|---|
| Host | $host | Le nom d'hĂŽte (ex: ideolab.com). |
| X-Real-IP | $remote_addr | L'IP réelle du client (80.1.2.3). |
| X-Forwarded-For | $proxy_add_x... | Liste des IPs (si plusieurs proxys). |
| X-Forwarded-Proto | $scheme | http ou https. (Important pour SECURE_PROXY_SSL_HEADER). |
Serveur WSGI (Web Server Gateway Interface)
Le manage.py runserver de Django est un serveur de **développement**. Il est lent, mono-thread, et non sécurisé.
En production, Nginx (client) ne sait pas parler Ă Django (Python). On utilise un **serveur WSGI** (comme Gunicorn) pour faire le lien.
(RequĂȘte) -> Nginx -> (Socket) -> Gunicorn -> (WSGI) -> Django
Socket (UNIX) vs TCP (Réseau)
Il y a deux façons de faire communiquer Nginx et Gunicorn (s'ils sont sur la *mĂȘme* machine) :
Socket TCP (Port)
Facile à débugger, mais léger overhead réseau.
# Gunicorn (écoute sur port 8000) gunicorn --bind 127.0.0.1:8000 ... # Nginx proxy_pass http://127.0.0.1:8000;
Socket UNIX (Fichier)
Recommandé en production. Plus rapide (pas d'overhead TCP/IP), et plus sécurisé (permissions de fichier).
# Gunicorn (crée un fichier .sock) gunicorn --bind unix:/run/gunicorn.sock ... # Nginx proxy_pass http://unix:/run/gunicorn.sock;
Service systemd (gunicorn.service)
Créez /etc/systemd/system/gunicorn.service :
[Unit]
Description=Gunicorn daemon for Django Project
After=network.target
[Service]
# Utilisateur/Groupe (doit exister)
User=ideo_user
Group=www-data
# Dossier du 'manage.py'
WorkingDirectory=/home/ideo_user/mon_projet
# Commande (chemin complet venv + socket dans /run)
ExecStart=/home/ideo_user/mon_projet/.venv/bin/gunicorn \
--access-logfile - \
--workers 3 \
--bind unix:/run/gunicorn.sock \
mon_projet.wsgi:application
Restart=always
[Install]
WantedBy=multi-user.targetNote : Le Group=www-data permet à Nginx (qui tourne en www-data) de lire/écrire sur le socket.
Voici le fichier /etc/nginx/sites-available/django_projet complet, qui gÚre les statiques, les médias, et le proxy vers Gunicorn.
server {
listen 80;
server_name ideolab.com www.ideolab.com;
# Taille max des uploads (ex: 50MB)
client_max_body_size 50M;
# --- Section Statique (STATIC_ROOT) ---
# (AprĂšs 'collectstatic')
location /static/ {
# 'alias' est la clé (voir 2.3)
alias /home/ideo_user/mon_projet/static_root/;
expires 1y;
access_log off;
add_header Cache-Control "public, immutable";
}
# --- Section Média (MEDIA_ROOT) ---
location /media/ {
alias /home/ideo_user/mon_projet/media_root/;
expires 7d;
access_log off;
}
# --- Section Principale (Django/Gunicorn) ---
location / {
# Inclure les headers (Host, X-Forwarded-For...)
include /etc/nginx/proxy_params;
# Passer au socket Gunicorn
proxy_pass http://unix:/run/gunicorn.sock;
}
# (Optionnel) Bloquer l'accÚs aux fichiers cachés
location ~ /\. {
deny all;
}
}Certbot (Let's Encrypt)
Certbot est l'outil (gratuit) qui gĂšre l'obtention et le renouvellement des certificats SSL/TLS.
Installation (Ubuntu)
sudo apt install certbot python3-certbot-nginx
Installation (RHEL/Rocky)
sudo dnf install certbot python3-certbot-nginx
Lancement
Le plugin Nginx va lire vos fichiers sites-enabled, trouver vos server_name, et vous demander pour lesquels activer HTTPS.
# (Assurez-vous que votre DNS pointe vers le serveur) sudo certbot --nginx # ... (Suivre les instructions) ... # (Choisir 'Redirect' pour forcer HTTP -> HTTPS)
Config (AprĂšs certbot)
Certbot va modifier votre fichier (ex: /etc/nginx/sites-available/django_projet) pour vous.
server {
server_name ideolab.com www.ideolab.com;
# (Vos locations /static, /media, / restent identiques)
location /static/ { ... }
location /media/ { ... }
location / { ... }
# --- BLOC AJOUTĂ PAR CERTBOT ---
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/ideolab.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ideolab.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# --- FIN BLOC CERTBOT ---
}
# (Certbot ajoute ce bloc pour la redirection HTTP->HTTPS)
server {
if ($host = www.ideolab.com) {
return 301 https://$host$request_uri;
}
if ($host = ideolab.com) {
return 301 https://$host$request_uri;
}
listen 80;
server_name ideolab.com www.ideolab.com;
return 404; # managed by Certbot
}upstream
Le bloc upstream (dĂ©fini dans le contexte http) crĂ©e un "pool" de serveurs backend (ex: plusieurs serveurs Gunicorn, sur la mĂȘme ou diffĂ©rentes machines).
Exemple de configuration
# (Dans nginx.conf ou /etc/nginx/conf.d/upstream.conf)
http {
# 1. Définir le pool de serveurs
upstream django_backend_pool {
server 10.0.0.1:8000;
server 10.0.0.2:8000;
server 10.0.0.3:8000 backup; # (N'est utilisé que si 1&2 tombent)
}
server {
listen 80;
server_name ideolab.com;
location / {
# 2. Passer au pool (au lieu d'une IP)
proxy_pass http://django_backend_pool;
include proxy_params;
}
}
}Méthodes de Load Balancing
Définit *comment* Nginx choisit un serveur dans le pool.
| Méthode | Description | Usage |
|---|---|---|
| (Aucune) | Round Robin (défaut). Tour à tour (1, 2, 1, 2...). | Usage général. |
least_conn | Envoie Ă celui qui a le moins de connexions actives. | Connexions longues. |
ip_hash | Garantit que la mĂȘme IP client va toujours sur le mĂȘme serveur. | Sessions "Sticky" (si Gunicorn ne partage pas les sessions). |
upstream django_backend_pool {
ip_hash;
server 10.0.0.1:8000;
server 10.0.0.2:8000;
}Cache des réponses proxy
Nginx peut mettre en cache (stocker temporairement) les réponses de Gunicorn/Django (ex: une page API ou une page d'accueil) pour ne pas redemander à Django à chaque fois.
1. Définir la zone (dans http)
Ă mettre dans nginx.conf, dans le bloc http.
# proxy_cache_path /chemin [niveaux] keys_zone=NOM:TAILLE inactive=...
proxy_cache_path /var/cache/nginx/django_cache
levels=1:2
keys_zone=DJANGO_CACHE:10m
max_size=1g
inactive=60m;2. Utiliser le cache (dans server / location)
server {
...
location /api/v1/public_data/ {
# 1. Utiliser cette zone de cache
proxy_cache DJANGO_CACHE;
# 2. Temps de validité (200/302 = 10 min)
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
# 3. Clé de cache (ignorer les query params)
proxy_cache_key "$scheme$request_method$host$request_uri";
# 4. Transmettre
proxy_pass http://127.0.0.1:8000;
include proxy_params;
}
}Headers de Sécurité (add_header)
Ajoute des en-tĂȘtes HTTP pour "durcir" le client (navigateur).
server {
...
# (Doit ĂȘtre dans le bloc 'server' ou 'location')
# HSTS (Force HTTPS)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# EmpĂȘche le 'MIME-sniffing'
add_header X-Content-Type-Options "nosniff" always;
# Anti-Clickjacking
add_header X-Frame-Options "SAMEORIGIN" always;
# Filtre XSS
add_header X-XSS-Protection "1; mode=block" always;
}Rate Limiting (Anti-DDoS/Bruteforce)
Limiter le nombre de requĂȘtes par IP.
1. Définir la zone (dans http)
# (nginx.conf) # $binary_remote_addr = IP client # zone=NOM:TAILLE rate=TAUX limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; # (10 requĂȘtes / seconde par IP)
2. Appliquer la zone (dans location)
location /api/login {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass ...;
}Quand ça ne marche pas, Nginx vous dit pourquoi. tail -f /var/log/nginx/error.log est votre meilleur ami.
| Erreur HTTP | Message error.log (Exemple) | Cause probable (Django/Gunicorn) | Solution |
|---|---|---|---|
| 502 Bad Gateway | connect() to unix:/run/gunicorn.sock failed (2: No such file or directory) | Gunicorn n'est pas démarré, ou le .sock est au mauvais endroit. | sudo systemctl start gunicorn. Vérifiez les chemins. |
| 502 Bad Gateway | connect() to unix:/run/gunicorn.sock failed (13: Permission denied) | Nginx (user www-data) n'a pas les droits R/W sur le socket. | sudo chown www-data:www-data /run/gunicorn.sock (ou Group=www-data dans gunicorn.service). |
| 403 Forbidden (Statique) | (13: Permission denied) while opening file "/var/www/static_root/..." | Nginx (user www-data) n'a pas les droits r-x sur le dossier. | sudo chmod -R 755 /var/www/static_root (ou chown). |
| 403 Forbidden (Statique) | (Rien dans error.log, 403 dans access.log) | (RHEL/Rocky) SELinux. Le contexte (ls -Z) n'est pas httpd_sys_content_t. | sudo restorecon -vR /var/www/. |
| 404 Not Found (Statique) | (2: No such file or directory) while opening file "/var/www/static_root/static/..." | Vous avez utilisé root au lieu de alias (voir 2.3). | Changer root en alias. |
Commandes CLI
# Tester la configuration (INDISPENSABLE) sudo nginx -t # Recharger (zĂ©ro downtime) sudo systemctl reload nginx # RedĂ©marrer (avec coupure) sudo systemctl restart nginx # DĂ©marrer / ArrĂȘter sudo systemctl start nginx sudo systemctl stop nginx # Activer au boot sudo systemctl enable nginx # Activer un site (Debian) sudo ln -s /etc/nginx/sites-available/mon_site /etc/nginx/sites-enabled/ # DĂ©sactiver un site (Debian) sudo rm /etc/nginx/sites-enabled/mon_site
Hiérarchie & Directives Clés
# http { ... } (Dans nginx.conf)
# Contient:
# upstream pool_name { ... }
# proxy_cache_path ...
# limit_req_zone ...
#
# server { ... } (Dans sites-available/)
# Contient:
# listen 80;
# listen 443 ssl;
# server_name example.com;
# root /var/www/html;
# add_header ...;
#
# location / { ... }
# Contient:
# proxy_pass http://pool_name;
# include proxy_params;
# proxy_cache ...;
# limit_req ...;
#
# location /static/ { ... }
# Contient:
# alias /path/to/static_root/;
# expires ...;
#
# location ~ \.php$ { ... } (Pour PHP)
# Contient:
# fastcgi_pass unix:/run/php/php-fpm.sock;
# include fastcgi_params;
# }