Skip to Content

Chapitre 9.4 — Nginx devant Next

⏱️ TL;DR — Ton front tourne en service sur 127.0.0.1:3000, mais un visiteur tape formacampus.fr sur le 443. Il manque la façade : Nginx, qui reçoit le trafic HTTPS (certificat déjà posé en Partie 8) et relaie vers Next avec proxy_pass http://127.0.0.1:3000. Ce chapitre te donne le server block complet pour Next.js : les en-têtes à transmettre (dont X-Forwarded-Proto, sans quoi Next croit être en HTTP), le support des WebSockets, le cache long sur /_next/static, client_max_body_size pour les uploads, et comment faire cohabiter plusieurs apps (front sur 3000, API sur 4000) derrière un seul Nginx.

🎯 Objectifs

  • Écrire le server block Nginx complet pour proxifier une app Next.js en HTTPS.
  • Transmettre les bons en-têtes, dont X-Forwarded-Proto (pour que Next sache qu’il est en HTTPS).
  • Servir /_next/static avec un cache long et gérer le support WebSocket.
  • Régler client_max_body_size pour autoriser les uploads.
  • Router plusieurs apps (front + API) sur un même domaine derrière un seul Nginx.

Le rôle de Nginx, appliqué à Next

On a vu le reverse proxy en Partie 7 et le HTTPS en Partie 8. On les applique ici à Next.js. Le principe reste : Nginx écoute en façade sur 443, gère le TLS, sert le statique à toute vitesse, et relaie (proxy_pass) le dynamique vers Next sur son port privé.

Le server block complet pour Next.js

Voici la configuration complète et commentée. On part du principe que Certbot a déjà posé le TLS (Partie 8) : les lignes ssl_certificate sont donc en place. On se concentre sur ce qui est spécifique à Next.

# /etc/nginx/sites-available/formacampus # Redirige tout le HTTP vers HTTPS server { listen 80; listen [::]:80; server_name formacampus.fr www.formacampus.fr; return 301 https://$host$request_uri; } server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name formacampus.fr www.formacampus.fr; # Certificats poses par Certbot (Partie 8) ssl_certificate /etc/letsencrypt/live/formacampus.fr/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/formacampus.fr/privkey.pem; # Autoriser des uploads consequents (0 = illimite, a eviter) client_max_body_size 25m; # --- Assets buildes : cache long et immuable --- # Next versionne ces fichiers par hash : on peut les cacher tres longtemps location /_next/static/ { proxy_pass http://127.0.0.1:3000; proxy_cache_valid 200 60m; add_header Cache-Control "public, max-age=31536000, immutable"; } # --- Tout le reste : proxy vers le service Next --- location / { proxy_pass http://127.0.0.1:3000; # En-tetes indispensables : dire a Next qui appelle et comment 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 X-Forwarded-Host $host; # Support WebSocket (sockets applicatifs eventuels) proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; # Timeouts confortables pour du SSR un peu lent proxy_read_timeout 60s; } }

On active ce site comme d’habitude (Partie 7.2) : lien symbolique dans sites-enabled/, test, rechargement.

sudo ln -s /etc/nginx/sites-available/formacampus /etc/nginx/sites-enabled/ sudo nginx -t # TOUJOURS tester la syntaxe avant de recharger sudo systemctl reload nginx # applique sans couper les connexions en cours

⚠️ Piège — Modifier la config et faire systemctl restart nginx sans nginx -t d’abord. Si la config a une faute, Nginx refuse de redémarrer et ton site tombe entièrement. Le réflexe systématique : nginx -t (qui valide la syntaxe), et seulement si c’est ok, systemctl reload nginx (qui applique sans couper les connexions en cours, contrairement à restart).

X-Forwarded-Proto : le piège du HTTPS invisible

Voici le piège spécifique au couple Nginx + Next. Nginx parle à Next en HTTP clair sur 127.0.0.1:3000 (le TLS s’arrête à Nginx — c’est le principe). Du coup, Next croit que la requête est en HTTP, pas en HTTPS. Conséquences visibles : des redirections cassées (Next redirige vers http://), des URL absolues en http:// dans les pages, des cookies Secure qui ne partent pas, une détection de protocole erronée.

La solution : Nginx informe Next du protocole d’origine via l’en-tête X-Forwarded-Proto (mis à $scheme, donc https). Encore faut-il que Next fasse confiance à cet en-tête. Beaucoup de librairies (NextAuth, middlewares, helpers d’URL) ont une option « trust proxy » ou lisent X-Forwarded-* — active-la, ou configure l’URL de base de ton app explicitement.

⚠️ Piège — Un login qui « ne marche qu’en prod », des redirections vers http:// malgré le cadenas, ou des cookies de session qui disparaissent : quasi toujours un X-Forwarded-Proto manquant ou ignoré. Vérifie deux choses : (1) Nginx envoie bien proxy_set_header X-Forwarded-Proto $scheme; ; (2) ton app (ou sa lib d’auth) est configurée pour faire confiance au proxy et lire cet en-tête. Les deux sont nécessaires.

💡 Réflexe — Transmets toujours le quatuor d’en-têtes derrière un proxy : Host (sinon Next voit 127.0.0.1 au lieu du vrai domaine), X-Real-IP et X-Forwarded-For (pour connaître la vraie IP du visiteur, sinon tes logs et rate-limits voient l’IP de Nginx), et X-Forwarded-Proto (le protocole d’origine). C’est le kit de base de tout reverse proxy.

Les WebSockets

Le HMR (hot reload) de next dev utilise des WebSockets, mais tu ne l’exposes jamais en prod (next start n’a pas de HMR). En revanche, ton app peut ouvrir des WebSockets (notifications en temps réel, chat, live). Pour qu’ils passent le proxy, Nginx doit gérer la montée en gamme (upgrade) de la connexion HTTP vers WebSocket, d’où les lignes proxy_http_version 1.1, Upgrade et Connection.

La variable $connection_upgrade se définit une fois, dans le contexte http (dans nginx.conf ou un fichier de conf.d/) :

# Dans le contexte http { } global (ex. /etc/nginx/conf.d/upgrade.conf) map $http_upgrade $connection_upgrade { default upgrade; '' close; }

💡 Réflexe — Pas de WebSocket dans ton app ? Ces lignes ne gênent pas : map renvoie close quand il n’y a pas d’upgrade, et tout fonctionne normalement. Autant les mettre d’emblée pour ne pas avoir à déboguer un temps réel qui ne passe pas le jour où tu ajoutes des notifications live.

Le cache du statique /_next/static

Next place ses assets buildés (JS, CSS) sous /_next/static/, avec un hash dans le nom de chaque fichier. Un hash change à chaque build si le contenu change : le nom est donc un identifiant immuable de version. On peut sans risque dire au navigateur de cacher ces fichiers très longtemps (max-age=31536000, immutable = un an), puisqu’un nouveau build produit de nouveaux noms. Gain : les visiteurs qui reviennent ne retéléchargent pas le JS/CSS inchangé.

⚠️ Piège — Appliquer ce cache immuable d’un an à tout le site, et pas seulement à /_next/static/. Tes pages HTML (SSR/ISR), elles, changent au même URL : les cacher un an ferait servir aux visiteurs une version périmée pendant des mois, sans moyen de forcer le rafraîchissement. Le Cache-Control long est réservé aux assets à nom haché (/_next/static/, images versionnées) ; les pages gardent une politique courte ou no-cache. Ne mélange pas les deux.

client_max_body_size : autoriser les uploads

Par défaut, Nginx limite la taille du corps d’une requête (souvent 1 Mo). Un utilisateur qui envoie un avatar ou un PDF de cours au-delà se prend un 413 Request Entity Too Large, avant même que Next reçoive quoi que ce soit. La directive client_max_body_size relève le plafond.

⚠️ Piège — Un upload qui échoue en 413 alors que le code Next est bon : c’est Nginx qui bloque en amont, pas ton app. Relève client_max_body_size (ex. 25m) dans le server ou le location concerné, nginx -t, reload. À l’inverse, ne mets pas 0 (illimité) : tu ouvrirais la porte à des uploads géants qui saturent le disque ou la RAM. Fixe une limite réaliste pour ton cas.

Plusieurs apps derrière un seul Nginx

FormaCampus, c’est deux apps : le front Next.js (3000) et l’API Symfony (4000). Un seul Nginx les route toutes les deux, par chemin d’URL. On ajoute un location pour l’API avant le location / attrape-tout.

# L'API Symfony ecoute en local sur 4000 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; } # Le front Next attrape tout le reste location / { proxy_pass http://127.0.0.1:3000; # ... (memes en-tetes que plus haut) ... }

Nginx choisit le location le plus spécifique : une requête vers /api/cours part vers l’API (4000), tout le reste vers le front (3000). Tu pourrais aussi router par sous-domaine (app.formacampus.fr → 3000, api.formacampus.fr → 4000) avec deux server blocks distincts — au choix, selon ton architecture d’URL. (Rappel des vhosts multi-sites : Partie 7.5.)

🔒 Sécurité — Que tu aies une ou dix apps derrière Nginx, aucune n’écoute en dehors de 127.0.0.1. Les ports 3000 et 4000 restent locaux, invisibles d’Internet ; seul Nginx (443) est exposé, et le pare-feu UFW (Partie 5) bloque le reste. Un attaquant ne peut pas court-circuiter la façade pour taper directement l’API sur 4000 : elle n’est pas joignable de l’extérieur. Façade unique = surface d’attaque unique.

🧭 Sur FormaCampus — Nginx est désormais la porte d’entrée de formacampus.fr : il termine le TLS (certificat Let’s Encrypt), sert /_next/static/ avec un cache d’un an, route /api/ vers formacampus-api (4000) et tout le reste vers formacampus-web (3000), transmet X-Forwarded-Proto pour que le login Next fonctionne en HTTPS, et autorise 25 Mo d’upload pour les supports de cours. Les deux services Node/PHP restent au chaud sur 127.0.0.1. Le cycle de vie d’une requête vu en Partie 1.5 est maintenant entièrement câblé.

📚 La doc — Les directives proxy_*, map, location et leur ordre de priorité sont documentés sur nginx.org (module ngx_http_proxy_module). Pour les recommandations TLS (protocoles, ciphers) posées par Certbot, voir la Partie 8. En cas de 502/504/413, le chapitre 7.6 détaille la lecture des logs Nginx.

✏️ Exercices

Exercice 1 — Le login casse en prod. En HTTPS, ton app redirige vers des URL http:// et les cookies de session Secure ne s’installent pas. En local (HTTP) tout marchait. Cause probable et correctif en deux points.

✅ Solution

Nginx parle à Next en HTTP clair sur 127.0.0.1:3000, donc Next croit être en HTTP et génère des URL/redirections http://, tandis que les cookies Secure ne partent pas. Correctif en deux points : (1) Nginx doit envoyer proxy_set_header X-Forwarded-Proto $scheme; (donc https) ; (2) l’app (ou sa lib d’auth, type NextAuth) doit être configurée pour faire confiance au proxy et lire X-Forwarded-Proto. Les deux sont indispensables — l’en-tête sans la confiance, ou l’inverse, ne suffit pas.

Exercice 2 — Upload en 413. Un formateur tente d’envoyer un PDF de 8 Mo et reçoit 413 Request Entity Too Large. Le code Next gère pourtant les gros fichiers. Où est le blocage et comment le lever ?

✅ Solution

Le 413 vient de Nginx, pas de Next : Nginx limite par défaut la taille du corps de requête (souvent 1 Mo) et rejette avant même de transmettre à l’app. On relève client_max_body_size (ex. 25m) dans le server (ou le location d’upload), puis sudo nginx -t et sudo systemctl reload nginx. On évite 0 (illimité), qui exposerait à des uploads géants saturant disque/RAM : on met une limite réaliste.

Exercice 3 — Deux apps, un domaine. Tu veux servir le front Next (port 3000) sur tout le site sauf /api/ qui doit aller vers une API sur le port 4000. Écris les deux location.

✅ Solution

location /api/ { proxy_pass http://127.0.0.1:4000; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location / { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }

Nginx choisit le location le plus spécifique : /api/... part vers 4000, tout le reste vers 3000. Les deux ports restent locaux (127.0.0.1), non exposés. Alternative : router par sous-domaine avec deux server blocks.

🧠 Quiz de révision

1. Pourquoi transmettre X-Forwarded-Proto à Next ?

Parce que Nginx parle à Next en HTTP clair sur 127.0.0.1:3000 : sans cet en-tête, Next croit être en HTTP et casse redirections, URL absolues et cookies Secure. X-Forwarded-Proto $scheme lui indique le protocole d’origine (https). Il faut aussi que l’app soit configurée pour faire confiance au proxy et lire cet en-tête.

2. Pourquoi peut-on cacher /_next/static/ très longtemps mais pas les pages ?

Les fichiers de /_next/static/ ont un hash dans leur nom : un changement de contenu produit un nouveau nom, donc l’ancien peut être caché un an (immutable) sans risque de servir du périmé. Les pages HTML changent au même URL : un cache long y servirait une version obsolète. Cache long réservé aux assets à nom haché.

3. Que signifie un 413 et qui en est responsable ?

413 Request Entity Too Large : la requête dépasse la taille max autorisée. C’est Nginx qui bloque (via client_max_body_size, ~1 Mo par défaut), avant que l’app reçoive la requête. On lève la limite avec client_max_body_size (valeur réaliste, pas 0), puis nginx -t et reload.

4. Pourquoi tester avec nginx -t avant de recharger ?

Parce qu’une faute de syntaxe empêche Nginx de (re)démarrer, ce qui ferait tomber tout le site. nginx -t valide la config sans l’appliquer ; s’il répond ok, on fait systemctl reload nginx, qui applique sans couper les connexions en cours (contrairement à restart).

5. Comment router deux apps (front + API) derrière un seul Nginx ?

Avec des location distincts pointant vers des ports locaux différents : location /api/ { proxy_pass ...:4000; } avant le location / { proxy_pass ...:3000; } attrape-tout. Nginx choisit le plus spécifique. Alternative : deux server blocks par sous-domaine. Dans tous les cas, les ports restent locaux, seul Nginx est exposé.


Chapitre suivant : Zéro-downtime & logs — déployer une nouvelle version sans page blanche, brancher un healthcheck et lire les logs applicatifs.

Last updated on