Configuration du proxy inverse Nginx : Diriger le trafic efficacement

Configurez un proxy inverse Nginx avec un routage clair, des en-têtes corrects, la prise en charge des WebSockets, des délais d'attente, une mise en mémoire tampon et des étapes de dépannage.

Configuration du proxy inverse Nginx : Diriger le trafic efficacement

Une configuration de proxy inverse Nginx permet à Nginx de recevoir le trafic web public et de le transmettre à une ou plusieurs applications backend. Cela est utile lorsque votre application fonctionne sur Node.js, Python, Go, Java ou un autre service qui ne doit pas être exposé directement sur Internet.

Au lieu que les utilisateurs se connectent au port de votre application, ils se connectent à Nginx sur les ports HTTP ou HTTPS standard. Nginx gère la bordure publique, puis dirige le trafic efficacement vers le service interne approprié.

Ce que fait un proxy inverse

Un proxy inverse se place devant vos serveurs d'application. Le client parle à Nginx, et Nginx parle au backend. Pour le navigateur, Nginx est le site web. Pour l'application, Nginx est le client amont, sauf si vous transmettez des en-têtes qui préservent les détails de la requête originale.

Ce modèle vous offre plusieurs avantages :

  • Vous pouvez exécuter des applications sur des ports privés comme 3000, 5000 ou 8080.
  • Vous pouvez terminer TLS au niveau de Nginx.
  • Vous pouvez router différents noms d'hôte ou chemins vers différents services.
  • Vous pouvez ajouter une mise en mémoire tampon, des délais d'attente, une compression et un cache.
  • Vous pouvez masquer les détails d'implémentation du backend au réseau public.

Un proxy inverse de base pour une application fonctionnant sur 127.0.0.1:3000 ressemble à ceci :

server {
    listen 80;
    server_name example.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;
    }
}

La directive proxy_pass indique à Nginx où envoyer la requête. Les lignes proxy_set_header préservent le contexte utile de la requête. Sans elles, votre application peut enregistrer chaque requête comme provenant de Nginx et peut ne pas savoir si la requête originale utilisait HTTP ou HTTPS.

Si vous débutez avec la structure d'hôte virtuel, consultez Les blocs serveur Nginx avant de répartir le trafic sur plusieurs domaines.

Routage du trafic par hôte ou chemin

Les règles de proxy inverse routent généralement par nom d'hôte, chemin, ou les deux. Le routage basé sur l'hôte est courant lorsque des applications distinctes utilisent des domaines différents :

server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_pass http://127.0.0.1:4000;
        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;
    }
}

Le routage basé sur le chemin est utile lorsqu'un seul domaine sert plusieurs services :

server {
    listen 80;
    server_name example.com;

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

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

Soyez prudent avec les barres obliques finales dans proxy_pass. Dans Nginx, proxy_pass http://backend; et proxy_pass http://backend/; peuvent réécrire l'URI transmise différemment lorsqu'elles sont utilisées dans un bloc d'emplacement. Testez les chemins d'URL exacts que votre application attend.

Par exemple, si /api/users atteint de manière inattendue votre backend en tant que /users ou /api/api/users, vérifiez d'abord la combinaison du préfixe d'emplacement et de la barre oblique finale. C'est l'une des erreurs les plus courantes de proxy inverse.

En-têtes, délais d'attente et WebSockets

Les en-têtes permettent au backend de connaître la requête originale. L'en-tête Host est important lorsque l'application construit des URL absolues, valide les hôtes autorisés ou prend en charge plusieurs locataires. X-Forwarded-For aide à préserver l'adresse IP du client d'origine. X-Forwarded-Proto aide les applications à générer des liens sécurisés après la terminaison TLS.

Si votre backend utilise des WebSockets, ajoutez des en-têtes de mise à niveau :

location /socket/ {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
}

Les délais d'attente doivent correspondre au comportement de votre application. Une requête web normale doit se terminer rapidement. Une exportation de rapport, un point de terminaison de streaming ou une requête de long polling peuvent nécessiter plus de temps :

proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;

Évitez de définir des délais d'attente énormes partout simplement pour masquer un point de terminaison lent. Des délais d'attente longs peuvent monopoliser les ressources et rendre les pannes réelles plus difficiles à remarquer. Ajustez l'emplacement qui en a besoin.

La mise en mémoire tampon est un autre paramètre important. Par défaut, Nginx peut mettre en mémoire tampon les réponses en amont avant de les envoyer au client. Cela est utile pour de nombreuses applications web, mais les points de terminaison de streaming peuvent nécessiter la désactivation de la mise en mémoire tampon :

proxy_buffering off;

Utilisez cela uniquement là où un comportement de streaming est requis. Pour les réponses HTML et API standard, la mise en mémoire tampon améliore souvent la stabilité.

Terminaison TLS et redirections HTTPS

Dans de nombreuses configurations, Nginx gère également HTTPS. Cela permet à l'application backend de fonctionner sur un port HTTP privé tandis que les utilisateurs obtiennent un site sécurisé normal sur le port 443.

Une forme courante ressemble à ceci :

server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    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;
    }
}

Le bloc serveur de redirection est intentionnellement petit. Il fait un seul travail : déplacer le trafic HTTP simple vers HTTPS. Le bloc serveur HTTPS gère le proxy.

Si votre application se trouve derrière Nginx et génère toujours des liens http://, vérifiez si elle fait confiance à X-Forwarded-Proto. De nombreux frameworks ont besoin d'un paramètre tel que "trust proxy" ou d'une liste de proxys autorisés avant d'utiliser les en-têtes transmis. Ne faites pas aveuglément confiance aux en-têtes transmis provenant d'Internet public au niveau de l'application ; assurez-vous que seul Nginx peut atteindre le port de l'application.

Groupes en amont et équilibrage de charge simple

Lorsqu'un seul backend ne suffit pas, définissez un groupe en amont :

upstream app_backend {
    server 10.0.1.10:3000;
    server 10.0.1.11:3000;
    keepalive 32;
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://app_backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        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;
    }
}

Nginx open source utilise l'équilibrage de charge round-robin par défaut. Vous pouvez également utiliser des options telles que least_conn lorsque des requêtes longues rendent un backend plus occupé que les autres. La vérification de l'état dans Nginx open source est principalement passive : si un backend échoue, Nginx peut le marquer comme indisponible pendant une période basée sur les paramètres d'échec. Nginx Plus dispose de vérifications de santé actives, mais ne supposez pas que ces fonctionnalités existent dans toutes les installations.

Keepalive dans le bloc en amont maintient les connexions backend ouvertes pour réutilisation. Cela aide avec beaucoup de petites requêtes, mais le backend doit être capable de gérer le nombre de connexions inactives et actives que Nginx peut maintenir.

Conteneurs et réseaux privés

La configuration du proxy inverse devient souvent confuse dans Docker ou Kubernetes car localhost change de signification. Si Nginx fonctionne à l'intérieur d'un conteneur, 127.0.0.1:3000 pointe vers le conteneur Nginx lui-même, pas vers un conteneur d'application séparé.

Dans Docker Compose, faites un proxy vers le nom du service :

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

Dans Kubernetes, vous faites généralement un proxy vers un nom DNS de service, bien que de nombreux déploiements Kubernetes utilisent un contrôleur Ingress au lieu de blocs serveur Nginx écrits à la main.

La règle simple est la suivante : testez la connectivité à partir de l'endroit où Nginx s'exécute, pas depuis votre ordinateur portable et pas depuis le conteneur backend. Si cela échoue, Nginx échouera également :

curl -v http://app:3000/

Exécutez cela à l'intérieur du conteneur Nginx ou sur l'hôte Nginx, selon votre déploiement.

Limites de sécurité à vérifier

Un proxy inverse doit réduire l'exposition publique, pas en créer accidentellement davantage. L'application backend doit normalement écouter sur une interface privée, un sous-réseau privé ou un réseau de conteneurs. Si votre application écoute sur 0.0.0.0:3000 sur une VM publique, les utilisateurs peuvent contourner Nginx entièrement en visitant http://example.com:3000.

Vérifiez les ports d'écoute sur l'hôte :

sudo ss -ltnp

Si le backend doit écouter sur toutes les interfaces à l'intérieur d'un conteneur, utilisez des règles de pare-feu, des groupes de sécurité ou des paramètres de réseau de conteneurs afin que seul Nginx puisse y accéder de l'extérieur. Cela est important car les applications comptent souvent sur Nginx pour TLS, les limites de taille de requête, les limites de débit, les passerelles d'authentification ou les listes d'autorisation IP.

Soyez également prudent avec les en-têtes transmis. Les en-têtes tels que X-Forwarded-For sont faciles à usurper pour les clients, sauf si Nginx les écrase et que l'application ne fait confiance qu'au proxy. Le modèle Nginx courant est :

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

Cela ajoute l'adresse du client à la chaîne. Votre application ou pipeline de journalisation doit savoir quelles adresses de proxy sont fiables. Sinon, la limitation de débit ou les journaux d'audit peuvent enregistrer la mauvaise IP "client".

Les limites de taille de requête font également partie de cette conversation. Si votre application accepte des téléchargements de fichiers, définissez client_max_body_size intentionnellement :

client_max_body_size 25m;

Ne l'augmentez pas globalement à une valeur énorme sauf si chaque route en a besoin. Un point de terminaison de téléchargement de photo de profil et un point de terminaison de connexion JSON ne devraient pas avoir besoin de la même limite de corps de requête.

Une liste de contrôle pratique pour le déploiement

Avant de déclarer le proxy inverse terminé, testez-le comme un utilisateur et comme un opérateur :

  • curl -I http://example.com/ devrait montrer la redirection ou la réponse attendue.
  • curl -I https://example.com/ devrait montrer le statut et les en-têtes attendus.
  • Les journaux de l'application devraient montrer l'hôte d'origine et une IP client utile.
  • Les points de terminaison WebSocket ou de streaming doivent être testés séparément.
  • Un mauvais chemin, comme /api/does-not-exist, devrait échouer de la manière attendue par votre application.
  • Les journaux d'erreur Nginx doivent être silencieux pendant les requêtes normales.

Pour le routage de chemin, j'aime tester trois URL pour chaque emplacement : le préfixe nu, un chemin imbriqué normal et un chemin avec une chaîne de requête. Par exemple :

curl -i http://example.com/api/
curl -i http://example.com/api/users
curl -i 'http://example.com/api/users?page=2'

Ces vérifications simples attrapent de nombreuses erreurs de barre oblique finale avant que les utilisateurs ne le fassent.

Lorsque vous rechargez, utilisez la même séquence sécurisée à chaque fois :

sudo nginx -t
sudo systemctl reload nginx
sudo tail -n 50 /var/log/nginx/error.log

Si l'application est derrière une terminaison TLS, vérifiez également que les liens générés, les redirections, les cookies et les URL de rappel utilisent HTTPS. Les flux de connexion sont souvent les premiers à se casser, car les redirections et les cookies sécurisés dépendent de la compréhension du schéma original par l'application.

Modèles d'échec courants

502 Bad Gateway signifie généralement que Nginx a atteint l'emplacement du proxy inverse mais n'a pas pu obtenir une réponse valide de l'amont. Le backend peut être en panne, le port peut être incorrect, l'application peut écouter sur une interface différente ou la connexion peut être refusée par un pare-feu.

504 Gateway Timeout signifie généralement que Nginx s'est connecté à quelque chose mais n'a pas reçu de réponse à temps. Cela peut être une application lente, une requête de base de données bloquée, un pool de workers surchargé ou un délai d'attente trop court pour le point de terminaison. Augmenter proxy_read_timeout peut être approprié pour un point de terminaison d'exportation longue durée connu. Ce n'est pas une solution pour une application généralement lente.

Les boucles de redirection proviennent souvent d'une inadéquation entre la terminaison TLS et les paramètres de confiance de l'application. Le navigateur atteint Nginx via HTTPS, Nginx fait un proxy vers l'application via HTTP, et l'application pense que la requête originale était en HTTP simple. L'application redirige vers HTTPS, mais la même chose se reproduit. Transmettre X-Forwarded-Proto n'est que la moitié de la solution ; l'application doit également lui faire confiance depuis le proxy.

Les IP client manquantes apparaissent généralement comme chaque requête provenant de 127.0.0.1, d'une adresse de pont Docker ou d'une adresse de load balancer privé. Transmettez X-Real-IP et X-Forwarded-For, puis configurez l'application et la couche de journalisation pour les lire en toute sécurité.

Les ressources statiques cassées après le routage de chemin proviennent souvent d'applications qui supposent qu'elles vivent à /. Si vous montez une application sous /admin/, elle peut toujours générer des liens vers /assets/app.css. Vous pouvez parfois résoudre cela avec les paramètres de chemin de base de l'application. Essayer de réécrire chaque chemin de ressource dans Nginx est généralement fragile.

Un petit exemple concret

Imaginez une VM exécutant trois services :

  • Un site marketing sur 127.0.0.1:3000
  • Une API sur 127.0.0.1:4000
  • Un outil d'administration sur 127.0.0.1:5000

Vous pourriez les router comme ceci :

server {
    listen 443 ssl;
    server_name example.com;

    location /api/ {
        proxy_pass http://127.0.0.1:4000/;
        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;
    }

    location /admin/ {
        proxy_pass http://127.0.0.1:5000/;
        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;
    }

    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;
    }
}

Cela peut fonctionner, mais cela a des compromis. L'API et l'application d'administration doivent toutes deux se comporter correctement sous leurs préfixes. Si ce n'est pas le cas, des noms d'hôte séparés comme api.example.com et admin.example.com peuvent être plus propres. Une bonne conception de proxy inverse ne consiste pas seulement à faire accepter la configuration par Nginx ; il s'agit de choisir un routage avec lequel vos applications peuvent vivre.

Tester et dépanner la configuration

Testez toujours la configuration avant de recharger :

nginx -t

Rechargez ensuite Nginx et faites une requête via le nom d'hôte public. Vérifiez à la fois le navigateur et les journaux. Les journaux d'accès Nginx montrent si la requête a atteint Nginx. Les journaux d'erreur montrent les échecs de connexion, les délais d'attente en amont et les détails de mauvaise passerelle.

Un exemple pratique : votre application Node.js fonctionne bien sur curl http://127.0.0.1:3000, mais le site public affiche 502 Bad Gateway. Cela signifie que Nginx est accessible, mais qu'il ne peut pas communiquer avec succès avec l'amont. Vérifiez si l'application écoute sur l'adresse attendue, si le port est correct et si un pare-feu local bloque la connexion.

Les problèmes courants de proxy inverse incluent :

  • Mauvais port ou adresse en amont.
  • Backend lié à localhost lorsque Nginx s'exécute dans un autre conteneur.
  • En-têtes de mise à niveau WebSocket manquants.
  • Application rejetant les requêtes car l'en-tête Host est inattendu.
  • Réécriture d'URI incorrecte causée par une barre oblique finale.
  • Délais d'attente trop courts pour un point de terminaison lent.

Pour les échecs en amont plus profonds, utilisez Dépannage Nginx 502.

Quand demander de l'aide

Demandez l'aide d'un ingénieur DevOps si le proxy inverse s'étend sur plusieurs conteneurs, réseaux privés, certificats TLS ou amonts équilibrés en charge. Ces configurations peuvent échouer de manière à ressembler à des problèmes Nginx mais sont en réalité des problèmes DNS, de pare-feu, de réseau de conteneurs ou de santé d'application.

Vous devriez également demander de l'aide avant d'exposer des panneaux d'administration, des API internes ou des services de staging via un proxy inverse public. De petites erreurs de routage peuvent créer de sérieux problèmes d'accès.

Une configuration de proxy inverse Nginx est l'un des modèles les plus utiles dans l'infrastructure web. Gardez le routage clair, transmettez les bons en-têtes, testez soigneusement le comportement du chemin et laissez Nginx être le point d'entrée public stable pour vos services backend.