Files
nas-runbooks/common/nginx-proxy-dns-resolution.md
T

6.4 KiB

Règle d'Or : Résolution DNS Dynamique dans les Proxies Nginx Docker

1. Contexte du Problème (Cause Racine de l'Erreur 502)

Dans un environnement Docker où les conteneurs sont reliés par un réseau virtuel ( ou réseau nommé partagé comme n8n), chaque conteneur peut obtenir une nouvelle adresse IP interne (ex. 172.27.0.x) à chaque redémarrage, mise à jour ou recréation.

Par défaut dans Nginx, lorsqu'une directive proxy_pass utilise un nom d'hôte statique direct :

# ❌ CONFIGURATION VULNÉRABLE (Résolution DNS statique au boot)
location / {
    proxy_pass http://mon-backend:8080;
}

Nginx résout le nom mon-backend une seule fois au démarrage de Nginx et met l'adresse IP en cache de manière permanente en mémoire.

Conséquence :

Dès que mon-backend redémarre ou est mis à jour :

  1. mon-backend reçoit une nouvelle adresse IP Docker (ex. 172.27.0.43).
  2. Nginx continue d'envoyer les requêtes vers l'ancienne IP (ex. 172.27.0.37).
  3. Nginx reçoit un Connection refused ou No route to host et renvoie un 502 Bad Gateway.
  4. Le service reste en panne jusqu'à un redémarrage manuel du conteneur Nginx.

2. Le Pattern Requis : resolver 127.0.0.11 + Variable Dynamique

Pour forcer Nginx à réévaluer la résolution DNS selon le TTL Docker sans dépendre d'un cache figé, deux éléments sont indispensables :

  1. La déclaration explicite du DNS interne Docker (127.0.0.11) avec un TTL court (ex. valid=10s).
  2. L'assignation de l'URL cible dans une variable intermédiaire (set $upstream_var ...). Nginx est conçu pour ne pas pré-résoudre les variables au boot, mais au moment du traitement de la requête.

Configuration Standard Recommandée :

server {
    listen 80;
    server_name service.bolbol.tn;

    # 1. DNS interne Docker (127.0.0.11) avec re-résolution toutes les 10s
    resolver 127.0.0.11 valid=10s ipv6=off;

    # 2. Définition dynamique de l'amont via variable
    location / {
        set $backend_upstream http://mon-service:8080;
        proxy_pass $backend_upstream;

        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;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_buffering off;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
        chunked_transfer_encoding on;
    }
}

3. Périmètre Appliqué sur le NAS (21/08/2026)

Service Proxy Fichier de Configuration Statut
baserow-oauth-proxy /volume1/docker/baserow-oauth-stub/nginx.conf Corrigé & Testé en direct
baserow-schema-mcp-proxy /volume1/docker/baserow-schema-mcp/nginx.conf Corrigé & Validé
redaction-pro /volume1/docker/redaction-pro/nginx.conf Corrigé & Validé
formation-consultant /volume1/docker/formation-consultant/nginx.conf N/A (fichiers statiques locaux, aucun upstream)

4. Protocole de Test de Résilience Obligatoire

Lors de la mise en place ou modification d'un reverse proxy Nginx :

  1. Valider la syntaxe : docker exec <proxy> nginx -t
  2. Recharger la config : docker exec <proxy> nginx -s reload
  3. Test d'attribution dynamique : Redémarrer le conteneur backend (docker restart <backend>) et vérifier avec curl l'accès via le proxy sans toucher au conteneur proxy.

Piege additionnel confirme (02/09/2026) — le prefixe de location n'est PAS tronque avec proxy_pass + variable

Symptome : sur redaction-pro, TOUS les appels API echouaient en 405 Method Not Allowed depuis le navigateur, quel que soit le modele choisi — alors que les memes appels fonctionnaient parfaitement en tapant directement l'URL de Bifrost. Aucun rapport avec la whitelist de modeles (voir common/bifrost-vk-incidents.md, incident du meme jour).

Cause : le pattern documente plus haut (set $upstream ...; proxy_pass $upstream;) resout bien le probleme de cache DNS, MAIS il a un effet de bord non documente jusqu'ici : quand proxy_pass cible une variable, nginx ne tronque plus jamais le prefixe du location — il transmet l'URI ORIGINALE complete au backend, contrairement a un proxy_pass avec une URI litterale qui tronque automatiquement le prefixe du location matche.

Concretement sur redaction-pro :

location /api/ {
    set $bifrost_upstream http://bifrost:8080;
    proxy_pass $bifrost_upstream/;   # BUG : le "/" final est ignore avec une variable
}

Une requete navigateur vers /api/v1/chat/completions etait transmise a Bifrost telle quelle, soit /api/v1/chat/completions — un chemin que Bifrost ne reconnait pas comme route API (son router de secours sert alors sa propre page de dashboard en GET, et renvoie 405 sur tout le reste).

Fix : ajouter un rewrite explicite AVANT le proxy_pass, pour forcer la troncature que la variable empeche :

location /api/ {
    set $bifrost_upstream http://bifrost:8080;
    rewrite ^/api/(.*)$ /$1 break;
    proxy_pass $bifrost_upstream;   # sans "/" final, inutile desormais
}

Valider avec docker exec <proxy> nginx -t puis nginx -s reload (pas besoin de redemarrer le conteneur).

Regle generale : des qu'un location a un PREFIXE non-racine (/api/, /mcp/, etc.) ET que le proxy_pass cible une variable (pattern DNS dynamique ci-dessus), un rewrite ... break; de troncature est obligatoire, sinon le backend recoit un chemin errone en silence (pas d'erreur nginx, juste un mauvais routage cote backend).

A verifier (non fait le 02/09, hors perimetre de la tache du jour) : baserow-schema-mcp/nginx.conf utilise location /mcp/ { proxy_pass $mcp_upstream; } — meme pattern a risque, jamais audite sous cet angle precis. A verifier si le backend MCP attend un chemin sans prefixe avant de considerer que c'est fonctionnel par coincidence ou reellement correct.

Tableau perimetre — mise a jour

Service Proxy Statut prefixe non-racine
redaction-pro (/api/) Bug trouve et corrige le 02/09/2026 (rewrite ajoute)
baserow-schema-mcp (/mcp/) ⚠️ A verifier — meme pattern a risque, non audite
baserow-oauth-proxy OK — utilise $upstream$request_uri (chemin complet volontaire), pas concerne