SSD Nodes Learn 🎉 VPS dès $5.50/mois
Guides Matt ConnorPar Matt Connor

Pourquoi n8n passe hors ligne sur votre VPS

Quatre pannes produisent le même symptôme n8n hors ligne : bannière websocket, boucle de redémarrage, kill OOM ou schedule inactif. Apprenez à les distinguer.

Pourquoi n8n passe hors ligne : quatre pannes, un même symptôme

« n8n passe hors ligne » peut désigner quatre pannes différentes, qui nécessitent chacune une correction spécifique. L’éditeur affiche une bannière indiquant que la connexion est perdue alors que le conteneur fonctionne normalement. Le conteneur redémarre tout seul. Le kernel tue le processus Node.js parce qu’il utilise trop de mémoire. Ou bien le processus fonctionne parfaitement, mais un workflow actif ne se déclenche jamais. Si vous modifiez le mauvais paramètre, vous risquez de passer un week-end sur un problème qui n’existe pas.

Identifiez donc la panne avant de modifier la configuration. n8n s’exécute comme un seul processus Node.js, généralement dans un conteneur Docker, derrière un reverse proxy qui termine TLS (transport layer security). Chacune de ces couches peut tomber en panne à sa manière, mais le navigateur signale toujours le même problème.

Diagnostiquez dans cet ordre

Exécutez ces commandes sur le VPS (virtual private server) et lisez les valeurs affichées par votre propre machine. Ne les comparez pas avec les chiffres d’une discussion sur un forum. Les valeurs importantes ici décrivent votre serveur, pas celui d’une autre personne.

docker ps -a --filter name=n8n
docker logs --tail 200 --timestamps n8n
docker inspect n8n | grep -iE 'Status|Running|RestartCount|OOMKilled|ExitCode'
docker stats --no-stream

La colonne STATUS de docker ps -a indique depuis combien de temps le conteneur est dans son état actuel. Comparez cette durée avec le moment où le problème a commencé. Si le conteneur fonctionne depuis bien avant l’apparition de la bannière, n8n n’a jamais été hors ligne. Le problème concerne la connexion entre votre navigateur et le backend, c’est-à-dire le chemin websocket présenté dans la section suivante.

RestartCount indique combien de fois Docker a redémarré ce conteneur. Notez ce nombre, attendez une minute, puis relevez-le à nouveau. S’il augmente pendant que vous observez la situation, le conteneur est pris dans une boucle de redémarrage. Les lignes de journal juste avant chaque redémarrage indiquent la cause.

OOMKilled est un indicateur true ou false. La valeur true signifie que le noyau Linux a tué le processus parce qu’il a dépassé une limite mémoire, celle du conteneur ou celle de la machine entière. Ce champ permet de distinguer un arrêt provoqué par un manque de mémoire de tous les autres types de sortie. C’est pourquoi vous devez le consulter avant de formuler une hypothèse.

ExitCode correspond au dernier code de sortie de votre conteneur. Vous n’avez pas besoin de mémoriser la signification de chaque code. Relevez le vôtre, puis consultez la fin de docker logs au même moment. La fin du journal et l’indicateur out of memory permettent ensemble de déterminer ce qui s’est passé. Pris séparément, chacun peut être trompeur.

docker stats affiche l’utilisation mémoire actuelle à côté de la limite effective. Laissez cette commande s’exécuter dans un second terminal, déclenchez le workflow qui provoque le problème et observez l’évolution de la valeur pendant l’échec.


La bannière « connexion perdue » vient généralement de votre reverse proxy

L’éditeur n8n maintient une connexion push longue durée avec le backend afin d’afficher la progression des exécutions sur le canvas. Par défaut, cette connexion est un WebSocket, sélectionné par N8N_PUSH_BACKEND, et sa valeur par défaut est websocket. Un WebSocket commence par une requête HTTP ordinaire qui contient les en-têtes Connection: Upgrade et Upgrade: websocket. Le serveur répond 101 Switching Protocols, puis les deux côtés utilisent la même socket TCP dans les deux directions.

Deux problèmes peuvent interrompre ce fonctionnement. Dans les deux cas, ils se situent dans le proxy et non dans n8n. Le proxy utilise HTTP/1.0 vers le backend ou supprime les en-têtes de mise à niveau. La mise à niveau n’a donc jamais lieu et l’éditeur se reconnecte indéfiniment. Ou bien la mise à niveau réussit, puis le proxy ferme la socket parce qu’elle reste inactive. Un WebSocket sans message ressemble en effet exactement à une connexion inactive. Dans les deux cas, le conteneur fonctionne correctement. La bannière indique simplement que le navigateur a perdu son canal.

Vérifiez ce point dans le navigateur avant de modifier quoi que ce soit. Ouvrez les outils de développement, accédez à l’onglet Network, filtrez sur WS, puis rechargez l’éditeur. La requête push doit atteindre 101 Switching Protocols et rester ouverte. Une requête push qui renvoie un code d’état ordinaire, ou qui réapparaît toutes les quelques secondes, indique un problème au niveau du proxy.

Les paramètres nginx qui maintiennent la connexion de l’éditeur

nginx ne transmet pas une requête d’upgrade sans configuration explicite. proxy_pass utilise HTTP/1.0 pour communiquer avec le backend par défaut, et Connection ainsi que Upgrade sont des en-têtes hop-by-hop que nginx supprime lors du transfert. Vous devez les rétablir tous les deux. Le bloc map se place dans le contexte http, et non à l’intérieur de server.

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}
server {
    listen 443 ssl;
    http2 on;
    server_name n8n.example.com;

    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_read_timeout 3600s;
        proxy_send_timeout 3600s;
        proxy_buffering off;
    }
}

proxy_read_timeout est la directive souvent oubliée. Sa valeur par défaut est de 60 secondes et elle s’applique également à un WebSocket en mode upgrade. Un onglet d’éditeur laissé ouvert sur une instance inactive perd donc sa connexion environ une minute après le dernier message transmis. Augmenter cette valeur corrige la bannière qui s’affiche lorsque vous revenez à un onglet resté ouvert.

sudo nginx -t && sudo systemctl reload nginx
sudo nginx -T | grep -iE 'proxy_http_version|upgrade|proxy_read_timeout'

nginx -T affiche l’intégralité de la configuration active, et non le contenu d’un seul fichier. Vous pouvez ainsi vérifier que votre modification est bien chargée. Une configuration placée dans un fichier qu’aucune directive include ne charge explique pourquoi une correction correcte semble ne rien changer.

Indiquez ensuite à n8n qu’il se trouve derrière un proxy, car il construit ses URL à partir de ces valeurs.

environment:
  - N8N_HOST=n8n.example.com
  - N8N_PROTOCOL=https
  - N8N_PORT=5678
  - N8N_PROXY_HOPS=1
  - N8N_WEBHOOK_URL=https://n8n.example.com/

Par défaut, N8N_PROXY_HOPS vaut 0. n8n considère alors l’adresse de connexion comme celle du client et ignore X-Forwarded-For. Définissez cette valeur sur le nombre de proxies placés devant le conteneur. En août 2026, N8N_WEBHOOK_URL est le nom actuel, tandis que l’ancien WEBHOOK_URL fonctionne encore, mais affiche un avertissement d’obsolescence au démarrage.

Traefik transmet les WebSockets, puis les interrompt à l’expiration du délai

Traefik transmet une requête de mise à niveau WebSocket sans middleware ni label supplémentaire. Un utilisateur de Traefik qui voit cette bannière rencontre donc généralement une expiration du délai, et non l’absence d’un en-tête. Les paramètres se trouvent sur l’entryPoint. En août 2026, dans Traefik v3, idleTimeout vaut 180 secondes par défaut et readTimeout vaut 60 secondes par défaut.

entryPoints:
  websecure:
    address: ":443"
    transport:
      respondingTimeouts:
        readTimeout: 0
        idleTimeout: 3600s

Caddy gère automatiquement la mise à niveau dans reverse_proxy et ne nécessite aucune directive pour cela. Si vous ne pouvez pas modifier le proxy, parce qu’il est géré par quelqu’un d’autre, remplacez le canal push par N8N_PUSH_BACKEND=sse. Les SSE (server-sent events) correspondent à une réponse HTTP normale maintenue ouverte. Ils continuent donc de fonctionner avec un proxy qui refuse les mises à niveau, mais un délai d’inactivité trop court les interrompt. Le choix du proxy est une décision distincte. La comparaison entre nginx, Caddy et Traefik détaille le coût d’exploitation de chacun.

Lorsque le conteneur redémarre réellement

Si RestartCount augmente, le conteneur échoue et Docker le relance. Comparez les horodatages des journaux avec chaque redémarrage et examinez ce qui s’est produit immédiatement avant. Quatre causes couvrent presque tous les cas : une erreur de configuration qui bloque le démarrage, une base de données qu’n8n ne peut pas joindre, un crash après le démarrage et un arrêt par manque de mémoire.

Commencez par le volume, car les permissions sont la cause la moins visible. L’image officielle s’exécute avec l’utilisateur non privilégié node et conserve ses données dans /home/node/.n8n. Un bind mount créé par root n’est pas accessible en écriture pour cet utilisateur. Le processus s’arrête donc au démarrage à chaque tentative et la restart policy masque le problème derrière une boucle.

docker compose config
docker run --rm -it --entrypoint sh docker.n8n.io/n8nio/n8n -c 'id'
docker exec n8n ls -ld /home/node/.n8n

Un volume nommé évite entièrement ce problème, car Docker le crée avec le propriétaire approprié. Si vous devez utiliser un bind mount, chown le répertoire hôte avec l’identifiant numérique de l’utilisateur affiché par la première commande. Il est utile de comprendre une fois pour toutes la correspondance des propriétaires entre l’hôte et le conteneur. L’explication sur PUID et PGID décrit comment ces images déterminent l’utilisateur qui écrit les fichiers.

Le kill pour manque de mémoire qui ressemble à un crash

Deux plafonds mémoire distincts s’appliquent au processus n8n, et ils ne provoquent pas le même échec. La limite du cgroup du conteneur est appliquée par le kernel : si le processus la dépasse, il est tué immédiatement, sans pouvoir rien écrire, et OOMKilled renvoie true. La limite du heap de V8 est appliquée dans Node.js : si le processus la dépasse, Node génère une erreur de heap avec une stack trace, puis se termine de lui-même ; OOMKilled renvoie donc false. Depuis le navigateur, ces deux cas semblent identiques. Dans docker inspect, ils ne diffèrent que d’un champ.

Définissez la limite du heap de Node en dessous de la limite du conteneur. Si la limite du heap est la plus élevée des deux, V8 continue ses allocations au-delà du point où le kernel intervient. Son garbage collector n’atteint donc jamais sa propre limite, et vous obtenez toujours l’échec le plus brutal, sans journal à consulter.

services:
  n8n:
    image: docker.n8n.io/n8nio/n8n
    restart: unless-stopped
    environment:
      - NODE_OPTIONS=--max-old-space-size=<MiB, below the container limit>
    deploy:
      resources:
        limits:
          memory: <your container limit>

Choisissez ces deux valeurs en fonction des ressources réellement disponibles sur votre VPS. Gardez de la mémoire pour la base de données, le proxy et le système d’exploitation. docker stats --no-stream affiche l’utilisation actuelle à côté de la limite appliquée. Vous pouvez ainsi vérifier que la limite indiquée est bien celle que Docker a appliquée. Comment les limites mémoire de Compose sont appliquées explique quelle clé est prioritaire lorsque plusieurs limites sont définies.

Les données d’exécution s’accumulent au fil des runs

Une exécution contient la sortie de chaque nœud pendant son déroulement, puis n8n stocke ces données. Deux conséquences en découlent. La mémoire maximale d’un run dépend du plus gros lot de données que vous lui transmettez. Un workflow qui traite dix mille lignes en une seule fois est donc un programme différent du même workflow qui en traite deux cents à la fois. La copie stockée continue aussi de grossir jusqu’à sa suppression.

Le pruning traite le second problème. En août 2026, les valeurs par défaut sont le pruning activé, EXECUTIONS_DATA_MAX_AGE à 336 heures (14 jours) et EXECUTIONS_DATA_PRUNE_MAX_COUNT à 10000. Ces valeurs sont élevées pour un petit VPS utilisant SQLite : un seul fichier contient toutes les données, et le même processus qui sert l’éditeur doit les lire et les écrire.

environment:
  - EXECUTIONS_DATA_PRUNE=true
  - EXECUTIONS_DATA_MAX_AGE=72
  - EXECUTIONS_DATA_PRUNE_MAX_COUNT=1000
  - EXECUTIONS_DATA_SAVE_ON_SUCCESS=none
  - EXECUTIONS_DATA_SAVE_MANUAL_EXECUTIONS=false

EXECUTIONS_DATA_SAVE_ON_SUCCESS=none est le réglage agressif. Il conserve les exécutions en échec pour le débogage et supprime les exécutions réussies. Décidez-le volontairement, car un workflow peut produire un résultat incorrect sans générer d’erreur et ne vous laisser alors rien à examiner. Le pruning marque également les lignes comme supprimées avant de les effacer lors d’un passage ultérieur. SQLite réutilise les pages libérées au lieu de les rendre au système, donc la taille du fichier sur disque ne diminue pas immédiatement après la modification du réglage.

Pour réduire le pic plutôt que le volume stocké, déplacez moins de données à chaque run. Divisez les traitements volumineux en sous-workflows qui renvoient de petits résultats au workflow parent, utilisez le nœud Loop Over Items pour traiter les données par lots et évitez de charger des jeux de données complets dans le nœud Code.

Les fichiers binaires ne doivent pas transiter par la mémoire

N8N_DEFAULT_BINARY_DATA_MODE est défini par défaut sur default, ce qui conserve les données binaires en mémoire pendant l’exécution. Chaque fichier téléchargé par un nœud, ainsi que chaque copie transmise au nœud suivant, y reste jusqu’à la fin de l’exécution. Un workflow qui récupère quelques pièces jointes volumineuses peut faire dépasser au processus une limite que les traitements JSON ordinaires n’atteignent jamais. C’est pourquoi le crash survient avec un workflow précis plutôt qu’à un moment donné.

environment:
  - N8N_DEFAULT_BINARY_DATA_MODE=filesystem

Avec filesystem, les données binaires sont écrites sous N8N_BINARY_DATA_STORAGE_PATH. Ce répertoire se trouve par défaut dans le dossier utilisateur de n8n et se situe donc sur le même volume que le reste. Vérifiez que le volume dispose de suffisamment d’espace avant d’effectuer la modification. N8N_PAYLOAD_SIZE_MAX définit la taille maximale, en Mio (mébioctets), de la charge utile entrante d’un webhook. Sa valeur par défaut est 16. L’augmenter permet d’accepter des requêtes plus volumineuses, au prix d’une consommation mémoire supplémentaire que vous choisissez d’assumer.

Tous les autres services présents sur le serveur se partagent la même RAM. Si les OOM killer ont commencé après l’ajout d’un conteneur de base de données, exécuter la base de données dans Docker ou sur l’hôte est le choix que vous devez maintenant faire.

Politique de redémarrage et reprise après un reboot

Un conteneur sans politique de redémarrage reste arrêté après sa sortie et après le redémarrage de l’hôte. restart: unless-stopped le redémarre dans les deux cas, tout en respectant un conteneur que vous avez arrêté manuellement. restart: always redémarre également un conteneur arrêté volontairement dès que Docker redémarre.

n8n expose un endpoint de healthcheck nommé par N8N_ENDPOINT_HEALTH, dont la valeur par défaut est healthz. Vérifiez-le d’abord depuis l’hôte afin de confirmer que le chemin est correct sur votre instance.

curl -fsS http://127.0.0.1:5678/healthz
docker exec n8n which wget curl
sudo systemctl is-enabled docker

Un healthcheck seul ne redémarre rien. Compose marque le conteneur comme non sain, puis s’arrête là. Pour produire un effet, le healthcheck doit être associé à une politique de redémarrage ou à un watcher externe. Écrire un healthcheck réellement actif et faire redémarrer la stack après un reboot couvrent ces deux aspects.

Le workflow qui ne se déclenche jamais alors que n8n fonctionne

Celui-ci n’affiche aucune bannière et ne redémarre pas. Le conteneur est en fonctionnement, l’éditeur fonctionne, et l’exécution attendue n’apparaît pas dans la liste des exécutions. Quatre causes expliquent la plupart des cas.

  • Le workflow n’est pas actif. Un Schedule Trigger s’exécute uniquement sur le chemin de production. Le tester dans le canvas ne programme donc rien.
  • Le fuseau horaire n’est pas le vôtre. GENERIC_TIMEZONE utilise America/New_York par défaut. Une tâche programmée à 09:00 s’exécute donc à 09:00 dans ce fuseau tant que vous n’avez pas défini GENERIC_TIMEZONE et TZ selon vos besoins.
  • Les périodes d’arrêt ne sont pas rattrapées. Les triggers sont enregistrés au démarrage de n8n. Une tâche arrivée à échéance pendant le redémarrage du conteneur ne s’exécute donc pas en retard. La prochaine exécution a lieu à la prochaine échéance après le démarrage.
  • Le workflow a été désactivé automatiquement. N8N_WORKFLOW_AUTODEACTIVATION_ENABLED est désactivé par défaut. Lorsqu’il est activé, un workflow qui échoue continuellement est dépublié. Il ressemble alors exactement à un workflow qui n’a jamais été activé.

Ouvrez la liste des exécutions et filtrez-la sur ce workflow. Une entrée en échec indique un problème du workflow. L’absence totale d’entrée indique un problème de trigger. Les quatre causes ci-dessus sont alors les premières vérifications à effectuer.

À modifier en premier

  1. Lisez vous-même STATUS, RestartCount et OOMKilled dans votre propre conteneur avant de modifier un fichier.
  2. Si le conteneur ne s’est jamais arrêté, corrigez les en-têtes de mise à niveau du proxy et le délai d’expiration d’inactivité.
  3. Si OOMKilled est vrai, définissez délibérément une limite pour le conteneur, placez la limite supérieure du tas Node en dessous de cette valeur et basculez les données binaires vers filesystem.
  4. Si rien ne s’est déclenché, vérifiez que le workflow est actif et que le fuseau horaire de l’instance est bien le vôtre.

La plupart de ces réglages se configurent une seule fois et ne nécessitent ensuite plus d’intervention, à condition que l’installation fonctionne. Si vous êtes encore en train de mettre cette installation en place, le guide n8n sur Docker avec HTTPS constitue la base à laquelle ces réglages s’ajoutent.

FAQ

Pourquoi l’éditeur n8n affiche-t-il une bannière indiquant que la connexion est perdue alors que le conteneur fonctionne ?

L’éditeur maintient une connexion WebSocket ouverte pour transmettre la progression des exécutions. Si votre reverse proxy ne transmet pas les en-têtes Connection: Upgrade et Upgrade: websocket, ou n’utilise pas HTTP/1.1 vers l’upstream, l’upgrade n’aboutit jamais et le navigateur se reconnecte indéfiniment alors que n8n fonctionne correctement. Avec nginx, vous devez définir proxy_http_version 1.1 ainsi que les deux lignes proxy_set_header, et utiliser un proxy_read_timeout supérieur aux 60 secondes par défaut pour éviter qu’un onglet inactif soit déconnecté. Vérifiez la configuration active avec sudo nginx -T, et non le fichier que vous avez modifié.

Comment distinguer un arrêt forcé pour manque de mémoire d’un crash classique ?

Exécutez docker inspect n8n | grep -iE 'OOMKilled|ExitCode|RestartCount' et consultez l’indicateur OOMKilled. La valeur True signifie que le kernel a arrêté le processus parce qu’il dépassait une limite de mémoire. Le journal du conteneur ne contiendra alors rien d’utile, car le processus n’a pas pu écrire. La valeur False, accompagnée d’une erreur de heap et d’une stack trace à la fin de docker logs, signifie que Node.js a atteint sa propre limite de heap V8 et s’est arrêté lui-même. Définissez NODE_OPTIONS=--max-old-space-size en dessous de la limite de votre conteneur pour obtenir ce second type d’échec, qui laisse des éléments de diagnostic.

La purge des données d’exécution libère-t-elle immédiatement de l’espace disque ?

Non. EXECUTIONS_DATA_PRUNE marque les anciennes exécutions pour suppression, puis un traitement ultérieur les supprime selon la planification définie par EXECUTIONS_DATA_PRUNE_HARD_DELETE_INTERVAL. Avec SQLite, le fichier réutilise également les pages libérées au lieu de les rendre au système de fichiers. Sa taille sur disque reste donc stable pendant un certain temps après la suppression des lignes. Définissez EXECUTIONS_DATA_MAX_AGE et EXECUTIONS_DATA_PRUNE_MAX_COUNT avec des valeurs adaptées à votre serveur, puis vérifiez à nouveau le lendemain plutôt qu’immédiatement.

Pourquoi mon workflow planifié ne s’est-il pas exécuté alors que n8n redémarrait ?

n8n enregistre les triggers au démarrage du processus et ne rejoue pas les planifications arrivées à échéance pendant son arrêt. Une boucle de redémarrage produit donc un silence, et non une série d’exécutions de rattrapage. L’exécution suivante a lieu à la prochaine échéance après le démarrage. Si certaines exécutions ne doivent pas être manquées, déclenchez le workflow depuis un appelant externe qui appelle un webhook. La logique de retry reste ainsi à l’extérieur de n8n.

Un healthcheck redémarrera-t-il n8n s’il ne répond plus ?

Pas à lui seul. Un healthcheck Compose marque seulement le conteneur comme healthy ou unhealthy. Le redémarrage relève de la restart policy. C’est donc restart: unless-stopped qui ramène le conteneur après son arrêt, et également après le redémarrage de l’hôte si le service Docker est activé. Vérifiez-le avec sudo systemctl is-enabled docker. Pour agir spécifiquement lorsqu’un conteneur est unhealthy, vous avez besoin d’un watcher externe à Docker qui lit son état et redémarre le service.