Ce qui se trouve entre le navigateur et votre app

Pointez votre navigateur vers example.com et la requête ne touche jamais le processus qui exécute votre code. Elle rencontre d’abord autre chose : un serveur qui lit la requête, décide quel backend doit répondre, la lui transmet, puis renvoie la réponse comme s’il l’avait produite lui-même. Ce serveur, c’est un reverse proxy.

Nginx, Caddy, Traefik et HAProxy font tous ce travail, tout comme le load balancer placé devant un cluster Kubernetes. Le schéma ne change jamais : les clients ne parlent qu’au proxy, à rien d’autre. Les serveurs réels restent derrière, en nombre quelconque et où qu’ils tournent.

Comment un reverse proxy traite une requête

Un client ouvre une connexion vers l’IP et le port du reverse proxy, comme il le ferait avec n’importe quel serveur. Le proxy termine cette connexion, lit la ligne de requête et les en-têtes, et les compare à ses propres règles — généralement l’en-tête Host et le chemin de l’URL. Selon ce résultat, il ouvre une seconde connexion, distincte, vers un backend, lui transmet la requête, attend la réponse, puis la relaie sur la connexion d’origine du client.

Deux connexions indépendantes, mises bout à bout. Côté client, rien ne distingue cela d’un échange direct avec le backend. Côté backend, chaque requête semble venir d’une seule machine, le proxy, plutôt que d’internet en général.

1
2
client  --- requête --->  reverse proxy  --- requête --->  serveur backend
client  <-- réponse ---  reverse proxy  <-- réponse ---  serveur backend

Comme le proxy voit chaque requête avant le backend, il peut l’inspecter, la modifier, la mettre en cache ou la rejeter. C’est cette position qui lui fait porter le travail dont personne ne veut à l’intérieur de l’application : terminer le TLS, compresser les réponses, mettre en cache le contenu statique, limiter les clients abusifs, et envoyer des chemins différents vers des services différents — /api vers un backend, / vers un autre, /static directement sur disque.

Reverse proxy vs forward proxy

Les deux se placent au milieu d’une connexion. Ce qui change, c’est pour qui ils travaillent.

Forward proxyReverse proxy
Agit pour le compte deLe clientLe serveur
Le client sait qu’il existeGénéralement oui (configuré explicitement)Non (ressemble au vrai serveur)
Ce qu’il cacheL’identité du client au serveurL’identité du serveur au client
Usage typiqueFiltrage sortant en entreprise, contournement de géoblocages, navigation anonymeRoutage, terminaison TLS, cache, protection des serveurs d’origine

Un forward proxy, c’est la boîte que votre employeur installe au bureau pour que tout le trafic sortant passe par un point unique filtré : le site que vous visitez enregistre l’adresse du proxy, pas celle de votre machine. Un reverse proxy est géré par les propriétaires du site, et cache l’autre extrémité. De l’extérieur, vous ne pouvez pas savoir si example.com est un seul serveur ou quarante.

Reverse proxy vs load balancer

Les deux termes s’emploient l’un pour l’autre, surtout parce qu’un même processus Nginx ou HAProxy fait souvent les deux à la fois.

Reverse proxyLoad balancer
Question centrale à laquelle il répondQue doit-il arriver à cette requête ?Quel serveur doit traiter cette requête ?
Fonctionne avec un seul backend ?Oui, reste utile (TLS, cache, routage par chemin)Non, il en faut au moins deux à répartir
Fonctionnalités supplémentaires typiquesCache, réécriture d’en-têtes, terminaison TLS, routage par cheminHealth checks, répartition pondérée, affinité de session

Un reverse proxy devant un seul backend justifie déjà sa présence : il termine le TLS pour que votre app n’ait pas à le faire, cache l’adresse réelle du backend, ajoute gzip. Un load balancer n’a rien à faire tant qu’il n’y a pas au moins deux backends entre lesquels choisir. L’étiquette que vous utilisez décrit la configuration que vous avez écrite, pas le logiciel que vous avez installé.

Comment configurer Nginx en reverse proxy

Écouter sur un port, comparer un hostname, transmettre à un backend. C’est tout le minimum nécessaire :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        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_pass est la ligne qui compte : tout ce que capture location part vers http://127.0.0.1:3000. Les quatre lignes proxy_set_header existent parce que, sinon, le backend voit le proxy comme client, et chaque requête semble venir de 127.0.0.1. X-Real-IP et X-Forwarded-For transportent l’adresse réelle du client jusqu’au bout. X-Forwarded-Proto indique au backend si la requête d’origine était en HTTPS, puisque le TLS a été terminé au niveau du proxy et que le saut suivant se fait en HTTP simple.

Un détail casse les configurations en permanence : une barre oblique finale sur l’URI de proxy_pass change le chemin transmis. proxy_pass http://backend; (sans chemin) transmet l’URI d’origine sans modification. proxy_pass http://backend/; (avec barre oblique finale) retire la partie de l’URI que location a capturée avant de transmettre. Avec location /api/ et proxy_pass http://backend/;, une requête vers /api/users arrive au backend sous la forme /users. Confondez les deux et toutes les routes renvoient 404 côté backend, alors que curl sur le proxy semble fonctionner.

Reverse proxy devant une app dockerisée

C’est là que ce schéma apparaît le plus au quotidien : Nginx dans un conteneur, votre app dans un autre, tous deux sur le même réseau Docker pour que Nginx puisse atteindre l’app par le nom du conteneur — le comportement DNS décrit dans ce qu’offre un réseau Docker.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# nginx.conf
server {
    listen 80;

    location / {
        proxy_pass http://app:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# compose.yaml
services:
  app:
    build: .
    expose:
      - "3000"

  nginx:
    image: nginx:1.27-alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - app

app expose le port 3000 sur le réseau Compose mais ne publie rien sur l’hôte. Seul Nginx le fait, sur le port 80. Le nom app:3000 dans la configuration Nginx se résout via le DNS interne de Docker, le même mécanisme décrit dans le guide Docker Compose. Lancez docker compose up et les requêtes vers localhost arrivent sur Nginx, qui les transmet dans le conteneur de l’app. L’app n’est jamais accessible depuis l’extérieur de l’hôte à elle seule, ce qui est aussi voulu que le routage lui-même.

Ce que coûte un reverse proxy

Il ajoute un saut réseau et un point de défaillance supplémentaire. Un proxy_pass mal configuré ou un en-tête perdu ressemble à un bug dans l’app, alors que le vrai problème se situe un niveau plus en amont. C’est aussi un point unique de défaillance par défaut : tuez cet unique processus Nginx et chaque backend derrière devient inaccessible. En production, on l’exécute soit de façon redondante, soit on confie la tâche à quelque chose qui suppose déjà la panne — un load balancer cloud, ou un Ingress controller Kubernetes appuyé sur les Deployments et Pods qui se trouvent dessous.

Un reverse proxy ne rend pas non plus une app plus rapide ou plus scalable à lui seul. Il peut mettre en cache et compresser, mais un backend lent reste lent ; le proxy se contente de transmettre cette lenteur un saut plus tard. Et ce n’est ni de l’authentification ni de la validation d’entrée. Bloquer des requêtes manifestement malformées en périphérie est un plus, jamais une raison pour que l’app cesse de vérifier ce qu’elle reçoit.

Passer cette configuration en production

Prenez le compose.yaml ci-dessus, remplacez app par un vrai service, et ajoutez ssl_certificate et ssl_certificate_key au bloc server de Nginx une fois que vous avez un certificat. C’est la différence entre un exercice local et quelque chose que vous mettriez devant du trafic réel. Si vous routez vers plusieurs backends, remplacez l’adresse unique par un bloc upstream contenant plusieurs lignes server, et pointez proxy_pass vers ce bloc : la même configuration devient un load balancer.

Articles associés