Nginx proxy inverse - Guide complet pour une config robuste

Étienne Lambert .

25 mai 2026

Schéma montrant une mauvaise configuration d'un nginx reverse proxy. Le PC interroge le DNS, puis le proxy, mais le proxy ne transmet pas la requête au serveur web.

Un proxy inverse placé devant une application change surtout trois choses : l’entrée réseau, les en-têtes transmis et la façon dont on fait évoluer le service. Quand je le configure avec Nginx, je cherche à obtenir un point d’accès unique, des règles de routage lisibles et un comportement prévisible en cas de montée en charge ou de panne. C’est utile autant pour une API Node.js que pour un backend monolithique, une grappe de microservices ou une application avec WebSocket.

Dans cet article, je passe en revue la logique du montage, la configuration minimale, les en-têtes à ne pas oublier, les réglages pour HTTPS et les connexions longues, puis les options de répartition de charge et les erreurs qui reviennent le plus souvent en production.

Les points essentiels à garder en tête avant de le déployer

  • Nginx devient un point d’entrée unique entre le client et vos services internes.
  • Le comportement de `proxy_pass` dépend du slash final et peut modifier l’URI transmise au backend.
  • Les en-têtes `Host`, `X-Real-IP` et `X-Forwarded-For` doivent être gérés explicitement si l’application en dépend.
  • La répartition de charge par défaut est en round-robin, avec d’autres stratégies comme `least_conn` ou `ip_hash` selon le besoin.
  • Les connexions longues, comme WebSocket ou certains flux SSE, exigent des timeouts adaptés.
  • Les erreurs les plus coûteuses viennent souvent d’un mauvais routage, d’un redirect non réécrit ou d’un timeout trop court.

Pourquoi placer Nginx en frontal change l’architecture

Je vois souvent Nginx comme une couche de traduction entre le monde public et les services applicatifs. Au lieu d’exposer plusieurs ports, plusieurs certificats et plusieurs règles réseau, on centralise l’accès, on masque l’architecture interne et on garde la main sur la façon dont les requêtes sont distribuées.

Dans un contexte DevOps, l’intérêt n’est pas seulement esthétique. On peut terminer le TLS au même endroit, journaliser les requêtes de manière cohérente, absorber des changements de backend sans déplacer l’URL publique, et faire évoluer les services sans casser les clients. C’est particulièrement propre quand plusieurs équipes déploient des services distincts derrière le même domaine.

Scénario Ce que Nginx apporte Point de vigilance
API unique derrière un domaine public Un seul point d’entrée, TLS centralisé, journaux homogènes Bien transmettre le bon `Host` pour éviter les routes cassées
Plusieurs services derrière le même domaine Routage par chemin ou par nom d’hôte Éviter les collisions de préfixes et les redirections absolues vers l’interne
Montée en charge Répartition de charge et mise à l’écart des nœuds défaillants Choisir une stratégie cohérente avec le type de session

Une fois ce rôle posé, le vrai sujet devient la configuration concrète, parce qu’un montage propre tient souvent à quelques lignes bien choisies.

Schéma montrant des utilisateurs accédant à des serveurs via Internet et un reverse proxy.

Construire une configuration de base qui reste lisible

Pour démarrer, je pars d’un bloc simple : un serveur public en face, un ou plusieurs upstreams derrière, et quelques en-têtes utiles pour préserver le contexte d’origine. Le but n’est pas d’empiler des directives, mais d’écrire quelque chose qu’on sait relire six mois plus tard.

upstream app_backend {
    server 127.0.0.1:3000;
}

server {
    listen 80;
    server_name api.exemple.fr;

    location / {
        proxy_pass http://app_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Le point le plus sous-estimé ici, c’est la transmission du bon `Host`. Si vous laissez Nginx substituer sa propre valeur, certaines applications ne reconnaissent plus le vhost attendu, génèrent des URLs internes ou cassent la logique de génération de liens. En pratique, je préfère presque toujours `proxy_set_header Host $host;`.

Le second piège, c’est le comportement de `proxy_pass` avec ou sans URI. Si vous écrivez `proxy_pass http://backend/;`, Nginx remplace la partie de l’URI qui correspond au `location`. Si vous écrivez `proxy_pass http://backend;`, il transmet l’URI telle qu’elle arrive, sans cette réécriture implicite. C’est un détail minuscule sur le papier, mais c’est souvent lui qui provoque un `404` bizarre ou un endpoint qui perd son préfixe.

location /app/ {
    proxy_pass http://127.0.0.1:3000/;
}

location /api/ {
    proxy_pass http://127.0.0.1:4000;
}

Dans le premier cas, le préfixe `/app/` est réécrit vers `/`. Dans le second, l’URI est envoyée sans réécriture automatique. C’est précisément le genre de différence qui mérite un test explicite dans votre pipeline avant d’exposer quoi que ce soit au public.

Quand cette base est stable, il faut regarder ce qui se passe dès qu’on ajoute du HTTPS, du WebSocket ou des échanges plus longs que la moyenne.

Gérer les cas modernes sans casser le trafic

Dès qu’une application n’est plus un simple backend HTTP court, je surveille trois sujets : le TLS en amont, les connexions longues et le buffering des requêtes. C’est là que beaucoup d’installations “fonctionnent” en apparence, mais se dégradent au premier trafic réel.

Si votre backend est lui-même en HTTPS, activez `proxy_ssl_server_name on;` lorsque le certificat dépend du nom d’hôte. Sans SNI, certains backends multi-domaines répondent avec le mauvais certificat ou le mauvais site virtuel. C’est un détail de couche transport, mais en pratique il évite des erreurs pénibles à diagnostiquer.

location /chat/ {
    proxy_pass http://backend;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 300s;
}

Pour WebSocket, il faut transmettre explicitement les en-têtes `Upgrade` et `Connection`. Nginx sait ensuite établir le tunnel quand le backend répond avec un statut `101`. Le timeout mérite aussi d’être ajusté : la valeur par défaut de lecture côté proxy est de 60 secondes, ce qui est souvent trop court pour un canal vivant mais silencieux entre deux messages.

Pour les uploads volumineux ou les APIs qui streament des données, le buffering demande un choix assumé. Par défaut, Nginx lit le corps complet de la requête avant de le relayer. C’est rassurant pour protéger un backend fragile, mais ce n’est pas idéal si vous voulez du flux quasi immédiat. Si vous désactivez le buffering, gardez en tête qu’une requête déjà envoyée ne se rejoue pas facilement vers un autre serveur si le premier tombe au mauvais moment.

Une fois ces cas gérés, la vraie question devient la distribution du trafic entre plusieurs instances et la manière d’absorber une panne sans faire tomber l’ensemble.

Répartir la charge et absorber les défaillances

Nginx ne sert pas seulement à faire passer des requêtes. Il peut aussi arbitrer entre plusieurs serveurs applicatifs. Le comportement par défaut est round-robin, ce qui convient à des backends homogènes et à des requêtes de coût comparable. Dès que ce n’est plus vrai, je change de méthode.

Méthode Quand je l’utilise Limite principale
Round-robin Backends similaires, trafic régulier Ne tient pas compte de la charge réelle de chaque nœud
`least_conn` Requêtes hétérogènes, certaines plus longues que d’autres Moins pertinent si la latence dépend surtout du traitement métier interne
`ip_hash` Besoin de “sticky sessions” temporaires Fragile si les sessions vivent seulement en mémoire locale
Poids (`weight`) Instances de capacité différente Demande un suivi réel des capacités, sinon le réglage devient arbitraire

La stratégie la plus saine dépend du type d’application. Pour une API sans état, je privilégie un équilibrage simple, puis je laisse le backend conserver les sessions côté base ou via un stockage partagé. Pour une application qui garde encore de l’état local, `ip_hash` peut dépanner, mais je le traite comme une béquille, pas comme une architecture finale.

Nginx ajoute aussi des vérifications passives de santé. Si un serveur renvoie une erreur ou ne répond plus, il est mis de côté pendant un certain temps. Par défaut, `proxy_next_upstream` tente déjà un relais sur `error` et `timeout`, ce qui aide à absorber un incident sans basculer immédiatement toute la requête côté client. Pour moi, cela reste très utile, à condition de garder un œil sur les requêtes non idempotentes : rejouer un `GET` n’a pas le même risque que rejouer un `POST` métier.

Ce mécanisme rend l’ensemble plus robuste, mais il ne pardonne pas les configurations approximatives. C’est précisément ce que je passe en revue juste après.

Les erreurs que je corrige le plus souvent avant la mise en production

Les déploiements cassent rarement sur une seule grosse faute. Ils cassent plus souvent sur une accumulation de petits écarts : un en-tête oublié, un redirect absolu mal réécrit, un timeout trop agressif ou une route qui ne correspond pas exactement au préfixe attendu.

  • Oublier `Host` : l’application voit le backend au lieu du domaine public, ce qui casse parfois la génération d’URL ou le routage virtuel.
  • Se tromper de slash dans `proxy_pass` : le préfixe est conservé ou supprimé au mauvais endroit, et l’API reçoit une route différente de celle que vous croyez envoyer.
  • Ignorer `proxy_redirect` : si le backend répond avec un `Location` vers son hôte interne, le navigateur suit un chemin inaccessible depuis l’extérieur.
  • Laisser le timeout par défaut pour un flux long : au-delà de 60 secondes sans activité, la connexion est fermée.
  • Désactiver le buffering sans raison claire : vous gagnez en immédiateté, mais vous perdez une partie de la marge de sécurité côté proxy.
  • Ne pas journaliser les timings amont : sans `upstream_response_time` et `upstream_status`, on sait qu’une requête a échoué, mais pas où elle a vraiment ralenti.

Je corrige aussi beaucoup de cas où les redirections d’un backend applicatif ne respectent pas l’URL publique. Dans ces situations, `proxy_redirect` sert à réécrire proprement les en-têtes `Location` et `Refresh` pour que le client reste sur la bonne adresse. C’est le genre de détail invisible dans un environnement de test, puis très visible dès qu’un utilisateur clique sur un lien de connexion ou de retour à l’application.

Quand ces points sont verrouillés, il reste la partie la plus sobre et la plus rentable du travail : vérifier que la configuration est testée, traçable et facile à faire évoluer.

Ce que je vérifie avant d’ouvrir l’accès au public

Avant une mise en production, je fais toujours le même contrôle de bon sens. Je valide la configuration, je teste le routage exact, je vérifie les comportements de reprise, puis je confirme que les journaux me donnent assez de contexte pour comprendre un incident en quelques minutes, pas en quelques heures.

  • Je lance une validation de configuration avec la commande de test de Nginx avant chaque rechargement.
  • Je teste au minimum un chemin racine, un chemin routé vers un backend, une redirection et, si besoin, un échange WebSocket.
  • Je confirme que le backend reçoit bien le bon `Host`, la bonne IP client et le bon schéma public.
  • Je contrôle le comportement en cas de backend indisponible pour éviter une panne silencieuse.
  • Je garde un format de logs qui expose au moins le statut amont et le temps de réponse côté backend.
Le point le plus important, à mes yeux, c’est de ne pas traiter Nginx comme un simple “bouchon” placé devant l’application. Bien réglé, il devient une vraie couche d’exploitation : il stabilise les entrées, protège les services internes, simplifie les déploiements et rend les incidents plus lisibles. C’est exactement ce que j’attends d’un proxy inverse en production, et c’est aussi ce qui fait la différence entre une configuration qui fonctionne et une configuration que l’on peut faire vivre sereinement.

Questions fréquentes

Nginx centralise l'accès, masque l'architecture interne, gère le TLS et la répartition de charge. Il simplifie la gestion de multiples services derrière un domaine unique, améliore la résilience et facilite les évolutions sans impacter les clients.
Les en-têtes `Host`, `X-Real-IP` et `X-Forwarded-For` sont essentiels. `Host` assure que l'application reçoit le bon nom de domaine. Les autres transmettent l'adresse IP réelle du client, importante pour la journalisation et la sécurité.
Pour WebSocket, il faut configurer `proxy_set_header Upgrade $http_upgrade;` et `proxy_set_header Connection "upgrade";`. Il est aussi crucial d'ajuster le `proxy_read_timeout` à une valeur plus élevée que les 60 secondes par défaut, pour éviter les coupures prématurées.
Le round-robin est la valeur par défaut. `least_conn` est préférable pour des requêtes hétérogènes. `ip_hash` maintient les sessions "collantes" pour un client donné. Le choix dépend de la nature de l'application et de la gestion des sessions.
Les erreurs fréquentes incluent l'oubli de l'en-tête `Host`, une mauvaise utilisation du slash dans `proxy_pass`, l'absence de `proxy_redirect` pour les redirections internes, des timeouts trop courts et la désactivation injustifiée du buffering.

Évaluer l'article

Moyenne: 0.0 / 5 · 0 évaluations

Tags

nginx reverse proxy configuration nginx proxy inverse nginx reverse proxy headers
Autor Étienne Lambert
Étienne Lambert
Je m'appelle Étienne Lambert et j'ai 13 ans d'expérience dans le développement web, avec un accent particulier sur JavaScript, le backend, NoSQL et la sécurité. Mon parcours dans ce domaine a commencé par une curiosité insatiable pour la technologie et la manière dont elle façonne notre monde. J'aime partager mes connaissances et aider les lecteurs à naviguer dans les complexités du développement web, en rendant des sujets parfois ardus plus accessibles. Je m'efforce toujours de fournir des informations utiles, précises et à jour, en vérifiant mes sources et en comparant les différentes perspectives. J'écris sur des sujets variés qui vont des meilleures pratiques en matière de sécurité aux tendances émergentes dans le développement. Mon objectif est de simplifier des concepts techniques et d'organiser les connaissances de manière claire, afin que chacun puisse en tirer profit et se sentir confiant dans ses compétences en développement web.

Commentaires (0)

Ajouter un commentaire