Perché n8n va offline sul tuo VPS
Scopri se il banner websocket, un riavvio continuo, un kill per memoria o una pianificazione bloccata fanno sembrare n8n offline e come distinguerli.
Perché n8n continua a risultare offline: quattro guasti, un solo sintomo
“n8n continua a risultare offline” è una frase che può indicare quattro guasti diversi, ognuno dei quali richiede una correzione specifica. L’editor mostra un banner che segnala la perdita della connessione mentre il container continua a funzionare normalmente. Il container si riavvia autonomamente. Il kernel termina il processo Node.js perché utilizza troppa memoria. Oppure il processo non presenta alcun problema e un workflow attivo semplicemente non viene mai eseguito. Se modifichi l’impostazione sbagliata, potresti passare un intero fine settimana a risolvere un problema che non esiste.
Devi quindi identificare il guasto prima di modificare la configurazione. n8n viene eseguito come un singolo processo Node.js, in genere all’interno di un unico container Docker, dietro un reverse proxy che termina TLS (transport layer security). Ognuno di questi livelli può guastarsi in modo diverso, ma il browser segnala tutti i casi con lo stesso messaggio.
Esegui la diagnosi in questo ordine
Esegui questi comandi sul VPS (virtual private server) e leggi i valori stampati dal tuo sistema. Non confrontarli con numeri riportati in una discussione del forum. I valori importanti descrivono il tuo server, non quello di qualcun altro.
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-streamLa colonna STATUS di docker ps -a indica da quanto tempo il container si trova nello stato attuale. Confronta questo valore con il momento in cui è iniziato il problema. Se il container è attivo da molto prima della comparsa del banner, n8n non è mai stato offline. Il problema riguarda la connessione tra il browser e il backend, cioè il percorso websocket descritto nella sezione successiva.
RestartCount indica quante volte Docker ha riavviato questo container. Annota il numero, attendi un minuto e leggilo di nuovo. Se il numero aumenta mentre lo controlli, è in corso un ciclo di riavvio. Le righe del log immediatamente precedenti a ogni riavvio indicano la causa.
OOMKilled è un flag con valore true o false. Il valore true indica che il kernel Linux ha terminato il processo perché ha superato un limite di memoria, quello del container o quello dell'intero sistema. Questo campo distingue un'uscita causata dalla memoria da qualsiasi altro tipo di terminazione. Per questo va letto prima di formulare ipotesi.
ExitCode contiene il codice con cui il container è terminato l'ultima volta. Non è necessario memorizzare il significato di ogni codice. Leggi il valore del tuo sistema, quindi leggi la parte finale di docker logs relativa allo stesso momento. Il contenuto finale del log e il flag out of memory, considerati insieme, indicano cosa è successo; presi singolarmente, possono fuorviare.
docker stats mostra l'uso corrente della memoria accanto al limite applicato. Lascialo in esecuzione in un secondo terminale, avvia il workflow che causa il problema e osserva come cambia il valore mentre si verifica l'errore.
Il banner “connessione persa” è generalmente causato dal reverse proxy
L’editor di n8n mantiene aperta una connessione push di lunga durata verso il backend, per trasmettere al canvas l’avanzamento delle esecuzioni. Per impostazione predefinita, questa connessione è un WebSocket, selezionato da N8N_PUSH_BACKEND, e il relativo valore predefinito è websocket. Un WebSocket inizia come una normale richiesta HTTP che contiene gli header Connection: Upgrade e Upgrade: websocket. Il server risponde con 101 Switching Protocols e, da quel momento, entrambi i lati usano lo stesso socket TCP in entrambe le direzioni.
Due problemi possono interrompere questo comportamento e si verificano entrambi nel proxy, non in n8n. Il proxy usa HTTP/1.0 verso l’upstream oppure rimuove gli header di upgrade. Di conseguenza, l’upgrade non avviene e l’editor tenta di riconnettersi continuamente. In alternativa, l’upgrade riesce, ma in seguito il proxy chiude il socket perché è rimasto inattivo: un WebSocket senza messaggi appare esattamente come una connessione inattiva. In entrambi i casi, il container è integro. Il banner indica che il browser ha perso il proprio canale di comunicazione.
Prima di modificare la configurazione, verifica il problema nel browser. Apri gli strumenti per sviluppatori, passa alla scheda Network, applica il filtro WS e ricarica l’editor. La richiesta push dovrebbe raggiungere 101 Switching Protocols e rimanere aperta. Se la richiesta push restituisce un normale codice di stato oppure ricompare ogni pochi secondi, il problema è probabilmente nel proxy.
Le impostazioni di nginx che mantengono connesso l’editor
nginx non inoltra un upgrade se non viene configurato esplicitamente. proxy_pass comunica con il backend tramite HTTP/1.0 per impostazione predefinita, mentre Connection e Upgrade sono header hop-by-hop che nginx rimuove durante l’inoltro. È necessario reinserire entrambi. Il blocco map va inserito nel contesto http, non all’interno di 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 è la direttiva che viene più spesso omessa. Il valore predefinito è 60 secondi e si applica anche a una connessione WebSocket sottoposta a upgrade. Di conseguenza, una scheda dell’editor lasciata aperta su un’istanza inattiva perde la connessione circa un minuto dopo l’ultimo messaggio transitato. Aumentare questo valore risolve il banner visualizzato quando si torna a una scheda lasciata aperta.
sudo nginx -t && sudo systemctl reload nginx
sudo nginx -T | grep -iE 'proxy_http_version|upgrade|proxy_read_timeout'nginx -T stampa l’intera configurazione in esecuzione, invece di un solo file, e quindi dimostra che la modifica è stata effettivamente caricata. Una configurazione contenuta in un file che nessuna direttiva include include è il motivo per cui una correzione corretta sembra non avere effetto.
Indicate quindi a n8n che si trova dietro un proxy, perché costruisce gli URL a partire da questi valori.
environment:
- N8N_HOST=n8n.example.com
- N8N_PROTOCOL=https
- N8N_PORT=5678
- N8N_PROXY_HOPS=1
- N8N_WEBHOOK_URL=https://n8n.example.com/Per impostazione predefinita, N8N_PROXY_HOPS vale 0. Questo indica a n8n di considerare l’indirizzo della connessione come indirizzo del client e di ignorare X-Forwarded-For. Impostatelo sul numero di proxy presenti davanti al container. Ad agosto 2026, N8N_WEBHOOK_URL è il nome corrente; il precedente WEBHOOK_URL funziona ancora, ma stampa un avviso di deprecazione all’avvio.
Traefik inoltra i WebSocket, poi li interrompe per timeout
Traefik inoltra un upgrade WebSocket senza middleware e senza label aggiuntive. Di conseguenza, un utente di Traefik che visualizza questo banner sta generalmente riscontrando un timeout, non l'assenza di un header. I parametri si trovano sull'entryPoint. Ad agosto 2026, in Traefik v3, idleTimeout ha un valore predefinito di 180 secondi e readTimeout di 60 secondi.
entryPoints:
websecure:
address: ":443"
transport:
respondingTimeouts:
readTimeout: 0
idleTimeout: 3600sCaddy gestisce automaticamente l'upgrade in reverse_proxy e non richiede alcuna direttiva. Se non puoi modificare il proxy perché è gestito da terzi, cambia il canale push usando N8N_PUSH_BACKEND=sse. Gli SSE (server-sent events) sono normali risposte HTTP mantenute aperte. Per questo funzionano anche con un proxy che rifiuta gli upgrade, anche se un timeout di inattività aggressivo interrompe comunque la connessione. La scelta del proxy è una decisione separata. il confronto tra nginx, Caddy e Traefik descrive i costi operativi di ciascuna soluzione.
Quando il container continua davvero a riavviarsi
Se RestartCount aumenta, il container sta fallendo e Docker lo riavvia. Confronta i timestamp dei log con ciascun riavvio e leggi ciò che è avvenuto immediatamente prima. Quattro cause coprono quasi tutti i casi: un errore di configurazione che impedisce l’avvio, un database che n8n non riesce a raggiungere, un arresto anomalo dopo l’avvio e un processo terminato per mancanza di memoria.
Inizia dal volume, perché i permessi sono la causa meno evidente. L’immagine ufficiale viene eseguita con l’utente non privilegiato node e conserva i dati in /home/node/.n8n. Un bind mount creato da root non è scrivibile da quell’utente. Di conseguenza, il processo termina all’avvio ogni volta e la restart policy nasconde il problema dietro un ciclo continuo.
docker compose config
docker run --rm -it --entrypoint sh docker.n8n.io/n8nio/n8n -c 'id'
docker exec n8n ls -ld /home/node/.n8nUn volume denominato evita completamente il problema, perché Docker lo crea con la proprietà corretta. Se devi usare un bind mount, chown la directory dell’host all’ID utente numerico mostrato dal primo comando. È utile capire una volta per tutte la mappatura della proprietà tra host e container. La spiegazione di PUID e PGID descrive come queste immagini stabiliscono quale utente può scrivere i file.
L’uccisione per memoria esaurita che sembra un crash
Esistono due limiti di memoria distinti al di sopra di un processo n8n, e vengono applicati in modo diverso. Il limite del control group del container è applicato dal kernel: se viene superato, il processo viene terminato immediatamente, senza poter scrivere alcun dato, e OOMKilled restituisce true. Il limite dell’heap di V8 è applicato all’interno di Node.js: se viene superato, Node genera un errore dell’heap con uno stack trace e termina autonomamente, quindi OOMKilled restituisce false. Dal browser, i due casi sembrano identici. In docker inspect differiscono per un solo campo.
Imposta il limite dell’heap di Node al di sotto del limite del container. Se il limite dell’heap è il più alto dei due, V8 continua ad allocare memoria oltre il punto in cui interviene il kernel. Il garbage collector non raggiunge mai il proprio limite e si verifica sempre l’errore più grave, senza alcun log da consultare.
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>Scegli entrambi i valori in base alle risorse effettivamente disponibili sul VPS, lasciando memoria per il database, il proxy e il sistema operativo. docker stats --no-stream mostra l’uso corrente accanto al limite applicato, quindi puoi verificare che il limite impostato sia quello applicato da Docker. Come vengono applicati i limiti di memoria di Compose spiega quale chiave prevale quando ne vengono impostate diverse.
I dati delle esecuzioni crescono senza che ve ne accorgiate
Una singola esecuzione contiene l’output di ogni nodo mentre il flusso è in corso, quindi n8n memorizza questi dati. Ne derivano due conseguenze. Il picco di memoria di un’esecuzione dipende dal batch di dati più grande che vi fate passare. Un workflow che gestisce 10000 righe alla volta è quindi un programma diverso dallo stesso workflow che ne gestisce 200 alla volta. Inoltre, la copia memorizzata continua a crescere finché non viene eliminata.
Il pruning risolve il secondo problema. Ad agosto 2026 i valori predefiniti prevedono il pruning attivo, EXECUTIONS_DATA_MAX_AGE impostato su 336 ore (14 giorni) e EXECUTIONS_DATA_PRUNE_MAX_COUNT impostato su 10000. Sono valori elevati per un piccolo VPS che usa SQLite, dove un unico file contiene tutti i dati e lo stesso processo che pubblica l’editor deve leggerli e scriverli.
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=falseEXECUTIONS_DATA_SAVE_ON_SUCCESS=none è l’impostazione più aggressiva. Conserva le esecuzioni non riuscite per il debugging ed elimina quelle riuscite. Applicatela solo dopo una valutazione consapevole: se un workflow produce un output errato senza generare errori, non resterà nulla da esaminare. Inoltre, il pruning contrassegna prima le righe come eliminate e le rimuove in un passaggio successivo. SQLite riutilizza le pagine liberate invece di restituirle al sistema, quindi il file su disco non si riduce immediatamente quando modificate l’impostazione.
Per ridurre il picco invece della quantità totale memorizzata, spostate meno dati in ogni esecuzione. Dividete i processi più grandi in sub-workflow che restituiscono risultati ridotti al workflow principale, usate il nodo Loop Over Items per elaborare i dati in batch ed evitate di passare interi dataset al nodo Code.
I file binari non devono passare dalla memoria
N8N_DEFAULT_BINARY_DATA_MODE è impostato per impostazione predefinita su default e mantiene i dati binari nella memoria dell'esecuzione in corso. Ogni file scaricato da un nodo e ogni copia passata al nodo successivo rimangono in memoria fino al termine dell'esecuzione. Un singolo workflow che scarica alcuni allegati di grandi dimensioni può portare il processo oltre un limite che le normali operazioni JSON non raggiungono mai. Per questo il crash si verifica con uno specifico workflow e non dopo un determinato intervallo di tempo.
environment:
- N8N_DEFAULT_BINARY_DATA_MODE=filesystemCon filesystem, i dati binari vengono scritti in N8N_BINARY_DATA_STORAGE_PATH, che per impostazione predefinita si trova nella directory dell'utente n8n e quindi sullo stesso volume di tutto il resto. Prima di attivare questa opzione, verifica che il volume disponga di spazio sufficiente. N8N_PAYLOAD_SIZE_MAX imposta la dimensione massima del payload dei webhook in ingresso in MiB (mebibyte) e il valore predefinito è 16. Aumentarlo consente di accettare richieste più grandi, ma comporta un costo in memoria che stai scegliendo di sostenere.
Anche gli altri servizi presenti sul server competono per la stessa RAM. Se gli arresti causati da OOM sono iniziati dopo l'aggiunta di un container database, eseguire il database in Docker o sul server host è il compromesso che stai facendo.
Politica di riavvio e ripristino dopo un reboot
Un container senza una politica di riavvio resta arrestato dopo l'uscita e dopo il riavvio dell'host. restart: unless-stopped lo riavvia in entrambi i casi, rispettando comunque un container arrestato manualmente. restart: always riavvia anche un container arrestato deliberatamente, al successivo avvio di Docker.
n8n espone un endpoint di healthcheck, indicato da N8N_ENDPOINT_HEALTH, il cui valore predefinito è healthz. Verificalo prima dall'host, per assicurarti che il percorso sia corretto nella tua istanza.
curl -fsS http://127.0.0.1:5678/healthz
docker exec n8n which wget curl
sudo systemctl is-enabled dockerUn healthcheck, da solo, non riavvia nulla. Compose contrassegna il container come non integro e si ferma, quindi l'healthcheck deve essere associato a una politica di riavvio o a un watcher esterno per produrre effetti. Scrivere un healthcheck che intervenga davvero e fare riavviare lo stack dopo un reboot descrivono entrambi gli aspetti.
Il workflow che non viene mai eseguito mentre n8n funziona
Questo caso non produce alcun banner né riavvio. Il container è attivo, l’editor funziona e l’esecuzione prevista non compare nell’elenco delle esecuzioni. Nella maggior parte dei casi la causa rientra in uno dei quattro casi seguenti.
- Il workflow non è attivo. Un Schedule Trigger viene eseguito soltanto nel percorso di produzione, quindi testarlo nel canvas non pianifica nulla.
- Il fuso orario non è quello corretto.
GENERIC_TIMEZONEha come valore predefinitoAmerica/New_York, quindi una pianificazione impostata per le 09:00 viene eseguita alle 09:00 in quel fuso finché non impostiGENERIC_TIMEZONEeTZcon il tuo fuso orario. - Il periodo di inattività non viene recuperato in seguito. I trigger vengono registrati quando n8n si avvia, quindi una pianificazione prevista mentre il container era in riavvio non viene eseguita in ritardo. La prossima esecuzione è quella prevista dopo l’avvio.
- Il workflow è stato disattivato automaticamente.
N8N_WORKFLOW_AUTODEACTIVATION_ENABLEDè disabilitato per impostazione predefinita e, quando è abilitato, un workflow che continua a terminare con errori viene rimosso dalla pubblicazione. In seguito appare esattamente come un workflow che non è mai stato attivato.
Apri l’elenco delle esecuzioni e filtra il workflow interessato. Una voce con esito negativo indica un problema del workflow. L’assenza totale di voci indica un problema del trigger: verifica innanzitutto i quattro casi descritti sopra.
Cosa modificare per primo
- Leggi
STATUS,RestartCounteOOMKillednel tuo container prima di modificare qualsiasi file. - Se il container non si è mai arrestato, correggi gli header di upgrade del proxy e il timeout di inattività.
- Se
OOMKilledè vero, imposta deliberatamente un limite per il container, configura il limite massimo dell'heap di Node al di sotto di tale valore e sposta i dati binari infilesystem. - Se non si è attivato nulla, verifica che il workflow sia attivo e che il fuso orario dell'istanza sia quello corretto.
La maggior parte di queste impostazioni viene configurata una sola volta e poi lasciata invariata, dopo aver completato un'installazione funzionante. Se stai ancora completando l'installazione, la guida per eseguire n8n su Docker con HTTPS è la base a cui si applicano queste impostazioni.
FAQ
Perché l'editor di n8n mostra un avviso di connessione persa mentre il container è in esecuzione?
L'editor mantiene aperta una connessione WebSocket per trasmettere l'avanzamento delle esecuzioni. Se il reverse proxy non inoltra gli header Connection: Upgrade e Upgrade: websocket oppure non usa HTTP/1.1 verso l'upstream, l'upgrade non viene completato e il browser tenta di riconnettersi continuamente, mentre n8n resta operativo. In nginx servono proxy_http_version 1.1 e entrambe le righe proxy_set_header, oltre a un proxy_read_timeout superiore ai 60 secondi predefiniti, per evitare che una scheda inattiva venga disconnessa. Controlla la configurazione effettivamente in esecuzione con sudo nginx -T, non il file che hai modificato.
Come posso distinguere una terminazione per esaurimento della memoria da un arresto anomalo ordinario?
Esegui docker inspect n8n | grep -iE 'OOMKilled|ExitCode|RestartCount' e leggi il flag OOMKilled. Il valore True indica che il kernel ha terminato il processo perché aveva superato un limite di memoria; nel log del container non troverai informazioni utili perché il processo non ha avuto modo di scriverle. Il valore False, insieme a un errore dell'heap e a uno stack trace alla fine di docker logs, indica invece che Node.js ha raggiunto il proprio limite dell'heap V8 e si è chiuso autonomamente. Imposta NODE_OPTIONS=--max-old-space-size su un valore inferiore al limite del container, così otterrai il secondo tipo di errore, che lascia tracce utili.
L'eliminazione dei dati delle esecuzioni libera subito spazio su disco?
No. EXECUTIONS_DATA_PRUNE contrassegna le esecuzioni meno recenti per l'eliminazione, mentre una passata successiva le rimuove secondo la pianificazione impostata da EXECUTIONS_DATA_PRUNE_HARD_DELETE_INTERVAL. Con SQLite, inoltre, il file riutilizza le pagine liberate invece di restituirle al filesystem; per questo la dimensione su disco resta invariata per un certo periodo dopo l'eliminazione delle righe. Imposta EXECUTIONS_DATA_MAX_AGE e EXECUTIONS_DATA_PRUNE_MAX_COUNT su valori adatti al tuo server, quindi verifica nuovamente il giorno successivo, non subito.
Perché il workflow pianificato non è stato eseguito mentre n8n veniva riavviato?
n8n registra i trigger all'avvio del processo e non recupera le pianificazioni diventate dovute mentre era arrestato. Un ciclo di riavvii produce quindi silenzio, non una serie di esecuzioni arretrate; la successiva esecuzione avverrà al primo orario previsto dopo l'avvio. Se hai bisogno di esecuzioni che non possano essere perse, attiva il workflow tramite un chiamante esterno che invia una richiesta a un webhook, in modo che la logica dei tentativi si trovi al di fuori di n8n.
Un healthcheck riavvia n8n quando smette di rispondere?
Non autonomamente. Un healthcheck di Compose indica soltanto se il container è healthy o unhealthy. Il riavvio dipende dalla restart policy: restart: unless-stopped riporta in esecuzione il container dopo la sua chiusura e lo riavvia anche dopo il reboot dell'host, purché il servizio Docker sia abilitato. Verificalo con sudo systemctl is-enabled docker. Per intervenire specificamente quando lo stato è unhealthy, serve un watcher esterno a Docker che legga lo stato e riavvii il servizio.