infra / nginx
Reverse proxy avec proxy_pass
Explication
Ce que vous allez apprendre
- Comprendre le rôle de
proxy_passpour transmettre une requête à une application - Maîtriser la règle du "/" final, qui change radicalement le comportement de
proxy_pass - Transmettre les bons headers pour que l'application connaisse le vrai client, pas seulement Nginx
- Ajuster les timeouts de proxy selon les besoins réels de l'application
- Régler
client_max_body_sizepour ne pas bloquer des uploads légitimes
Dans quel contexte ?
Une API FastAPI tourne en interne sur 127.0.0.1:8000, jamais exposée directement à Internet. Nginx écoute sur le port 443 et doit transmettre chaque requête entrante à cette application, tout en lui faisant croire, autant que possible, qu'elle reçoit la requête directement du vrai client — pas de Nginx lui-même.
D'abord, la mécanique de base de proxy_pass
proxy_pass http://127.0.0.1:8000; dit simplement à Nginx : "transmets cette requête à cette adresse, attends la réponse, et renvoie-la au client". C'est le cœur du reverse proxy, mais un détail syntaxique change complètement son comportement.
Une fois cette base posée, le piège le plus fréquent apparaît : le "/" final
Si l'URL cible se termine par un / (comme http://127.0.0.1:8000/), Nginx retire le préfixe du location avant de transmettre la requête : une requête vers /api/users avec location /api/ devient http://127.0.0.1:8000/users. Sans ce / final, le préfixe est conservé tel quel : la même requête devient http://127.0.0.1:9000/api/users. C'est une différence syntaxique minime avec un effet radical sur le routage côté application.
Configuration proxy_pass | URL entrante | URL transmise au backend |
|---|---|---|
proxy_pass http://backend/; (avec /) | /api/users | http://backend/users (préfixe retiré) |
proxy_pass http://backend; (sans /) | /api/users | http://backend/api/users (préfixe conservé) |
Ensuite, un problème plus subtil : l'application ne voit plus le vrai client
Sans configuration supplémentaire, l'application derrière Nginx reçoit toutes ses requêtes comme venant de 127.0.0.1 (Nginx lui-même), perdant l'adresse IP réelle du visiteur et le protocole d'origine (HTTP ou HTTPS). C'est un problème concret pour tout ce qui dépend de l'IP client : rate limiting applicatif, géolocalisation, logs de sécurité.
Piège fréquent
Oublier proxy_set_header X-Forwarded-Proto $scheme; fait croire à l'application qu'elle reçoit toujours du HTTP, même quand le client se connecte réellement en HTTPS — ce qui casse en silence toute logique applicative qui vérifie ce protocole (comme forcer des cookies "secure").
Prérequis
Cette leçon suppose que tu es à l'aise avec les server blocks et les location (leçon précédente) : proxy_pass s'utilise toujours à l'intérieur d'un bloc location.
Il reste deux réglages à ne pas laisser aux valeurs par défaut
Les timeouts par défaut de Nginx (souvent 60 secondes) sont parfois trop courts pour un traitement long côté application (un export de rapport, un traitement d'image). De même, client_max_body_size vaut 1 Mo par défaut : bien trop petit pour la plupart des uploads réels, et il faut l'augmenter explicitement, jamais le laisser au hasard.
Bonne pratique
Fixe toujours client_max_body_size à une valeur explicite et documentée (par exemple 20m pour des uploads d'images), plutôt que de découvrir la limite par défaut en production le jour où un utilisateur reçoit une erreur "413 Request Entity Too Large" incompréhensible sans contexte.
Maintenant que tu sais transmettre une requête à une seule application, la prochaine leçon passe à l'échelle supérieure : répartir la charge entre plusieurs instances d'une même application avec les groupes upstream.
Commandes & code
Reverse proxy avec proxy_pass
server {
listen 80;
server_name api.example.com;
location /api/ {
# ATTENTION : le "/" final change tout le comportement
proxy_pass http://127.0.0.1:8000/;
# avec "/" final : /api/users -> http://127.0.0.1:8000/users (le préfixe /api/ est RETIRÉ)
}
location /legacy/ {
proxy_pass http://127.0.0.1:9000;
# SANS "/" final : /legacy/users -> http://127.0.0.1:9000/legacy/users (préfixe CONSERVÉ)
}
}# Headers indispensables : sans eux, l'app derrière ne voit QUE Nginx, jamais le vrai client
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host; # préserve le Host original (vhosts app)
proxy_set_header X-Real-IP $remote_addr; # IP réelle du client
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # chaîne de proxies traversés
proxy_set_header X-Forwarded-Proto $scheme; # http ou https côté client
# Timeouts : à ajuster selon l'app (défauts souvent trop courts pour des uploads/traitements longs)
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}# Côté FastAPI, exploiter ces headers (avec un middleware type ProxyHeadersMiddleware)
from fastapi import FastAPI, Request
app = FastAPI()
@app.get("/whoami")
def whoami(request: Request):
return {
"client_ip": request.headers.get("x-real-ip"), # IP réelle grâce à Nginx
"proto": request.headers.get("x-forwarded-proto"), # https détecté même si uvicorn parle en http
}# Buffers et gestion des grosses réponses (utile pour API renvoyant du JSON volumineux)
location /api/ {
proxy_pass http://127.0.0.1:8000/;
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;
client_max_body_size 20m; # taille max du BODY envoyé par le client (uploads)
}Résumé
- Le
/final dansproxy_pass http://backend/retire le préfixe delocation; son absence le conserve : erreur fréquente. - Toujours définir
Host,X-Real-IP,X-Forwarded-For,X-Forwarded-Protopour que l'app connaisse le vrai client. client_max_body_sizedoit être augmenté explicitement pour les uploads (défaut = 1m, trop petit pour la plupart des cas réels).
Exercices pratiques
Mission : corriger un proxy_pass qui casse le routage et masque le vrai client
Objectif : Diagnostiquer une erreur de barre oblique finale dans proxy_pass et rétablir les headers qui révèlent le vrai client à l'application.
Contexte
L'équipe a configuré location /api/ { proxy_pass http://127.0.0.1:8000; } (sans "/" final) mais l'application FastAPI, qui écoute ses routes à la racine (/users, pas /api/users), renvoie des 404 sur toutes les requêtes. Par ailleurs, ses logs de sécurité affichent 127.0.0.1 comme IP cliente pour absolument toutes les connexions, rendant tout rate limiting applicatif par IP inutile.