installare n8n su VPS con Docker e HTTPS
Guida all'installazione di n8n con Docker Compose e Postgres. Risolvi gli errori di WEBHOOK_URL e la configurazione di HTTPS con reverse proxy su VPS.
Cosa stai costruendo
n8n è uno strumento di automazione dei workflow: un editor visuale in cui un trigger — un webhook, un programma pianificato, l'invio di un modulo — attiva una catena di nodi che chiamano API, rielaborano dati e scrivono su altri sistemi. È diventato lo standard per i workflow degli agenti AI perché comunica con ogni provider di modelli e database senza dover scrivere un servizio. Con un docker run si ottiene un editor funzionante in due minuti. Questa guida riguarda il restante novanta percento: rendere il sistema duraturo utilizzando Postgres invece del file SQLite predefinito, renderlo accessibile tramite HTTPS e — la parte che quasi tutti sbagliano — configurare i webhook affinché forniscano un URL effettivamente raggiungibile dall'esterno.
Lo stack finale è composto da due container su una rete Docker: n8n stesso e un database Postgres che contiene i workflow e le credenziali. Un reverse proxy sull'host termina la connessione TLS e inoltra le richieste a n8n su localhost; in questo modo, nulla è esposto su internet se non tramite il proxy. Si inserisce tra gli altri servizi della lista selezionata per l'auto-hosting 2026.
Prerequisiti e limiti reali
È necessario un VPS con almeno 1 GB di RAM; prevedi 2 GB quando i workflow saranno intensivi. L'esecuzione dei processi e il runtime di Node.js consumano molta memoria; l'OOM killer che interrompe il container durante l'esecuzione è un modo inefficiente per imparare il problema. Un singolo vCPU è sufficiente per iniziare.
È necessario un dominio o un sottodominio — ad esempio n8n.example.com — con un record A puntato all'IP pubblico del VPS che sia già risolto prima di richiedere il certificato. Le porte 80 e 443 devono essere aperte verso il proxy; la porta 5678 di n8n non deve essere esposta su internet. Sono necessari Docker Engine e il plugin Compose; se docker compose version restituisce l'errore docker: 'compose' is not a docker command, stai utilizzando il vecchio binario standalone e il plugin è sudo apt install docker-compose-plugin.
SQLite è adatto per i test, Postgres per qualsiasi uso critico
Il database predefinito di n8n è un file SQLite situato in /home/node/.n8n/database.sqlite. Per i test iniziali è sufficiente — se non viene montato un volume, i dati andranno persi alla prima ricreazione del container. Il motivo per passare a Postgres non è la velocità pura; il problema è che SQLite utilizza un lock di scrittura singolo. Di conseguenza, un'istanza che esegue più workflow contemporaneamente, o l'uso della queue mode, causa errori di SQLITE_BUSY: database is locked in caso di concorrenza. Postgres non presenta questo limite, supporta backup puliti con pg_dump ed è la soluzione prevista dalla documentazione ufficiale di n8n per i server di produzione. Passare a Postgres in un secondo momento richiede una migrazione manuale dei dati; se l'integrità dei dati è importante, inizia con Postgres.
DNS e firewall
Configura prima il record DNS e apri le porte. Questo evita che il passaggio relativo al certificato fallisca a causa di un nome non risolvibile.
dig +short n8n.example.com
curl -s ifconfig.me
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow OpenSSH
sudo ufw enableNon aprire la porta 5678. Il file compose associa n8n a 127.0.0.1:5678; pertanto, solo il reverse proxy dell'host può raggiungerlo. L'apertura di un ufw allow 5678 annullerebbe tale isolamento.
Il file Compose
Crea una directory di lavoro e un docker-compose.yml. Questo file definisce l'intero stack: due servizi, una rete privata e due volumi nominati.
services:
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: n8n
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: n8n
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- n8n_net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U n8n -d n8n"]
interval: 10s
timeout: 5s
retries: 5
n8n:
image: docker.n8n.io/n8nio/n8n:2.29.10
restart: unless-stopped
ports:
- "127.0.0.1:5678:5678"
environment:
- N8N_HOST=n8n.example.com
- N8N_PORT=5678
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://n8n.example.com/
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
- N8N_PROXY_HOPS=1
- GENERIC_TIMEZONE=Europe/London
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_PORT=5432
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- n8n_data:/home/node/.n8n
networks:
- n8n_net
depends_on:
postgres:
condition: service_healthy
volumes:
postgres_data:
n8n_data:
networks:
n8n_net:Alcune decisioni tecniche importanti. DB_POSTGRESDB_HOST=postgres è il service name, che Docker risolve sulla rete condivisa, non localhost, che all'interno del container n8n indica il processo n8n stesso. L'uso di depends_on con condition: service_healthy impedisce a n8n di avviarsi prima di Postgres; senza questo controllo, n8n non trova il database e termina l'esecuzione. Il volume nominato n8n_data in /home/node/.n8n contiene la chiave di cifratura e, nel caso di SQLite, il database: è l'unica directory da non perdere. Blocca l'immagine a una versione specifica e non usare mai latest; i motivi sono spiegati nella sezione relativa agli upgrade qui sotto.
Il file secrets
Non inserire mai le password nel file compose. Utilizza un file .env situato nella stessa cartella che Compose legge automaticamente. Genera le password in modo che siano realmente casuali.
printf 'POSTGRES_PASSWORD=%s\n' "$(openssl rand -hex 24)" > .env
printf 'N8N_ENCRYPTION_KEY=%s\n' "$(openssl rand -hex 32)" >> .env
chmod 600 .envIl N8N_ENCRYPTION_KEY è la stringa più importante in questo contesto: è la chiave con cui viene crittografata ogni credenziale memorizzata. Imposta il valore esplicitamente invece di lasciare che n8n ne generi uno automatico; un valore generato da te può essere annotato e ripristinato. Una volta che n8n ha crittografato la prima credenziale con questa chiave, cambiarla renderà tutte le credenziali illeggibili — quindi impostala una volta per tutte e non modificare mai più quella riga.
Le variabili d'ambiente che determinano il funzionamento dei webhook
Quattro variabili controllano il modo in cui n8n comunica i propri dati all'esterno. Errori in queste variabili causano la maggior parte delle richieste di supporto per n8n.
N8N_HOSTè l'hostname pubblico,n8n.example.com. Se impostato sul valore predefinitolocalhostdietro un proxy, l'editor cercherà di caricare la propria API dalocalhostnel browser dell'utente, causando un errore.N8N_PROTOCOL=httpsindica a n8n che il servizio è servito tramite TLS; di conseguenza, n8n imposta il cookie di sessioneSecuree genera URLhttps://.N8N_PORT=5678è la porta su cui n8n ascolta all'interno del container. Non è la porta pubblica; la porta 443 è gestita dal proxy.WEBHOOK_URL=https://n8n.example.com/è la variabile critica. n8n genera gli indirizzi dei webhook da copiare in Stripe, GitHub o altri servizi esterni utilizzando questi valori. Se non impostata o errata, n8n utilizzaN8N_HOST:N8N_PORTe restituiscehttps://n8n.example.com:5678/webhook/...o, peggio,http://localhost:5678/webhook/.... Questi indirizzi appaiono corretti ma sono irraggiungibili da internet, quindi le richieste esterne non arriveranno mai. Impostare l'URL base pubblico esatto con la barra finale (trailing slash) e verificare che il nodo webhook mostri un URL senza porta.
N8N_PROXY_HOPS=1 indica al server Express di n8n di fidarsi di un proxy. Questo permette al rate-limiting e alle funzioni che leggono l'IP del client di vedere l'indirizzo reale invece di quello del proxy. Una variabile che non deve essere impostata è N8N_RUNNERS_ENABLED: i task runner — n8n esegue la logica dei Code-node in un processo sandbox separato — sono il default dalla versione 1.69 e sono obbligatori dalla versione 2.x indicata in questa guida; la vecchia opzione è deprecata. Se impostata, n8n genererà solo un avviso che invita a rimuoverla.
Primo avvio
docker compose up -d
docker compose ps
docker compose logs -f n8nUn primo avvio corretto termina con una riga Editor is now accessible via:, preceduta da una riga n8n ready on ..., port 5678. docker compose ps deve mostrare entrambi i container Up, con postgres contrassegnato come (healthy). Se n8n si trova in un loop Restarting, consultare i log: la causa è quasi sempre la connessione al database o i permessi del volume descritti di seguito.
TLS con un reverse proxy
n8n utilizza il protocollo HTTP sulla porta 5678; un servizio esterno deve gestire la terminazione HTTPS. Esistono due opzioni principali.
Se utilizzi già diversi container, posiziona n8n dietro un reverse proxy Traefik che emette certificati TLS automaticamente configurando alcune labels: Traefik richiede e rinnova il certificato per te.
Se questa è l'unica applicazione sul server, un virtual host nginx con un certificato Let's Encrypt è la soluzione più semplice. Utilizza la configurazione TLS Certbot e nginx per Ubuntu 24.04 per ottenere il certificato, quindi utilizza questo server block:
server {
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;
location / {
proxy_pass http://127.0.0.1:5678;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600;
client_max_body_size 16m;
}
}Gli header Upgrade e Connection "upgrade" sono obbligatori. n8n invia aggiornamenti di esecuzione in tempo reale all'editor tramite WebSocket; senza queste due righe, la pagina di login si carica ma si blocca mostrando un banner di connessione persa. proxy_read_timeout 3600 impedisce che le esecuzioni lunghe vengano interrotte dal timeout predefinito di nginx di 60 secondi. L'header X-Forwarded-Proto $scheme è associato a N8N_PROXY_HOPS=1: comunica a n8n che la richiesta originale era in HTTPS anche se il proxy la inoltra in HTTP; questo evita che n8n consideri la connessione non sicura e rifiuti il proprio cookie.
Il tuo primo workflow, per renderlo operativo
Apri https://n8n.example.com/, crea l'account proprietario (sezione successiva) e costruisci il workflow minimo per verificare il funzionamento del percorso: un webhook in entrata, una chiamata HTTP e una risposta in uscita.
- Aggiungi un nodo Webhook. Imposta il metodo su
POSTe un path comehello. Verranno mostrate due URL, una Test URL e una Production URL — la causa della metà delle segnalazioni relative a "il mio webhook non funziona". La Test URL risponde a una singola chiamata e solo mentre è attivo il comando Listen for test event; successivamente scade. La Production URL risponde ogni volta che il workflow è Active. - Aggiungi un nodo HTTP Request subito dopo, puntato verso una qualsiasi API JSON pubblica — una richiesta GET verso
https://api.github.com/zenrestituisce una stringa di una riga, sufficiente per il test. - Aggiungi un nodo Respond to Webhook e imposta l'opzione Respond del nodo Webhook su "Using Respond to Webhook node", in modo che il chiamante riceva l'output del nodo HTTP.
- Attiva il workflow tramite l'interruttore Active (in alto a destra) e chiamalo tramite
curl -X POST https://n8n.example.com/webhook/hello. Dovresti ricevere la stringa di ritorno — POST in entrata, chiamata API, risposta in uscita: la struttura della maggior parte delle automazioni reali.
Una variante pianificata sostituisce il nodo Webhook con un Schedule Trigger e chiama un endpoint di un modello — un'istanza self-hosted tramite Ollama in esecuzione sullo stesso VPS è un metodo efficiente per creare un riassuntore notturno.
Gestione utenti, non basic auth
Le vecchie guide di n8n suggeriscono di impostare N8N_BASIC_AUTH_ACTIVE=true. Queste variabili sono state rimosse in n8n 1.0 e ora non hanno effetto. L'autenticazione attuale si basa sull'owner account: al primo caricamento dell'editor, n8n richiede la creazione di un account owner con email e password. Questo passaggio è obbligatorio: non esiste una modalità anonima. Crea l'account immediatamente dopo il primo avvio, prima di fornire l'URL a chiunque: tra docker compose up e l'invio del primo modulo, l'istanza può essere registrata da chiunque vi acceda per primo. Un livello di basic-auth tramite reverse-proxy è un ulteriore blocco ragionevole, ma si tratta di un secondo fattore, non dell'autenticazione principale.
Backups: prima la chiave di cifratura, poi il database
Sono necessari due tipi di backup, che non sono ugualmente sostituibili.
Il N8N_ENCRYPTION_KEY. Tutte le credenziali salvate in n8n — token API, password del database, segreti OAuth — sono cifrate a riposo con questa chiave. I workflow in Postgres sono inutilizzabili senza di essa: se si ripristina il database su un nuovo server con una chiave diversa, n8n non potrà decifrare alcuna credenziale; non è possibile il recupero né il reset. Il file .env contiene la chiave; copialo in un luogo esterno al server — un password-manager è la soluzione ideale — il giorno stesso in cui lo crei. Questo è il backup fondamentale.
Il database Postgres, per i workflow, la cronologia delle esecuzioni e le credenziali cifrate stesse:
docker compose exec -T postgres pg_dump -U n8n -d n8n \
| gzip > n8n-db-$(date +%F).sql.gzEsegui il comando tramite un job pianificato e copia il dump fuori dal server. Per il ripristino su un nuovo VPS: avvia lo stack una volta per creare il database, ferma n8n, carica il dump con psql, inserisci la stessa N8N_ENCRYPTION_KEY in .env e avvia n8n. La combinazione della stessa chiave e del dump garantisce un'istanza funzionante; una chiave nuova renderà i workflow incapaci di utilizzare qualsiasi credenziale.
Upgrades: bloccare il tag
Il file compose blocca n8nio/n8n:2.29.10 invece di latest intenzionalmente. n8n rilascia una nuova versione minor quasi ogni settimana e occasionalmente modifica lo schema del database o il comportamento dei node tra un rilascio e l'altro. Per questo motivo, latest implica che un pull automatico potrebbe scaricare una build che avvia la migrazione del database non appena il container viene eseguito. Blocca una versione specifica, leggi le release notes prima di aggiornare — n8n segnala le breaking changes lì — e aggiorna in modo deliberato:
docker compose exec -T postgres pg_dump -U n8n -d n8n | gzip > pre-upgrade.sql.gz
# edit the image tag in docker-compose.yml, then:
docker compose pull n8n
docker compose up -d n8n
docker compose logs -f n8nI salti di major-version sono i casi in cui questo è fondamentale. La linea 2.0, ad esempio, ha cambiato N8N_BLOCK_ENV_ACCESS_IN_NODE in true di default; qualsiasi Code node che leggeva process.env perde l'accesso silenziosamente finché non viene impostato nuovamente su false. Lo stesso rilascio ha iniziato ad applicare permessi restrittivi sul settings file. Leggi la pagina delle breaking-changes della versione 2.0 prima di passare a una nuova major-version. n8n esegue automaticamente tutte le migrazioni del database necessarie all'avvio; è esattamente per questo che il pg_dump pre-upgrade non è opzionale. Poiché le credenziali sono criptate con una chiave in .env e i dati risiedono in Postgres, i container sono disposable: l'aggiornamento avviene sostituendoli, mentre il rollback si effettua bloccando il tag precedente e ripristinando il dump.
Modalità di errore, con le stringhe che visualizzerai
The requested webhook "POST hello" is not registered. Un errore 404 derivante dalla chiamata a un webhook il cui workflow non è Active, o dalla chiamata al percorso di test quando nessun processo è in ascolto. I percorsi di test (/webhook-test/...) rispondono solo se è stato cliccato su "Listen for test event"; i percorsi di produzione (/webhook/...) rispondono solo quando l'interruttore del workflow è attivo. Il relativo This webhook is not registered for GET requests. Did you mean to make a POST request? indica che il metodo è errato: il nodo si aspetta POST e hai inviato GET.
L'URL del webhook mostra un :5678 o localhost. Il nodo visualizza https://n8n.example.com:5678/webhook/... o http://localhost:5678/.... WEBHOOK_URL è non impostato o errato, quindi n8n ha costruito l'indirizzo partendo da N8N_HOST:N8N_PORT invece della tua base pubblica. Imposta WEBHOOK_URL=https://n8n.example.com/, ricrea il container con docker compose up -d e la porta scomparirà.
There was a problem loading init data nel browser. L'editor è stato caricato ma non riesce a raggiungere la propria API backend. Dietro un proxy, questo è quasi sempre dovuto a un N8N_HOST o WEBHOOK_URL errato, a un proxy che non include gli header WebSocket Upgrade, o a un N8N_PROTOCOL che non corrisponde al metodo di connessione. Verifica le quattro variabili esposte pubblicamente e assicurati che il proxy inoltri Upgrade e Connection.
password authentication failed for user "n8n" nei log, con il container in riavvio continuo. La password inviata da n8n non corrisponde a quella utilizzata durante l'inizializzazione del database. L'errore comune: Postgres legge POSTGRES_PASSWORD solo durante l'inizializzazione di una directory dati vuota. Avvia lo stack una volta, quindi cambia POSTGRES_PASSWORD in .env; il volume postgres_data esistente conterrà ancora la vecchia password. Ripristina il valore originale o, se non hai dati da conservare, esegui docker compose down e docker volume rm sul volume postgres e avvia un'istanza nuova.
EACCES: permission denied, open '/home/node/.n8n/config' all'avvio. n8n viene eseguito come utente node (UID 1000) e non può scrivere nella directory di configurazione. Questo accade quando si utilizza un bind-mount di una cartella host (./n8n_data:/home/node/.n8n) di proprietà di root. Usa il volume nominato indicato sopra o, se preferisci il bind mount, esegui prima sudo chown -R 1000:1000 ./n8n_data.
Permissions 0644 for n8n settings file /home/node/.n8n/config are too wide. Changing permissions to 0600.. Dalla versione 2.x, n8n impone 0600 su quel file di impostazioni per impostazione predefinita e lo corregge automaticamente all'avvio: questa riga di log indica che la modalità è già stata corretta, solitamente dopo un bind mount o dopo che un ripristino ha copiato il file con permessi errati. Non è necessaria alcuna azione; imposta N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=false solo se il file system non può supportare i permessi.
Mismatching encryption keys — la riga completa indica che la chiave di cifratura nel file delle impostazioni /home/node/.n8n/config non corrisponde alla N8N_ENCRYPTION_KEY nel tuo ambiente. La chiave nell'ambiente è diversa da quella che n8n ha scritto nel volume dati durante un avvio precedente — spesso perché n8n ha generato una chiave casuale quando la variabile non era impostata, e successivamente ne è stata impostata una diversa. Inserisci la chiave originale in .env o, solo se non hai credenziali salvate da conservare, elimina il file config all'interno del volume n8n_data e lascia che n8n lo rigeneri, accettando che le credenziali esistenti diventino illeggibili.
Un banner di login relativo ai cookie sicuri: Your n8n server is configured to use a secure cookie, however you are either visiting this via an insecure URL, or using Safari. Hai impostato N8N_PROTOCOL=https ma hai raggiunto n8n tramite HTTP semplice — solitamente accedendo direttamente all'IP e alla porta invece che tramite il proxy HTTPS. Accedi tramite https://n8n.example.com/. Imposta N8N_SECURE_COOKIE=false solo se non puoi effettivamente usare HTTPS, e mai su un server esposto su internet.
Per inserire un modello linguistico in questi workflow, consulta building AI workflows with Claude and n8n.
FAQ
Devo usare SQLite o Postgres per n8n?
SQLite (il default) è sufficiente per testare n8n o per un'istanza personale che esegue un workflow alla volta. Passa a Postgres per qualsiasi uso critico: il lock di scrittura singolo di SQLite causa database is locked in caso di concorrenza, mentre Postgres gestisce correttamente i backup con pg_dump. La migrazione successiva è manuale, quindi se il server è importante, inizia con Postgres.
Perché i webhook di n8n non vengono mai attivati?
Quasi sempre è colpa di WEBHOOK_URL. Se non impostato o errato, n8n genera indirizzi webhook basati su N8N_HOST:N8N_PORT — spesso con un :5678 o localhost — che sembrano validi ma non sono raggiungibili da internet; di conseguenza le richieste del chiamante non arrivano mai. Imposta WEBHOOK_URL=https://n8n.example.com/ e conferma che il nodo mostri un URL senza porta. La seconda causa è chiamare un webhook il cui workflow non è impostato su Active, il che restituisce The requested webhook ... is not registered..
Cosa devo eseguire il backup in n8n?
Due elementi. Il N8N_ENCRYPTION_KEY dal file .env, poiché ogni credenziale memorizzata è criptata con esso e la sua perdita rende le credenziali indecifrabili in modo permanente — copialo dal server il giorno stesso in cui lo crei. E un pg_dump del database Postgres per i workflow, la cronologia e le credenziali. Il ripristino richiede entrambi: la stessa chiave più il dump.
Come posso mettere n8n dietro HTTPS?
n8n serve traffico HTTP semplice sulla porta 5678; un reverse proxy davanti termina il protocollo TLS. Vincola n8n a 127.0.0.1:5678 in modo che solo il proxy possa raggiungerlo, quindi usa Traefik con certificati automatici o nginx con un certificato Let's Encrypt. Imposta N8N_PROTOCOL=https e WEBHOOK_URL=https://your-host/, e assicurati che il proxy inoltri gli header WebSocket Upgrade, altrimenti l'editor si bloccherà.
Come posso aggiornare n8n in modo sicuro?
Usa un tag immagine specifico invece di latest, esegui prima un pg_dump perché n8n avvia automaticamente le migrazioni all'avvio, leggi le note di rilascio per eventuali breaking changes, quindi aggiorna il tag ed esegui docker compose pull n8n && docker compose up -d n8n. Il container è eliminabile, quindi per fare il rollback basta vincolare il tag precedente e ripristinare il dump eseguito prima dell'aggiornamento.