Se hai già usato n8n per qualche workflow base, sai che le possibilità sono enormi. Ma il vero potere di n8n si sblocca quando padroneggi i sub-workflow, l’error handling robusto, e sai come fare il deploy self-hosted in produzione. Questa guida avanzata parte dal presupposto che tu abbia già visto il confronto Make vs n8n vs Zapier e abbia scelto n8n — ora impariamo a usarlo seriamente.
Trovi tutto il riferimento tecnico nei docs n8n ufficiali e le immagini Docker su Docker Hub.
Sub-Workflow con Execute Workflow Node
I sub-workflow (o workflow annidati) in n8n ti permettono di creare logiche riutilizzabili che puoi chiamare da qualsiasi altro workflow. Il nodo chiave è Execute Workflow.
Quando usarli:
- Hai la stessa logica ripetuta in più workflow (es. normalizzazione dati cliente)
- Vuoi separare workflow complessi in moduli testabili indipendentemente
- Hai un workflow “orchestratore” che ne chiama altri specializzati
Ecco come passare dati tra workflow parent e child. Nel workflow parent:
// Nodo "Execute Workflow" — configurazione
{
"resource": "workflow",
"operation": "execute",
"workflowId": "42", // ID del sub-workflow
"mode": "passDataToTrigger",
"workflowInputs": {
"customerId": "={{ $json.id }}",
"email": "={{ $json.email }}",
"planType": "={{ $json.subscription.plan }}"
}
}Nel workflow child, usa il nodo When Executed by Another Workflow come trigger. Riceve i dati come items normali:
// Nel nodo Code del sub-workflow
const customer = $input.first().json;
// customer.customerId, customer.email, customer.planType
const result = {
normalized: {
id: customer.customerId.toString(),
email: customer.email.toLowerCase().trim(),
tier: customer.planType === 'pro' ? 'premium' : 'basic',
}
};
return [{ json: result }];💡 Tip: Usa
mode: "passDataToTrigger"quando vuoi passare items multipli al sub-workflow. Usamode: "define"per mappare i campi esplicitamente. Sempre documenta lo schema di input del sub-workflow in un nodo Sticky Note.
Error Handling Robusto: Retry Logic e Notifiche
Un workflow in produzione deve gestire i fallimenti in modo elegante. n8n offre due livelli di error handling:
1. Error handling a livello di nodo — per ogni nodo puoi attivare “On Error” e scegliere tra:
- Stop Workflow: interrompe tutto (default)
- Continue: continua con l’output precedente
- Continue (using error output): aggiunge il messaggio di errore all’item e continua
2. Error Trigger a livello di workflow — crea un workflow dedicato alla gestione errori:
// Struttura workflow "Error Handler Global"
// Trigger: Error Trigger
// Input disponibili automaticamente:
{
"execution": {
"id": "550",
"url": "https://n8n.tuodominio.it/workflow/42/executions/550",
"retryOf": null,
"error": {
"message": "Request failed with status code 429",
"stack": "...",
"cause": { "status": 429 }
},
"lastNodeExecuted": "HTTP Request"
},
"workflow": {
"id": "42",
"name": "Sync Clienti CRM"
}
}Nel workflow di error handling, aggiungi questi nodi:
// Nodo Code: Formatta messaggio di errore
const exec = $input.first().json.execution;
const wf = $input.first().json.workflow;
const message = [
`🚨 *Errore workflow n8n*`,
`*Workflow:* ${wf.name} (ID: ${wf.id})`,
`*Nodo:* ${exec.lastNodeExecuted}`,
`*Errore:* ${exec.error.message}`,
`*URL esecuzione:* ${exec.url}`,
`*Timestamp:* ${new Date().toISOString()}`,
].join('
');
return [{ json: { text: message, url: exec.url } }];Poi collega a un nodo Slack (o Send Email) per la notifica, e opzionalmente a un nodo HTTP Request per fare retry:
// Nodo HTTP Request: Trigger retry esecuzione
{
"method": "POST",
"url": "=https://n8n.tuodominio.it/api/v1/executions/{{ $json.executionId }}/retry",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "n8nApi",
"sendBody": true,
"bodyParameters": {
"loadWorkflow": true
}
}🔧 Pattern consigliato: Crea un solo workflow “Error Handler Globale” e collegalo come error workflow in Settings → Error Workflow di ogni workflow critico. Centralizza la logica di notifica in un unico punto.
Pattern Avanzati: SplitInBatches e Espressioni JS
Quando lavori con centinaia o migliaia di item, il nodo Loop Over Items (ex SplitInBatches) è fondamentale per evitare timeout e rate limiting:
// Espressioni avanzate nell'Expression Editor
// $jmespath: query potente su JSON annidati
{{ $jmespath($json, "orders[?status=='pending'].id") }}
// $node: accedi all'output di qualsiasi nodo precedente
{{ $node["HTTP Request"].json.data.users }}
// $items: tutti gli items del run corrente
{{ $items("SplitInBatches")[0].json }}
// Filtrare items con espressione
{{ $items().filter(item => item.json.active === true).length }}
// $runIndex: indice del batch corrente (in SplitInBatches)
// Utile per logging: "Batch {{ $runIndex + 1 }} di N"Per il merge di branch multipli, usa il nodo Merge con mode combineAll o multiplex a seconda che tu voglia combinare o fare prodotto cartesiano degli item:
// Nodo Code: pattern avanzato con $jmespath
const orders = $input.all().map(item => item.json);
// Raggruppa per cliente usando reduce
const byCustomer = orders.reduce((acc, order) => {
const key = order.customerId;
if (!acc[key]) acc[key] = { customerId: key, orders: [], total: 0 };
acc[key].orders.push(order.id);
acc[key].total += order.amount;
return acc;
}, {});
return Object.values(byCustomer).map(c => ({ json: c }));Deploy Self-Hosted con Docker Compose
La configurazione di produzione consigliata usa n8n con PostgreSQL (non SQLite che è solo per sviluppo) e un reverse proxy Nginx o Caddy:
# docker-compose.yml
version: '3.8'
services:
postgres:
image: postgres:15-alpine
restart: always
environment:
POSTGRES_DB: n8n
POSTGRES_USER: n8n
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U n8n"]
interval: 10s
timeout: 5s
retries: 5
n8n:
image: n8nio/n8n:latest
restart: always
ports:
- "5678:5678"
environment:
DB_TYPE: postgresdb
DB_POSTGRESDB_HOST: postgres
DB_POSTGRESDB_PORT: 5432
DB_POSTGRESDB_DATABASE: n8n
DB_POSTGRESDB_USER: n8n
DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}
N8N_HOST: ${N8N_HOST}
N8N_PORT: 5678
N8N_PROTOCOL: https
WEBHOOK_URL: https://${N8N_HOST}/
N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
EXECUTIONS_MODE: regular
N8N_LOG_LEVEL: info
GENERIC_TIMEZONE: Europe/Rome
volumes:
- n8n_data:/home/node/.n8n
depends_on:
postgres:
condition: service_healthy
volumes:
postgres_data:
n8n_data:Il file .env deve contenere almeno:
POSTGRES_PASSWORD=una_password_lunga_e_sicura
N8N_HOST=n8n.tuodominio.it
N8N_ENCRYPTION_KEY=$(openssl rand -hex 32) # Genera una chiave sicuraPer avviare e aggiornare:
# Avvio iniziale
docker compose up -d
# Update n8n all'ultima versione
docker compose pull n8n
docker compose up -d n8n
# Backup workflow (esporta via API)
curl -H "X-N8N-API-KEY: $N8N_API_KEY" https://n8n.tuodominio.it/api/v1/workflows > backup_workflows_$(date +%Y%m%d).jsonPer il monitoring, aggiungi un workflow n8n con trigger Schedule che ogni 5 minuti chiama il tuo endpoint di health check e notifica su Slack se non risponde:
// Health check URL n8n built-in
// GET /healthz → { "status": "ok" }
// In Cron workflow ogni 5min:
// HTTP Request → https://n8n.tuodominio.it/healthz
// IF: {{ $json.status !== 'ok' }} → Slack notify🎯 Performance tip: Per workflow con molti item (1000+), aumenta
EXECUTIONS_PROCESSamaine configuraN8N_CONCURRENCY_PRODUCTION_LIMIT. Per workflow molto pesanti, considera la modalità Queue con Redis e worker separati.
FAQ: n8n Avanzato
Quanti sub-workflow annidati posso avere?
Non c’è un limite hard, ma considera che ogni livello di annidamento aggiunge overhead. In pratica, 2-3 livelli di sub-workflow sono più che sufficienti. Per logiche molto complesse, considera di usare un nodo Code con logica JavaScript diretta.
Come faccio il backup automatico dei workflow?
La soluzione migliore è usare la Source Control integrata in n8n (Enterprise) o l’API REST per esportare tutti i workflow in JSON e commitarli su Git. Crea un workflow n8n schedulato che esporta e committa ogni notte. Puoi anche usare il --export-all della CLI n8n.
SQLite o PostgreSQL per n8n self-hosted?
PostgreSQL sempre per produzione. SQLite va bene solo per test locali: non è thread-safe per esecuzioni concorrenti, non scala, e si può corrompere. La migrazione da SQLite a PostgreSQL una volta in produzione è dolorosa — parti subito con Postgres.
Come gestisco le credenziali in modo sicuro?
n8n cripta le credenziali nel database usando N8N_ENCRYPTION_KEY. Tieni questa chiave in una variabile d’ambiente sicura (mai nel codice o nel docker-compose committato). In alternativa, usa un secret manager (AWS Secrets Manager, HashiCorp Vault) e inietta le credenziali a runtime.
Posso usare le espressioni JavaScript in tutti i nodi?
Le espressioni {{ }} funzionano nella maggior parte dei campi di configurazione dei nodi. Per logica complessa, usa il nodo Code (JavaScript/Python). Le espressioni non supportano statement multi-linea — per quelli, usa sempre Code. Ricorda che $jmespath, $node, $items e $runIndex sono disponibili solo nelle espressioni, non nel nodo Code.

