Sécuriser n8n avec un reverse proxy Nginx, TLS et des en-têtes de sécurité

15 min de lecture·Matthieu·webhookssecuritylets-encryptreverse-proxynginxn8n|

Placez votre instance n8n auto-hébergée derrière Nginx avec TLS Let's Encrypt, des en-têtes de sécurité, du rate limiting, des règles de pare-feu et une protection des webhooks. Chaque étape inclut une commande de vérification.

Ce tutoriel place votre instance n8n auto-hébergée derrière Nginx avec TLS, des en-têtes de sécurité, du rate limiting et des règles de pare-feu. Vous passerez d'un n8n exposé sur le port 5678 à une configuration prête pour la production où seul le trafic HTTPS atteint l'éditeur et les webhooks.

Prérequis :

  • n8n en fonctionnement via Docker Compose sur un VPS ()
  • Un nom de domaine (ex. n8n.example.com) avec un enregistrement A pointant vers l'IP de votre serveur
  • Un accès SSH au serveur avec un utilisateur non-root disposant de sudo
  • Les ports 80 et 443 ouverts au niveau du pare-feu de votre hébergeur (Virtua Cloud les ouvre par défaut)

Ce guide suppose Ubuntu 24.04 LTS. Les commandes fonctionnent sur Debian 12 avec des ajustements mineurs. n8n version 2.x est utilisé tout au long du guide.

Comment configurer Nginx comme reverse proxy pour n8n avec le support WebSocket ?

Nginx se place entre Internet et n8n, et transmet les requêtes à localhost:5678. L'éditeur n8n repose sur des connexions WebSocket pour les mises à jour en temps réel. Sans un proxying WebSocket correct, l'interface de l'éditeur se fige et affiche « Connection lost ». Vous avez besoin de proxy_pass vers localhost:5678, des en-têtes HTTP/1.1 d'upgrade pour WebSocket, et de la variable d'environnement N8N_PROXY_HOPS=1 pour que n8n lise la vraie IP du client depuis X-Forwarded-For.

Installer Nginx

sudo apt update && sudo apt install -y nginx

Vérifiez que Nginx fonctionne :

sudo systemctl status nginx

Vous devriez voir active (running) en vert. Sinon, démarrez-le et activez-le :

sudo systemctl enable --now nginx

Le flag enable --now fait deux choses : enable fait démarrer Nginx automatiquement après un redémarrage, et --now le lance immédiatement.

Créer le bloc serveur Nginx

Créez un nouveau fichier de configuration pour votre domaine n8n :

sudo nano /etc/nginx/sites-available/n8n.example.com

Collez cette configuration. Remplacez n8n.example.com par votre domaine réel :

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;
    listen [::]:80;
    server_name n8n.example.com;

    # Will be replaced by Certbot later
    location / {
        proxy_pass http://127.0.0.1:5678;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        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_buffering off;
        proxy_cache off;
        chunked_transfer_encoding off;
    }
}

Le bloc map en haut gère les upgrades WebSocket. Quand un navigateur envoie un en-tête Upgrade: websocket, Nginx le transmet. Pour les requêtes HTTP classiques, il envoie close. Cela maintient la réactivité de l'éditeur n8n.

proxy_buffering off et proxy_cache off empêchent Nginx de mettre en tampon les server-sent events de n8n, ce qui causerait des latences dans l'éditeur.

Activez le site et testez la configuration :

sudo ln -s /etc/nginx/sites-available/n8n.example.com /etc/nginx/sites-enabled/
sudo nginx -t

Vous devriez voir :

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Si le test passe, rechargez :

sudo systemctl reload nginx

Vérifiez que n8n est accessible via Nginx :

curl -s -o /dev/null -w "%{http_code}" http://n8n.example.com

Une réponse 200 signifie que Nginx transmet le trafic à n8n. Un 502 signifie que n8n ne tourne pas sur le port 5678. Vérifiez avec docker ps.

Mettre à jour les variables d'environnement de n8n

Ouvrez le fichier .env de n8n (dans le même répertoire que votre docker-compose.yml) :

nano ~/n8n-docker/.env

Ajoutez ou mettez à jour ces variables :

N8N_HOST=n8n.example.com
N8N_PROTOCOL=https
N8N_PROXY_HOPS=1
WEBHOOK_URL=https://n8n.example.com/
N8N_EDITOR_BASE_URL=https://n8n.example.com/
N8N_SECURE_COOKIE=true

Rôle de chaque variable :

Variable Valeur Fonction
N8N_HOST Votre domaine Indique à n8n quel nom d'hôte utiliser dans les URL de webhook
N8N_PROTOCOL https n8n génère des URL de webhook en HTTPS au lieu de HTTP
N8N_PROXY_HOPS 1 n8n fait confiance à une couche de X-Forwarded-For pour obtenir la vraie IP du client
WEBHOOK_URL URL complète Remplace l'URL de base des webhooks générée automatiquement
N8N_EDITOR_BASE_URL URL complète URL utilisée dans les notifications par e-mail et les redirections SAML
N8N_SECURE_COOKIE true Les cookies ne sont envoyés qu'en HTTPS. Empêche le détournement de session en HTTP

Redémarrez n8n pour appliquer les changements :

cd ~/n8n-docker && docker compose down && docker compose up -d

Vérifiez que n8n a pris en compte le nouvel environnement :

docker compose logs --tail=20 n8n | grep -i "editor\|webhook\|proxy"

Vous devriez voir des lignes de log mentionnant votre domaine et le protocole HTTPS.

Comment ajouter TLS Let's Encrypt à mon reverse proxy n8n ?

Certbot automatise l'émission des certificats Let's Encrypt et configure automatiquement Nginx pour TLS. Après exécution de Certbot, tout le trafic HTTP est redirigé vers HTTPS, et votre éditeur n8n ainsi que vos webhooks sont chiffrés en transit.

Installer Certbot

sudo apt install -y certbot python3-certbot-nginx

Obtenir le certificat

sudo certbot --nginx -d n8n.example.com

Certbot va :

  1. Vérifier que vous possédez le domaine via un challenge HTTP-01
  2. Obtenir un certificat auprès de Let's Encrypt
  3. Modifier votre configuration Nginx pour ajouter les paramètres TLS et une redirection HTTP vers HTTPS

Quand une adresse e-mail est demandée, entrez une adresse réelle. Vous y recevrez les alertes d'échec de renouvellement.

Vérifier que TLS fonctionne

curl -I https://n8n.example.com

Vérifiez la présence de HTTP/2 200 dans la réponse. En cas d'erreur de certificat, attendez quelques minutes pour la propagation DNS.

Testez la chaîne de certificats depuis votre machine locale (pas le serveur) :

openssl s_client -connect n8n.example.com:443 -servername n8n.example.com </dev/null 2>/dev/null | head -20

Recherchez Verify return code: 0 (ok).

Vérifier le renouvellement automatique

Certbot installe un timer systemd qui renouvelle les certificats avant leur expiration. Confirmez qu'il est actif :

sudo systemctl status certbot.timer

Vous devriez voir active (waiting). Testez le processus de renouvellement sans renouveler réellement :

sudo certbot renew --dry-run

Un dry run réussi signifie que vos certificats se renouvelleront automatiquement tous les 60 à 90 jours.

Quels en-têtes de sécurité n8n nécessite-t-il en production ?

Les en-têtes de sécurité indiquent aux navigateurs comment traiter le contenu de votre site. Sans eux, votre éditeur n8n est vulnérable au clickjacking, aux attaques par MIME-type sniffing et au cross-site scripting. Ajoutez six en-têtes à votre configuration Nginx pour combler ces failles.

Ouvrez la configuration Nginx modifiée par Certbot :

sudo nano /etc/nginx/sites-available/n8n.example.com

Dans le bloc server qui écoute sur le port 443 (celui créé par Certbot), ajoutez ces en-têtes au-dessus du bloc location / :

    # Security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' wss://n8n.example.com; frame-ancestors 'none'" always;

Remplacez n8n.example.com dans la directive CSP connect-src par votre domaine réel. L'entrée wss:// autorise les connexions WebSocket vers l'éditeur.

Rôle de chaque en-tête :

En-tête Valeur Ce qu'il empêche
Strict-Transport-Security 1 an, sous-domaines inclus Le navigateur utilise toujours HTTPS. Bloque les attaques par SSL stripping
X-Frame-Options DENY Personne ne peut intégrer votre éditeur n8n dans une iframe. Bloque le clickjacking
X-Content-Type-Options nosniff Le navigateur fait confiance au type MIME déclaré. Bloque les attaques par MIME confusion
Referrer-Policy strict-origin-when-cross-origin Limite les informations de referrer transmises aux sites externes
Permissions-Policy Bloque caméra, micro, géolocalisation Le navigateur interdit l'accès aux API matérielles dont n8n n'a pas besoin
Content-Security-Policy Voir ci-dessus Contrôle quels scripts, styles et connexions le navigateur autorise

L'en-tête CSP est le plus complexe. L'éditeur n8n utilise des scripts inline et eval() pour le constructeur de workflows, donc 'unsafe-inline' et 'unsafe-eval' sont nécessaires pour script-src. C'est un compromis connu. Les autres directives sont restrictives : seules les ressources de même origine et les connexions WebSocket vers votre domaine sont autorisées.

Testez et rechargez :

sudo nginx -t && sudo systemctl reload nginx

Vérifiez la présence des en-têtes :

curl -sI https://n8n.example.com | grep -iE "strict-transport|x-frame|x-content|referrer|permissions|content-security"

Vous devriez voir les six en-têtes dans la sortie. Si l'un manque, vérifiez les erreurs de frappe dans le fichier de configuration.

Comment configurer le pare-feu pour n8n sur un VPS ?

UFW bloque tout le trafic sauf ce que vous autorisez. Pour n8n derrière Nginx, seuls trois ports doivent être ouverts : 22 (SSH), 80 (HTTP, pour le renouvellement Certbot et les redirections) et 443 (HTTPS). Le port 5678, où n8n écoute directement, doit être bloqué depuis l'extérieur. Beaucoup de guides sautent cette étape, laissant n8n accessible sans TLS.

Pour approfondir UFW, consultez .

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp comment 'SSH'
sudo ufw allow 80/tcp comment 'HTTP - Certbot renewal'
sudo ufw allow 443/tcp comment 'HTTPS - n8n via Nginx'
sudo ufw enable

Quand la confirmation est demandée, tapez y. Vérifiez les règles :

sudo ufw status verbose

Vous devriez voir trois règles ALLOW pour les ports 22, 80 et 443. Rien d'autre. Le port 5678 n'apparaît pas, ce qui signifie qu'il est bloqué.

Confirmez que n8n n'est pas directement accessible. Depuis votre machine locale :

curl -s --connect-timeout 3 http://n8n.example.com:5678 || echo "Connection refused - correct!"

Si vous obtenez « Connection refused » ou un timeout, le pare-feu fonctionne. Si vous obtenez une réponse n8n, Docker contourne probablement UFW. Docker modifie directement iptables et peut outrepasser les règles UFW.

Lier n8n à localhost et empêcher Docker de contourner UFW

La solution la plus simple est de lier n8n à localhost uniquement. Dans votre docker-compose.yml, modifiez le mapping de ports :

    ports:
      - "127.0.0.1:5678:5678"

Cela garantit que n8n n'accepte que les connexions provenant de la même machine. Nginx sur le même serveur peut l'atteindre, mais le trafic extérieur ne le peut pas.

Redémarrez n8n :

cd ~/n8n-docker && docker compose down && docker compose up -d

Si cela seul ne suffit pas (testez de nouveau avec curl depuis votre machine locale), vous pouvez aussi désactiver la gestion iptables de Docker. Éditez /etc/docker/daemon.json :

sudo nano /etc/docker/daemon.json

Ajoutez :

{
  "iptables": false
}

Puis redémarrez Docker :

sudo systemctl restart docker
cd ~/n8n-docker && docker compose up -d

Attention : mettre "iptables": false empêche Docker de créer les règles NAT et de forwarding. Cela peut casser la communication entre conteneurs et l'accès Internet sortant depuis les conteneurs. Si vos workflows n8n font des requêtes HTTP vers des API externes (la plupart le font), testez la connectivité sortante après cette modification. Le binding sur localhost seul suffit en général.

Vérifiez de nouveau depuis votre machine locale que le port 5678 est inaccessible.

Comment appliquer du rate limiting sur les endpoints webhook de n8n dans Nginx ?

Le rate limiting protège vos endpoints webhook contre les abus et les tentatives de déni de service. Vous définissez un débit de requêtes par adresse IP. Les intégrations légitimes (GitHub, Stripe) envoient des webhooks à un rythme prévisible. Un attaquant qui martèle votre URL de webhook reçoit une réponse 429 Too Many Requests au lieu de déclencher des workflows.

Pour en savoir plus sur les stratégies de rate limiting, consultez .

Ajoutez une directive limit_req_zone en haut de votre fichier de configuration Nginx, en dehors de tout bloc server, à côté de la directive map existante :

limit_req_zone $binary_remote_addr zone=webhooks:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=editor:10m rate=30r/s;

Cela crée deux zones :

  • webhooks : 10 requêtes par seconde par IP. Gère les callbacks de services externes.
  • editor : 30 requêtes par seconde par IP. L'éditeur fait de nombreux petits appels API, il a besoin d'une limite plus élevée.

Dans le bloc server écoutant sur le port 443, ajoutez un bloc location séparé pour les chemins webhook avant le bloc location / principal :

    # Rate limit webhook endpoints
    location /webhook/ {
        limit_req zone=webhooks burst=20 nodelay;
        limit_req_status 429;

        proxy_pass http://127.0.0.1:5678;
        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;

        # Generous timeout for long-running webhook workflows
        proxy_read_timeout 300s;
        proxy_send_timeout 300s;
    }

    location /webhook-test/ {
        limit_req zone=webhooks burst=5 nodelay;
        limit_req_status 429;

        proxy_pass http://127.0.0.1:5678;
        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 paramètre burst=20 autorise des pics courts jusqu'à 20 requêtes avant que le rate limiting ne s'applique. Cela gère les cas comme GitHub qui envoie plusieurs événements webhook depuis un seul push. nodelay traite les requêtes en rafale immédiatement au lieu de les mettre en file d'attente.

Dans le bloc location / principal, ajoutez le rate limit de l'éditeur :

    location / {
        limit_req zone=editor burst=50 nodelay;
        limit_req_status 429;

        # ... existing proxy settings ...
    }

Testez et rechargez :

sudo nginx -t && sudo systemctl reload nginx

Vérifiez que le rate limiting fonctionne en envoyant une rafale de requêtes vers un webhook de test :

for i in $(seq 1 30); do
    curl -s -o /dev/null -w "%{http_code} " https://n8n.example.com/webhook/test-rate-limit
done
echo

Vous devriez voir un mélange de réponses 404 (le chemin webhook n'existe pas, c'est normal) et de réponses 429 une fois la limite atteinte.

Comment restreindre l'éditeur n8n à des adresses IP spécifiques ?

Par défaut, toute personne connaissant l'URL de votre n8n peut accéder à la page de connexion. L'allowlisting par IP au niveau Nginx ajoute une couche avant l'authentification propre à n8n. Seules les requêtes provenant de votre adresse IP (ou de votre VPN) atteignent l'éditeur. Les endpoints webhook restent ouverts à Internet pour que les services externes puissent les appeler.

Ajoutez un nouveau bloc location pour les chemins de l'éditeur. Placez-le après les locations webhook et avant le bloc location / principal :

    # Restrict editor/API access to specific IPs
    location /rest/ {
        allow 203.0.113.50;   # Your office/home IP
        allow 10.0.0.0/8;     # Your VPN range
        deny all;

        limit_req zone=editor burst=50 nodelay;

        proxy_pass http://127.0.0.1:5678;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        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_buffering off;
    }

Remplacez 203.0.113.50 par votre IP réelle. Trouvez-la avec :

curl -4 ifconfig.me

Le chemin /rest/ gère les appels API de l'éditeur n8n. Le bloc location / principal sert toujours le frontend de l'éditeur et les webhooks. Pour un verrouillage plus strict, vous pouvez aussi restreindre le chemin racine et ajouter des locations ouvertes séparées pour /webhook/ et /webhook-test/ uniquement.

Testez et rechargez :

sudo nginx -t && sudo systemctl reload nginx

Vérifiez depuis votre IP autorisée :

curl -s -o /dev/null -w "%{http_code}" https://n8n.example.com/rest/settings

Un 200 signifie que vous êtes autorisé. Depuis une autre IP (utilisez un téléphone mobile hors de votre Wi-Fi), accédez à https://n8n.example.com/rest/settings dans un navigateur. Vous devriez obtenir un 403 Forbidden.

Comment sécuriser les webhooks n8n contre les accès non autorisés ?

Les URL de webhook sont publiques par défaut. Toute personne qui découvre ou devine l'URL peut déclencher vos workflows. Deux stratégies protègent contre cela : garder les chemins webhook imprévisibles et valider les signatures HMAC dans vos workflows.

Utiliser les webhooks de production avec des ID uniques

n8n génère deux chemins webhook pour chaque nœud Webhook :

  • URL de test : /webhook-test/<id> (actif uniquement quand l'éditeur est ouvert)
  • URL de production : /webhook/<id> (actif quand le workflow est activé)

Le <id> est un UUID par défaut. Ne le changez pas pour quelque chose de devinable comme /webhook/github ou /webhook/stripe. L'UUID aléatoire est votre première couche de défense.

Valider les signatures HMAC dans les workflows

Des services comme GitHub et Stripe signent leurs payloads webhook avec un secret partagé. Votre workflow n8n doit vérifier cette signature avant de traiter les données.

Pour un webhook GitHub, ajoutez un nœud IF après le nœud Webhook avec cette condition :

  1. Dans les paramètres de votre dépôt GitHub, définissez un secret webhook (générez-en un avec openssl rand -base64 32)
  2. Dans votre workflow n8n, ajoutez un nœud Code après le nœud Webhook :
const crypto = require('crypto');
const secret = $env.GITHUB_WEBHOOK_SECRET;
const signature = $input.first().headers['x-hub-signature-256'];
const body = JSON.stringify($input.first().json);
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(body).digest('hex');

if (signature !== expected) {
  throw new Error('Invalid webhook signature');
}

return $input.all();
  1. Stockez le secret dans l'environnement n8n, pas en dur dans le workflow. Ajoutez-le à votre fichier .env :
GITHUB_WEBHOOK_SECRET=your-generated-secret-here

Et exposez-le à n8n dans docker-compose.yml :

    environment:
      - GITHUB_WEBHOOK_SECRET=${GITHUB_WEBHOOK_SECRET}

Pour les webhooks Stripe, le principe est similaire mais utilise un en-tête différent (stripe-signature) et une comparaison sécurisée en temps constant. Consultez la documentation Stripe sur les signatures webhook pour le schéma de signature actuel.

Masquer les informations de version de n8n

Par défaut, n8n inclut des informations de version dans les réponses API. Désactivez la divulgation de version pour rendre la reconnaissance plus difficile :

Dans votre fichier .env :

N8N_VERSION_NOTIFICATIONS_ENABLED=false

Dans votre configuration Nginx, ajoutez à l'intérieur du bloc server :

    server_tokens off;

La divulgation de version aide les attaquants à cibler des vulnérabilités connues dans des versions spécifiques de n8n. La masquer les oblige à sonder à l'aveugle.

Comment ajuster les timeouts et les limites d'upload pour n8n ?

Certains workflows n8n tournent pendant plusieurs minutes, traitant de gros jeux de données ou attendant des API externes. Le timeout par défaut de 60 secondes de Nginx coupe ces requêtes. Les workflows d'upload de fichiers échouent si le payload dépasse la limite par défaut de 1 Mo de Nginx.

Dans le bloc location / principal de votre serveur HTTPS, ajoutez ou mettez à jour :

    # Timeout tuning for long-running workflows
    proxy_connect_timeout 60s;
    proxy_read_timeout 300s;
    proxy_send_timeout 300s;

    # Allow file uploads up to 50 MB
    client_max_body_size 50m;

proxy_read_timeout 300s donne aux workflows jusqu'à 5 minutes pour répondre. Ajustez cette valeur en fonction de votre workflow le plus long. Le bloc location des webhooks a déjà ses propres paramètres de timeout depuis la section rate limiting.

client_max_body_size 50m autorise les uploads de fichiers jusqu'à 50 Mo via les endpoints webhook et de l'éditeur. Les workflows n8n qui traitent des imports CSV, des uploads d'images ou des conversions de documents en ont besoin.

Testez et rechargez :

sudo nginx -t && sudo systemctl reload nginx

Référence complète de la configuration Nginx

La configuration complète après toutes les modifications. Comparez-la avec votre fichier :

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

limit_req_zone $binary_remote_addr zone=webhooks:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=editor:10m rate=30r/s;

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

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name n8n.example.com;

    ssl_certificate /etc/letsencrypt/live/n8n.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/n8n.example.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    server_tokens off;

    # Security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' wss://n8n.example.com; frame-ancestors 'none'" always;

    client_max_body_size 50m;

    # Rate limit webhook endpoints
    location /webhook/ {
        limit_req zone=webhooks burst=20 nodelay;
        limit_req_status 429;

        proxy_pass http://127.0.0.1:5678;
        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_read_timeout 300s;
        proxy_send_timeout 300s;
    }

    location /webhook-test/ {
        limit_req zone=webhooks burst=5 nodelay;
        limit_req_status 429;

        proxy_pass http://127.0.0.1:5678;
        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;
    }

    # Restrict editor API to specific IPs
    location /rest/ {
        allow 203.0.113.50;
        allow 10.0.0.0/8;
        deny all;

        limit_req zone=editor burst=50 nodelay;

        proxy_pass http://127.0.0.1:5678;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        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_buffering off;
    }

    # Main location - editor frontend and fallback
    location / {
        limit_req zone=editor burst=50 nodelay;
        limit_req_status 429;

        proxy_pass http://127.0.0.1:5678;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        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_buffering off;
        proxy_cache off;
        chunked_transfer_encoding off;
        proxy_connect_timeout 60s;
        proxy_read_timeout 300s;
        proxy_send_timeout 300s;
    }
}

Comment vérifier que les connexions WebSocket fonctionnent dans l'éditeur n8n ?

L'éditeur n8n utilise une connexion WebSocket sur /rest/push pour recevoir les mises à jour d'exécution des workflows en temps réel. Si cette connexion échoue, vous verrez des bannières « Connection lost » et l'éditeur ne se mettra pas à jour après l'exécution des workflows.

Ouvrez l'éditeur n8n dans votre navigateur. Ouvrez les DevTools (F12), allez dans l'onglet Network et filtrez par « WS » (WebSocket). Vous devriez voir une connexion vers wss://n8n.example.com/rest/push avec le statut 101 (Switching Protocols).

Depuis la ligne de commande, testez l'upgrade WebSocket :

curl -sI -H "Upgrade: websocket" -H "Connection: Upgrade" -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" -H "Sec-WebSocket-Version: 13" https://n8n.example.com/rest/push

Une réponse 101 Switching Protocols confirme que le proxying WebSocket fonctionne. Un 200 ou 400 signifie que les en-têtes d'upgrade n'atteignent pas n8n. Revenez vérifier la directive map et les en-têtes proxy Upgrade/Connection.

Un problème ?

L'éditeur n8n affiche « Connection lost »

Le proxying WebSocket est cassé. Vérifiez que :

  1. Le bloc map $http_upgrade $connection_upgrade existe en haut de la configuration
  2. proxy_set_header Upgrade $http_upgrade et proxy_set_header Connection $connection_upgrade sont dans le bloc location /
  3. proxy_http_version 1.1 est défini (WebSocket nécessite HTTP/1.1)

Consultez les logs Nginx :

sudo journalctl -u nginx -f

« 502 Bad Gateway » après redémarrage

Le conteneur n8n ne tourne pas ou n'écoute pas sur le port 5678 :

docker ps | grep n8n
docker compose logs --tail=50 n8n

Le renouvellement Certbot échoue

Vérifiez que le timer fonctionne et testez manuellement :

sudo systemctl status certbot.timer
sudo certbot renew --dry-run

Si le renouvellement échoue, assurez-vous que le port 80 est ouvert dans UFW et que le bloc serveur HTTP est toujours présent (Certbot en a besoin pour le challenge HTTP-01).

« 403 Forbidden » depuis une IP autorisée

Votre IP a peut-être changé. Vérifiez votre IP actuelle avec curl -4 ifconfig.me et mettez à jour la directive allow dans la configuration Nginx.

Rate limiting trop agressif

Si des expéditeurs légitimes de webhooks reçoivent des erreurs 429, augmentez les valeurs rate et burst dans vos directives limit_req_zone et limit_req. Surveillez d'abord le débit des requêtes entrantes :

sudo tail -f /var/log/nginx/access.log | grep webhook

Prochaines étapes

  • Mettre en place des sauvegardes et mises à jour automatisées pour votre instance n8n ()
  • En savoir plus sur les patterns de reverse proxy Nginx ()
  • Explorer la configuration TLS en profondeur ()
  • Retourner à la vue d'ensemble de l'automatisation de workflows (auto-héberger l'automatisation de workflows)

Copyright 2026 Virtua.Cloud. Tous droits réservés. Ce contenu est une création originale de l'équipe Virtua.Cloud. Toute reproduction, republication ou redistribution sans autorisation écrite est interdite.

Prêt à essayer ?

Déployez votre serveur en quelques secondes. Linux, Windows ou FreeBSD.

Voir les offres VPS