Reverse proxy Nginx : configuration pas à pas, sécurisation et optimisations

Configurez un reverse proxy Nginx fiable et sécurisé en suivant ces étapes clés.

  • Dépôt officiel nginx.org pour une version stable et corrigée.
  • Testez avec nginx -t avant tout redémarrage.
  • SELinux (RHEL) : activez httpd_can_network_connect.
  • Obtenez un certificat HTTPS avec Certbot et le plugin Nginx.
  • Activez HSTS (31536000s) et désactivez TLS 1.0/1.1.
  • Utilisez add_header avec always pour les erreurs.

Installation de Nginx : dépôt officiel et premières commandes

C’est en 2004 qu’Igor Sysoev a créé Nginx, un serveur conçu pour gérer un trafic massif avec une faible consommation de ressources. Pour une configuration reverse proxy fiable, la première étape consiste à installer une version stable et récente, plutôt que celle, souvent obsolète, des dépôts système.

Voici la marche à suivre selon votre distribution :

  • Dépôt officiel nginx.org indispensable pour obtenir la version stable la plus récente, avec les correctifs de sécurité.
  • Ubuntu/Debian ajoutez le dépôt puis exécutez apt install nginx pour une installation rapide.
  • RHEL/Rocky/Fedora créez le fichier /etc/yum.repos.d/nginx.repo, puis lancez dnf install nginx.
  • SELinux (RHEL) activez setsebool -P httpd_can_network_connect 1, sinon les connexions sortantes seront bloquées par défaut.

Une fois l’installation terminée, démarrez et activez le service au démarrage :

La commande systemctl enable --now nginx s’occupe de tout. Vérifiez ensuite que le port 80 (le port d’écoute HTTP par défaut) est bien ouvert, puis testez la configuration avec nginx -t avant tout redémarrage.

En suivant ces étapes, vous disposez d’un serveur Nginx fonctionnel en 5 minutes pour servir un site statique, avant d’aborder la configuration du reverse proxy.

Sécurisation d’un reverse proxy Nginx : HTTPS, en-têtes et rate limiting

configurer un reverse proxy nginx

Un reverse proxy non sécurisé expose votre application à des risques majeurs. La première barrière à ériger est le HTTPS : Google pénalise les sites non chiffrés, et vos utilisateurs méritent une connexion protégée. La bonne nouvelle est que cette protection est désormais gratuite et automatisable.

Obtenir un certificat HTTPS gratuit avec Let’s Encrypt et Certbot

  • Certificat valide 90 jours Let’s Encrypt impose un renouvellement fréquent, par sécurité.
  • Certbot automatise le renouvellement via un timer systemd, sans intervention manuelle.
  • TLS 1.2/1.3 uniquement désactivez TLS 1.0 et 1.1 dans la configuration Nginx.
  • max-age HSTS : 31536000 secondes équivaut à un an, impose le HTTPS à tous les visiteurs.

Installez Certbot avec le plugin Nginx, puis exécutez certbot --nginx -d api.exemple.com. L’outil modifie automatiquement votre configuration et rechargement Nginx. En cas d’erreur, vérifiez que le port 80 est accessible depuis l’extérieur, car la validation HTTP est effectuée sur ce port.

Durcir les en-têtes de sécurité et limiter le débit

Protéger les en-têtes HTTP est une couche défensive essentielle. La directive add_header nécessite le mot-clé always pour s’appliquer aussi aux réponses d’erreur, pas seulement aux réponses 200. Attention : l’héritage en Nginx n’est pas cumulatif si vous déclarez un add_header dans un bloc location, il annule tous ceux définis au niveau du serveur parent.

Le rate limiting protège votre backend contre les abus et attaques par force brute. Deux directives suffisent :

  • limit_req_zone : 10 req/s max par IP, avec une zone mémoire de 10MB.
  • burst=20 avec nodelay permet de courtes rafales sans bloquer les utilisateurs légitimes.

Pour les réponses, la compression gzip réduit la taille des réponses de 60 à 80 %, un gain de performance significatif. N’oubliez pas de tester votre configuration avec nginx -t avant de recharger le service, afin d’éviter toute coupure involontaire.

Qu’est-ce qu’un reverse proxy Nginx et pourquoi l’utiliser ?

Un reverse proxy Nginx est un serveur intermédiaire qui se positionne entre les utilisateurs d’Internet et vos applications backend. Né en 2004 sous la plume d’Igor Sysoev, Nginx s’est imposé comme la solution de référence pour cette mission, bien au-delà de son rôle initial de serveur web. Il agit comme un véritable chef d’orchestre : une seule interface publique pour recevoir les requêtes, et une multitude de serveurs backend pour y répondre.

  • Intermédiaire entre utilisateurs et application : il reçoit les requêtes HTTP et les relaie vers le serveur applicatif, puis renvoie la réponse au client.
  • Évite l’exposition directe du backend : votre application reste isolée sur le réseau interne, inaccessible depuis l’extérieur.
  • Nginx termine le TLS avant le backend : le déchiffrement HTTPS s’effectue au niveau du proxy, déchargeant l’application de cette tâche coûteuse.
  • Une interface pour les requêtes, une pour les backends : vous ne gérez qu’un seul point d’entrée public, quelle que soit la complexité de vos serveurs internes.
  • API Node.js : localhost:3000 → api.example.com : une application écoutant sur le port 3000 devient accessible via un nom de domaine propre et sécurisé.

Pourquoi adopter cette architecture ?

L’intérêt principal réside dans la séparation des responsabilités. Votre backend peut écouter sur un port non privilégié comme 3000 ou 8080, sans se soucier du trafic entrant. Nginx gère à sa place la terminaison TLS (avec des protocoles stricts TLS 1.2/1.3), le cache des réponses, la compression gzip qui réduit la taille des transferts de 60 à 80%, et la limitation du débit. Vous bénéficiez ainsi d’une couche de protection et d’optimisation centralisée, sans toucher au code de votre application.

Cette architecture offre aussi une flexibilité précieuse : vous pouvez rediriger le trafic vers plusieurs backends (load balancing), basculer vers un serveur de secours, ou modifier l’infrastructure interne sans changer l’adresse publique. Le tout avec une configuration simple et un gain de performance immédiat, Nginx étant conçu pour gérer un grand nombre de connexions simultanées avec une consommation mémoire minimale.

Optimisation : load balancing, cache, compression et timeouts

Directive Rôle Exemple de valeur
proxy_cache_path Définit la zone de cache disque /var/cache/nginx levels=1:2 keys_zone=my_cache:10m
proxy_cache_valid Durée de conservation par code réponse 200 60m, 404 1m
gzip Compresse les réponses texte on
proxy_read_timeout Timeout de lecture du backend 120s
upstream Définit le groupe de serveurs backend server localhost:3000 weight=3
keepalive Connexions persistantes vers le backend 32

Compression gzip : un gain immédiat de performance

Activez gzip sur les réponses texte (HTML, CSS, JavaScript, JSON) : la taille des réponses diminue de 60-80%, ce qui réduit d’autant la bande passante consommée et le temps de chargement perçu. Configurez-le dans le bloc http avec `gzip on; gzip_types text/css application/javascript application/json;`.

Cache proxy : alléger la charge du backend

Le cache proxy évite de solliciter l’application pour chaque requête. Déclarez `proxy_cache_path` au niveau http (jamais dans un bloc location), puis activez-le avec `proxy_cache my_cache` dans votre location. Fixez les durées selon le type de contenu avec proxy_cache_valid 200 60m : les pages statiques ou les réponses API peu changeantes sont servies directement par Nginx, le backend ne reçoit que les requêtes réellement nouvelles.

Load balancing : répartir la charge entre plusieurs backends

Définissez plusieurs serveurs dans un bloc upstream : Nginx applique alors un round-robin par défaut, ou `least_conn` pour privilégier le serveur le moins chargé. Le mode ip_hash garantit qu’un même visiteur retombe toujours sur le même backend (utile si vos sessions ne sont pas partagées). Ajoutez `server.. backup;` pour qu’un serveur ne prenne le relais qu’en cas de panne des autres.

Timeouts : adapter les délais aux traitements lourds

Un backend qui effectue des traitements longs (génération de rapports, envoi d’e-mails) peut dépasser le timeout par défaut de 60 secondes. Augmentez proxy_read_timeout à 120s pour éviter des erreurs intempestives. En complément, activez keepalive vers vos upstreams pour réutiliser les connexions TCP et réduire la latence de quelques millisecondes par requête.

Erreurs fréquentes : diagnostiquer un 502 Bad Gateway et autres codes

Un 502 Bad Gateway est l’erreur la plus courante lors de la mise en place d’un reverse proxy. Comprendre son origine permet de la résoudre en quelques minutes. Contrairement à ce que l’on pense souvent, ce code ne signale pas un problème de configuration Nginx, mais indique simplement que Nginx ne peut pas joindre le backend. Le diagnostic doit donc se concentrer sur l’application, pas sur le proxy lui-même.

  • 502 Bad Gateway : le backend est injoignable, pas un souci de syntaxe Nginx
  • Vérification cible : tester avec curl http://127.0.0.1:PORT pour isoler le problème
  • SELinux (RHEL) : cause fréquente de 502, activer httpd_can_network_connect 1
  • 403/404 : permissions ou racine incorrecte, penser aux droits 755 dossiers et 644 fichiers
  • Test préalable : nginx -t avant tout rechargement, il indique la ligne exacte

Vérifier le backend avec curl

La première action à mener face à un 502 est de confirmer que votre application répond bien localement. Une commande curl http://127.0.0.1:3000 (pour une API Node.js) ou curl http://127.0.0.1:8080 (pour Tomcat) permet de savoir si le backend écoute correctement sur le port attendu. Si l’application ne répond pas sur localhost, Nginx ne sera jamais en mesure de l’atteindre, quel que soit votre fichier de configuration.

Autre point souvent négligé : la résolution DNS. Par défaut, Nginx ne résout les noms de domaine du backend qu’au démarrage. Si votre backend change d’adresse IP sans redémarrer Nginx, les requêtes continueront vers l’ancienne adresse, provoquant des 502 intermittents. L’ajout d’un bloc resolver avec une durée de validité courte (par exemple valid=5s) règle ce problème pour les environnements Docker ou Kubernetes.

Environnement RHEL et SELinux

Sur CentOS, Rocky Linux ou Fedora, SELinux est souvent la cause cachée des erreurs 502. Le proxy a besoin d’une autorisation explicite pour établir des connexions réseau sortantes vers le backend. Sans cette autorisation, Nginx fait correctement son travail mais se voit bloquer par le système, et l’utilisateur reçoit un 502. La commande setsebool -P httpd_can_network_connect 1 active définitivement cette permission.

Pour les erreurs 403 Forbidden, vérifiez les permissions des fichiers et dossiers servis. Corrigez avec chmod 755 sur les répertoires et chmod 644 sur les fichiers. Enfin, adoptez le réflexe nginx -t puis nginx -s reload après chaque modification : deux commandes qui évitent la grande majorité des erreurs de configuration.

Configuration avancée : WebSockets et cas pratiques

WebSockets et headers de forwarding

Les WebSockets reposent sur un mécanisme de mise à niveau HTTP : le client envoie une requête avec l’en-tête Upgrade, et le serveur répond en basculant la connexion vers un canal bidirectionnel. Par défaut, Nginx relaie en HTTP/1.0 vers le backend, un protocole qui ne transmet pas cet en-tête. Pour que la mise à niveau fonctionne, il faut forcer proxy_http_version 1.1 dans le bloc location et ajouter les headers Upgrade et Connection en utilisant la variable $http_upgrade.

Le forwarding des adresses IP repose sur proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for. Contrairement à une simple affectation, $proxy_add_x_forwarded_for complète la chaîne existante transmise par les proxies précédents, sans jamais l’écraser. Associez-y X-Real-IP et Host pour des logs exploitables côté application. Un bloc WebSocket typique se présente ainsi :

location /ws/ {
 proxy_pass http://localhost:3000;
 proxy_http_version 1.1;
 proxy_set_header Upgrade $http_upgrade;
 proxy_set_header Connection "upgrade";
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 proxy_read_timeout 120s;
}

Exemples concrets : Gunicorn, Node.js et Docker

Voici trois configurations éprouvées, chacune avec ses particularités :

  • Gunicorn : backend sur 127.0.0.1:8000, un classique pour Django et Flask aucun réglage spécial, il suffit de définir proxy_pass http://127.0.0.1:8000; dans le bloc location.
  • Node.js : application sur localhost:3000, nécessite le bloc WebSocket complet vu plus haut pour les fonctionnalités temps réel type Socket.IO.
  • Docker : Nginx dans un conteneur doit résoudre le nom du conteneur backend dynamiquement, d’où l’ajout de resolver 127.0.0.11 valid=5s sans cela, il mémorise l’IP au démarrage et perd la trace si le conteneur est recréé.

Pour un cluster Kubernetes, la directive devient resolver kube-dns.kube-system.svc.cluster.local, et vous devez définir une variable pour que Nginx interroge le DNS à chaque requête : set $backend http://mon-service:3000; puis proxy_pass $backend;. Cette indirection est indispensable pour accompagner la scalabilité des pods.

Configuration de base : location et proxy_pass

La directive proxy_pass et les blocs location

La directive proxy_pass est le cœur du reverse proxy Nginx : elle définit vers quel backend la requête doit être transmise. Dans sa forme la plus simple, elle pointe vers une adresse IP et un port non privilégié, par exemple localhost:3000 pour une API Node.js, car Nginx lui-même écoute sur le port 80 par défaut.

Le bloc location détermine quelles URLs sont concernées par ce proxy. Le point le plus subtil concerne le slash final. Si vous écrivez proxy_pass http://localhost:3000; sans slash, l’URI complète d’origine est transmise telle quelle. En revanche, avec http://localhost:3000/;, Nginx remplace le préfixe du location par le slash, modifiant ainsi l’URI transmise au backend et cela sans jamais générer de message d’erreur.

Un exemple typique pour une API :

location /api/ {
 proxy_pass http://localhost:3000;
}

Dans ce cas, une requête vers /api/users sera transmise intégralement au backend. Le choix du port backend repose sur une règle simple : utilisez toujours un port non privilégié (supérieur à 1024) comme 3000, 8080 pour Tomcat, ou 7080 pour Apache, jamais un port réservé.

Les 4 proxy_set_header indispensables pour des logs exploitables

Sans configuration explicite des en-têtes, votre backend verra toutes les requêtes provenir de 127.0.0.1 et ne pourra pas générer de logs exploitables. Ces quatre proxy_set_header sont le minimum vital :

  • Host transmet le nom de domaine d’origine obligatoire pour le routage.
  • X-Real-IP donne l’adresse IP réelle du client à l’application.
  • X-Forwarded-For utilise $proxy_add_x_forwarded_for qui préserve la chaîne existante.
  • X-Forwarded-Proto indique le schéma original (http ou https).

La directive X-Forwarded-For mérite une attention particulière : $proxy_add_x_forwarded_for ajoute l’IP du client à la liste existante au lieu de l’écraser. Cette distinction est cruciale si votre architecture comprend déjà un autre proxy en amont.