SSD Nodes Learn Hosting plans →
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-08-27

n8n offline sul VPS: 4 cause e come distinguerle

Scopri se n8n mostra l’avviso websocket, entra in un loop di riavvio, viene terminato per out of memory o non esegue i workflow pianificati.

Perché n8n continua a risultare offline: quattro problemi, un solo sintomo

“n8n continua a risultare offline” è una frase che può indicare quattro problemi diversi, ciascuno dei quali richiede una correzione specifica. L’editor mostra un avviso di connessione persa 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 funziona correttamente e semplicemente un workflow attivo non viene mai eseguito. Modificando l’impostazione sbagliata, potresti passare un intero fine settimana a risolvere un problema che non esiste.

Devi quindi identificare il problema effettivo prima di modificare la configurazione. n8n viene eseguito come singolo processo Node.js, in genere all’interno di un unico container Docker, dietro un reverse proxy che termina TLS (transport layer security). Ogni livello può avere problemi diversi, ma il browser segnala tutti con lo stesso messaggio.

Diagnosi nell’ordine indicato

Esegui questi comandi sul VPS (virtual private server) e leggi i valori restituiti dalla tua macchina. Non confrontarli con numeri riportati in una discussione su un forum. Qui contano i valori relativi al tuo server, non a 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-stream

La 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 andato 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. Annotane il valore, attendi un minuto e leggilo di nuovo. Se il numero aumenta mentre lo osservi, è in corso un ciclo di riavvio. Le righe di log immediatamente precedenti a ogni riavvio riportano la causa.

OOMKilled è un flag booleano. True indica che il kernel Linux ha terminato il processo perché ha superato un limite di memoria, imposto dal container oppure dall'intera macchina. Questo singolo campo distingue un'interruzione per esaurimento della memoria da qualsiasi altro tipo di uscita. Per questo va letto prima di formulare ipotesi.

ExitCode indica il codice con cui il container è terminato l'ultima volta. Non devi memorizzare il significato di ogni codice. Leggi quello presente sul tuo sistema, quindi consulta la parte finale di docker logs relativa allo stesso timestamp. La coda del log e il flag di esaurimento della memoria, considerati insieme, indicano che cosa è successo; presi singolarmente, però, possono risultare fuorvianti.

docker stats mostra l'uso attuale 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» è solitamente causato dal reverse proxy

L’editor di n8n mantiene aperta una connessione push di lunga durata verso il backend, così può trasmettere l’avanzamento delle esecuzioni sull’area di lavoro. 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 include gli header Connection: Upgrade e Upgrade: websocket. Il server risponde con 101 Switching Protocols; da quel momento, entrambe le parti usano lo stesso socket TCP in entrambe le direzioni.

Due problemi possono interrompere questa connessione, ed entrambi si verificano nel proxy, non in n8n. Il proxy usa HTTP/1.0 verso il backend oppure rimuove gli header di upgrade. In questo modo l’upgrade non viene completato 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 è operativo. Il banner indica che il browser ha perso il relativo canale.

Verifica questa situazione nel browser prima di modificare la configurazione. Apri gli strumenti per sviluppatori, vai alla scheda Network, filtra per WS e ricarica l’editor. La richiesta push dovrebbe raggiungere 101 Switching Protocols e rimanere aperta. Una richiesta push che restituisce un normale codice di stato, oppure che ricompare ogni pochi secondi, indica un problema nel proxy.

Le impostazioni di nginx che mantengono connesso l’editor

nginx non inoltra un upgrade se non viene configurato esplicitamente. proxy_pass usa HTTP/1.0 per comunicare con il backend per impostazione predefinita, mentre Connection e Upgrade sono header hop-by-hop che nginx rimuove durante l’inoltro. È necessario reinserirli entrambi. Il blocco map va inserito nel contesto http, non all’interno di server. Se il resto del server block riportato sotto non è familiare, la spiegazione riga per riga di un server block nginx descrive il funzionamento di ogni direttiva.

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 riga che viene omessa più spesso. Il valore predefinito è 60 secondi e si applica anche a un WebSocket aggiornato. Di conseguenza, una scheda dell’editor lasciata aperta su un’istanza inattiva perde la connessione circa un minuto dopo l’invio dell’ultimo messaggio. 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 attiva invece di un singolo file, dimostrando che la modifica è stata effettivamente caricata. Se nessuna riga include include un file di configurazione, una correzione valida può sembrare inefficace perché nginx non la carica.

Poi indica 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/

N8N_PROXY_HOPS ha il valore predefinito 0. Questo indica a n8n di considerare l’indirizzo che effettua la connessione come indirizzo del client e di ignorare X-Forwarded-For. Impostalo sul numero di proxy presenti davanti al container. Ad agosto 2026, N8N_WEBHOOK_URL è il nome corrente e il precedente WEBHOOK_URL continua a funzionare, ma all’avvio viene visualizzato un avviso di deprecazione.

Traefik inoltra i WebSocket, quindi poi li chiude per timeout

Traefik inoltra un aggiornamento WebSocket senza middleware e senza label aggiuntive. Per questo, quando un utente di Traefik vede questo banner, di solito sta riscontrando un timeout e non un header mancante. I parametri da configurare si trovano nell’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: 3600s

Caddy gestisce automaticamente l’aggiornamento in reverse_proxy e non richiede alcuna direttiva. Se non puoi modificare il proxy perché è gestito da un’altra persona, cambia il canale push usando N8N_PUSH_BACKEND=sse. SSE (server-sent events) è una normale risposta HTTP mantenuta aperta. Per questo continua a funzionare con un proxy che rifiuta gli aggiornamenti, anche se un timeout aggressivo sull’inattività interrompe comunque la connessione. La scelta del proxy è una decisione distinta. Il confronto tra nginx, Caddy e Traefik descrive i costi operativi di ciascuna soluzione.

Quando il container continua effettivamente a riavviarsi

Se RestartCount aumenta, il container sta terminando in errore e Docker lo riavvia. Confronta i timestamp del log con ogni riavvio e leggi ciò che è avvenuto subito prima. Quattro cause spiegano quasi tutti i casi: un errore di configurazione che impedisce l'avvio, un database che n8n non riesce a raggiungere, un crash dopo l'avvio e una terminazione forzata per esaurimento della memoria.

Inizia dal volume, perché i permessi sono la causa meno evidente. L'immagine ufficiale viene eseguita con l'utente senza privilegi 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/.n8n

Un volume denominato evita completamente il problema, perché Docker lo crea con il proprietario corretto. Se devi usare un bind mount, chown la directory sull'host all'ID numerico dell'utente mostrato dal primo comando. Vale la pena comprendere una volta il mapping dei proprietari tra host e container. La guida esplicativa su PUID e PGID descrive come queste immagini determinano l'utente che scrive i file.

L’uccisione per esaurimento della memoria che sembra un crash

Al di sopra di un processo n8n esistono due limiti di memoria distinti, che producono errori diversi. Il limite del control group del container è applicato dal kernel: quando viene superato, il processo viene terminato immediatamente, senza possibilità di scrivere alcun dato, e OOMKilled restituisce true. Il limite dell’heap di V8 è applicato all’interno di Node.js: quando viene superato, Node genera un errore dell’heap con uno stack trace e termina autonomamente, quindi OOMKilled restituisce false. Dal browser, i due casi sono identici. In docker inspect differiscono per un solo campo.

Impostare 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 quindi 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>

Scegliere entrambi i valori in base alle risorse effettivamente disponibili sul VPS, lasciando margine per il database, il proxy e il sistema operativo. docker stats --no-stream stampa l’uso corrente insieme al limite applicato, quindi consente di verificare che il limite configurato sia quello applicato da Docker. Come vengono applicati i limiti di memoria di Compose spiega quale chiave prevale quando ne sono impostate diverse.

I dati di esecuzione crescono sotto il carico

Una singola esecuzione contiene l'output di ogni nodo mentre il workflow è in corso, quindi n8n archivia questi dati. Ne derivano due conseguenze. Il picco di memoria di un'esecuzione dipende dal batch di dati più grande che vi fai transitare. Un workflow che gestisce 10000 righe alla volta è quindi un programma diverso dallo stesso workflow che ne gestisce 200 per volta. Inoltre, la copia archiviata continua a crescere finché qualcosa non la elimina.

Il pruning gestisce il secondo problema. Ad agosto 2026, i valori predefiniti prevedono il pruning abilitato, EXECUTIONS_DATA_MAX_AGE impostato su 336 ore (14 giorni) e EXECUTIONS_DATA_PRUNE_MAX_COUNT 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 serve 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=false

EXECUTIONS_DATA_SAVE_ON_SUCCESS=none è l'impostazione più aggressiva. Conserva le esecuzioni non riuscite per il debugging ed elimina quelle riuscite. Decidi consapevolmente se usarla, perché un workflow che produce un output errato senza generare errori non lascia poi nulla da esaminare. Il pruning contrassegna inoltre prima le righe come eliminate e le rimuove in un passaggio successivo. SQLite riutilizza le pagine liberate invece di restituirle, quindi il file su disco non si riduce appena cambi l'impostazione.

Per ridurre il picco invece della quantità totale archiviata, sposta meno dati in ogni esecuzione. Dividi i job di grandi dimensioni in sub-workflow che restituiscono risultati ridotti al workflow principale, usa il nodo Loop Over Items per creare batch ed evita di caricare interi dataset nel nodo Code.

I file binari non devono transitare dalla memoria

N8N_DEFAULT_BINARY_DATA_MODE è impostato per impostazione predefinita su default, che mantiene i dati binari nella memoria dell'esecuzione in corso. Ogni file scaricato da un nodo e ogni copia passata al nodo successivo rimangono lì fino al termine dell'esecuzione. Un workflow che recupera 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 intervallo di tempo preciso.

environment:
  - N8N_DEFAULT_BINARY_DATA_MODE=filesystem

Con filesystem, i dati binari vengono scritti in N8N_BINARY_DATA_STORAGE_PATH, che per impostazione predefinita si trova nella cartella utente di n8n e quindi sullo stesso volume del resto dei dati. Prima di modificare questa impostazione, verifica che il volume disponga di spazio sufficiente. N8N_PAYLOAD_SIZE_MAX imposta la dimensione massima del payload del webhook in ingresso in MiB (mebibyte) e il valore predefinito è 16. Aumentandola puoi accettare richieste più grandi, ma scegli di conseguenza un maggiore consumo di memoria.

Tutto ciò che condivide il server compete per la stessa RAM. Se i processi OOM sono iniziati quando hai aggiunto un container del database, eseguire il database in Docker o sull'host è il compromesso che stai facendo.

Policy di riavvio e ripristino dopo un reboot

Un container senza una policy 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 health check, indicato da N8N_ENDPOINT_HEALTH, che per impostazione predefinita è 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 docker

Un health check da solo non riavvia nulla. Compose contrassegna il container come non integro e si ferma, quindi l'health check deve essere associato a una policy di riavvio o a un watcher esterno per avere effetto. Scrivere un health check che agisca davvero e far ripartire lo stack dopo un reboot trattano entrambi gli aspetti.

Il workflow che non si attiva mentre n8n funziona

Questo caso non produce alcun banner e non riavvia nulla. 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 una di queste quattro categorie.

  • Il workflow non è attivo. Un Schedule Trigger viene eseguito soltanto nel percorso di produzione, quindi provarlo nell’area di lavoro non pianifica nulla.
  • Il fuso orario non è quello corretto. GENERIC_TIMEZONE usa per impostazione predefinita America/New_York, quindi una pianificazione impostata per le 09:00 viene eseguita alle 09:00 in quel fuso finché non imposti GENERIC_TIMEZONE e TZ sul tuo fuso orario.
  • Il periodo di inattività non viene recuperato in seguito. I trigger vengono registrati quando n8n si avvia, quindi una pianificazione la cui scadenza cade mentre il container è in riavvio non viene eseguita in ritardo. La prossima esecuzione avviene al successivo orario previsto dopo l’avvio.
  • Il workflow è stato disattivato automaticamente. N8N_WORKFLOW_AUTODEACTIVATION_ENABLED è disabilitato per impostazione predefinita; 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. Se l’esecuzione è terminata con un errore 429 verso un altro servizio ospitato sullo stesso server, il limite appartiene a quel servizio e non a n8n; la procedura per analizzare gli errori 429 di SearXNG spiega come distinguere il suo rate limiter dai motori che bloccano l’indirizzo IP del server. L’assenza totale di voci indica invece un problema del trigger; le quattro cause precedenti sono il primo punto da controllare.

Cosa modificare per prima cosa

  1. Leggi STATUS, RestartCount e OOMKilled sul container interessato prima di modificare qualsiasi file.
  2. Se il container non si è mai arrestato, correggi gli header per l’upgrade del proxy e il timeout di inattività.
  3. 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 trasferisci i dati binari a filesystem.
  4. 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 volta sola e poi lasciata invariata, su un’installazione già funzionante. Se stai ancora completando l’installazione, la guida 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 quando 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 il backend, 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, in modo che una scheda inattiva non venga disconnessa. Controllare la configurazione effettivamente in uso con sudo nginx -T, non il file modificato.

Come posso distinguere un'operazione di terminazione per memoria insufficiente da un normale arresto anomalo?

Eseguire docker inspect n8n | grep -iE 'OOMKilled|ExitCode|RestartCount' e leggere 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 comparirà nulla di utile, perché il processo non ha avuto la possibilità di scrivere. 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 limite del proprio heap V8 e si è arrestato autonomamente. Impostare NODE_OPTIONS=--max-old-space-size al di sotto del limite del container, così si verifica 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 obsolete per l'eliminazione e un passaggio successivo le rimuove, secondo la pianificazione definita da EXECUTIONS_DATA_PRUNE_HARD_DELETE_INTERVAL. Con SQLite, inoltre, il file riutilizza le pagine liberate invece di restituirle al filesystem. Di conseguenza, la dimensione su disco resta invariata per un certo periodo dopo l'eliminazione delle righe. Impostare EXECUTIONS_DATA_MAX_AGE e EXECUTIONS_DATA_PRUNE_MAX_COUNT su valori adatti al sistema, quindi verificare nuovamente il giorno successivo, non subito.

Perché il workflow pianificato non è stato eseguito mentre n8n si riavviava?

n8n registra i trigger all'avvio del processo e non recupera le esecuzioni pianificate la cui scadenza è avvenuta mentre il servizio era fermo. Un ciclo di riavvii produce quindi silenzio, non una serie di esecuzioni arretrate. L'esecuzione successiva avviene al primo orario previsto dopo l'avvio. Se alcune esecuzioni non possono essere perse, avviare il workflow da un chiamante esterno che richiama un webhook, in modo che la logica dei tentativi risieda al di fuori di n8n.

Un healthcheck riavvia n8n quando smette di rispondere?

Non autonomamente. Un healthcheck di Compose indica soltanto se il container è in stato healthy o unhealthy. Il riavvio dipende dalla restart policy: restart: unless-stopped riporta in esecuzione il container dopo la sua terminazione e lo riavvia anche dopo il riavvio dell'host, purché il servizio Docker sia abilitato. Verificarlo con sudo systemctl is-enabled docker. Per intervenire specificamente sullo stato unhealthy serve un watcher esterno a Docker che legga lo stato e riavvii il servizio.