Come mettere in sicurezza n8n esposto su internet
Guida pratica per proteggere n8n accessibile da internet: Nginx reverse proxy, fail2ban, Cloudflare, autenticazione, IP whitelisting e sicurezza webhook.

Il problema: n8n è esposto su internet
Se usi n8n in self-hosting, il tuo server è raggiungibile da internet. Questo è necessario per i webhook (WooCommerce, Stripe, CRM devono poter inviare dati a n8n), ma significa anche che la tua istanza è esposta ad attacchi. Scanner automatici trovano le istanze n8n esposte in pochi minuti dalla pubblicazione.
Questa guida ti mostra come proteggere n8n in modo concreto e stratificato, dalla configurazione base fino all'hardening avanzato.
Livello 1: il fondamento - Nginx reverse proxy
Perché non esporre n8n direttamente
n8n ha un server HTTP integrato, ma non è progettato per gestire traffico diretto da internet. Nginx aggiunge:
- Terminazione SSL/TLS
- Header di sicurezza
- Rate limiting
- Buffering e compressione
- Logging strutturato
- Possibilità di filtrare richieste malevole
Configurazione Nginx completa
# /etc/nginx/sites-available/n8n
# Rate limiting zones
limit_req_zone $binary_remote_addr zone=login:10m rate=3r/m;
limit_req_zone $binary_remote_addr zone=webhook:10m rate=30r/s;
limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=addr:10m;
# Redirect HTTP -> HTTPS
server {
listen 80;
listen [::]:80;
server_name n8n.tuodominio.it;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name n8n.tuodominio.it;
# SSL
ssl_certificate /etc/letsencrypt/live/n8n.tuodominio.it/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/n8n.tuodominio.it/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers EECDH+AESGCM:EDH+AESGCM;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# Header di sicurezza
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
# Nascondi versione Nginx
server_tokens off;
# Limiti
client_max_body_size 16M;
client_body_timeout 30s;
client_header_timeout 30s;
# Limiti connessioni simultanee
limit_conn addr 20;
# Blocca user-agent malevoli noti
if ($http_user_agent ~* (nmap|nikto|wikto|sf|sqlmap|bsqlbf|w3af|acunetix|havij|appscan)) {
return 403;
}
# Blocca richieste senza Host header
if ($host !~* ^n8n\.tuodominio\.it$) {
return 444;
}
# Endpoint login - rate limiting stretto
location /rest/login {
limit_req zone=login burst=5 nodelay;
proxy_pass http://127.0.0.1:5678;
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;
}
# Webhook - rate limiting moderato
location /webhook/ {
limit_req zone=webhook burst=50 nodelay;
proxy_pass http://127.0.0.1:5678;
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;
# Timeout più lungo per webhook che processano dati
proxy_read_timeout 120s;
}
# Webhook di test (blocca in produzione)
location /webhook-test/ {
# Permetti solo da IP interni
allow 127.0.0.1;
allow 10.0.0.0/8;
allow 172.16.0.0/12;
allow 192.168.0.0/16;
deny all;
proxy_pass http://127.0.0.1:5678;
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;
}
# Tutto il resto - rate limiting standard
location / {
limit_req zone=general burst=20 nodelay;
proxy_pass http://127.0.0.1:5678;
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;
# WebSocket per l'editor
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# Blocca accesso a file sensibili
location ~ /\. {
deny all;
}
# Access log separato per analisi
access_log /var/log/nginx/n8n-access.log;
error_log /var/log/nginx/n8n-error.log warn;
}
Dopo la configurazione:
# Verifica sintassi
nginx -t
# Ricarica
systemctl reload nginx
Livello 2: fail2ban - blocco automatico IP malevoli
Installazione e configurazione
apt install fail2ban
Filtro per n8n - tentativi di login
Crea /etc/fail2ban/filter.d/n8n-auth.conf:
[Definition]
failregex = ^<HOST> .* "POST /rest/login HTTP.*" (401|403)
^<HOST> .* "POST /rest/login HTTP.*" 429
ignoreregex =
Filtro per scanner e bot
Crea /etc/fail2ban/filter.d/n8n-scanner.conf:
[Definition]
failregex = ^<HOST> .* "(GET|POST|HEAD) .*(wp-login|wp-admin|phpmyadmin|.env|.git|actuator|solr|console).*" (404|403)
^<HOST> .* ".*" 400
ignoreregex =
Jail configuration
Crea /etc/fail2ban/jail.d/n8n.conf:
[n8n-auth]
enabled = true
port = http,https
filter = n8n-auth
logpath = /var/log/nginx/n8n-access.log
maxretry = 5
findtime = 300
bantime = 3600
action = %(action_mwl)s
[n8n-scanner]
enabled = true
port = http,https
filter = n8n-scanner
logpath = /var/log/nginx/n8n-access.log
maxretry = 10
findtime = 60
bantime = 86400
action = %(action_mwl)s
[n8n-aggressive]
enabled = true
port = http,https
filter = n8n-auth
logpath = /var/log/nginx/n8n-access.log
maxretry = 15
findtime = 86400
bantime = 604800
action = %(action_mwl)s
# Riavvia fail2ban
systemctl restart fail2ban
# Verifica stato
fail2ban-client status
fail2ban-client status n8n-auth
Livello 3: Cloudflare come scudo
Perché usare Cloudflare
Cloudflare (piano gratuito) aggiunge un livello di protezione prima che il traffico raggiunga il tuo server:
- DDoS protection: assorbe attacchi volumetrici
- WAF base: blocca attacchi comuni
- Bot management: filtra bot malevoli
- IP nascosto: il tuo IP reale non è visibile
- Cache: riduce il carico sul server
Configurazione consigliata
- Proxy attivo (nuvola arancione): attiva per il dominio n8n
- SSL mode: Full (Strict) - Cloudflare verificherà il tuo certificato SSL
- Minimum TLS version: 1.2
- Always Use HTTPS: attivo
- HSTS: attivo con includeSubDomains
WAF Rules personalizzate
Nel dashboard Cloudflare, crea queste regole:
Regola 1 - Blocca paesi non necessari: Se il tuo business è solo italiano, puoi limitare l'accesso al pannello n8n solo dall'Italia:
(http.request.uri.path ne "/webhook/" and ip.geoip.country ne "IT")
-> Block
Nota: i webhook restano aperti a tutti i paesi (servizi esterni devono poterli raggiungere).
Regola 2 - Challenge per richieste sospette:
(cf.threat_score gt 14)
-> Managed Challenge
Regola 3 - Blocca accesso diretto via IP:
(http.host eq "123.456.789.0")
-> Block
Configurazione Nginx con Cloudflare
Se usi Cloudflare, configura Nginx per accettare connessioni solo da IP Cloudflare:
# /etc/nginx/conf.d/cloudflare-ips.conf
# Aggiorna periodicamente da https://www.cloudflare.com/ips/
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 131.0.72.0/22;
real_ip_header CF-Connecting-IP;
E nel firewall, permetti traffico HTTP/HTTPS solo da Cloudflare:
# Script per aggiornare le regole UFW
#!/bin/bash
for ip in $(curl -s https://www.cloudflare.com/ips-v4); do
ufw allow from $ip to any port 443 proto tcp
done
# Rimuovi la regola generica
ufw delete allow 443/tcp
Livello 4: sicurezza dei webhook
Il problema
I webhook sono l'unico endpoint che deve restare aperto al pubblico. Questo li rende il vettore di attacco principale.
Protezioni da implementare
1. Validazione HMAC per webhook WooCommerce:
WooCommerce firma i webhook con un secret. Verifica la firma in n8n:
// Primo nodo dopo il Webhook: verifica firma
const crypto = require('crypto');
const secret = $env.WOOCOMMERCE_WEBHOOK_SECRET;
const signature = $input.first().headers['x-wc-webhook-signature'];
const body = JSON.stringify($input.first().json);
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(body)
.digest('base64');
if (signature !== expectedSignature) {
throw new Error('Firma webhook non valida - richiesta rifiutata');
}
// Firma valida, continua il workflow
return $input.all();
2. Validazione per Stripe webhook:
const crypto = require('crypto');
const endpointSecret = $env.STRIPE_WEBHOOK_SECRET;
const signature = $input.first().headers['stripe-signature'];
const payload = $input.first().json;
// Stripe usa un formato specifico per la firma
const elements = signature.split(',');
const timestamp = elements.find(e => e.startsWith('t=')).split('=')[1];
const receivedSig = elements.find(e => e.startsWith('v1=')).split('=')[1];
const signedPayload = `${timestamp}.${JSON.stringify(payload)}`;
const expectedSig = crypto
.createHmac('sha256', endpointSecret)
.update(signedPayload)
.digest('hex');
if (receivedSig !== expectedSig) {
throw new Error('Firma Stripe non valida');
}
3. IP whitelisting per webhook specifici:
Se sai da quali IP arrivano i webhook, restringe l'accesso in Nginx:
# Webhook WooCommerce - solo dal server del sito
location /webhook/woocommerce/ {
allow 1.2.3.4; # IP del server WooCommerce
deny all;
proxy_pass http://127.0.0.1:5678;
# ... proxy headers ...
}
# Webhook Stripe - solo da IP Stripe
location /webhook/stripe/ {
allow 3.18.12.63;
allow 3.130.192.163;
allow 13.235.14.237;
allow 13.235.122.149;
# ... altri IP Stripe ...
deny all;
proxy_pass http://127.0.0.1:5678;
# ... proxy headers ...
}
4. URL webhook non prevedibili:
SBAGLIATO:
/webhook/ordini
/webhook/pagamenti
/webhook/lead
GIUSTO:
/webhook/f8a3b1c7-ordini-woo-9d4e
/webhook/c2e5d8f1-stripe-pay-7b3a
/webhook/a1d4e7f2-lead-form-5c8b
Livello 5: autenticazione avanzata
Basic auth con password forte
Come minimo:
N8N_BASIC_AUTH_ACTIVE=true
N8N_BASIC_AUTH_USER=admin_nome_non_ovvio
N8N_BASIC_AUTH_PASSWORD=generata_con_openssl_rand_base64_32
Autenticazione a due fattori con Authelia
Per un livello di sicurezza superiore, metti Authelia davanti a n8n:
# docker-compose.yml
services:
authelia:
image: authelia/authelia:latest
volumes:
- ./authelia:/config
environment:
- TZ=Europe/Rome
n8n:
image: n8nio/n8n:latest
# ... configurazione n8n ...
Authelia aggiunge:
- Autenticazione a due fattori (TOTP, WebAuthn)
- Single Sign-On
- Gestione sessioni
- Logging accessi
La configurazione di Nginx viene modificata per passare attraverso Authelia per tutte le rotte tranne i webhook:
# Proteggi tutto tranne webhook
location / {
# Authelia verification
auth_request /authelia;
# ... proxy a n8n ...
}
# Webhook senza Authelia (ma con altre protezioni)
location /webhook/ {
# Niente auth_request qui
# ... proxy a n8n ...
}
Monitoraggio della sicurezza
Log da controllare
# Tentativi di login falliti
grep "401\|403" /var/log/nginx/n8n-access.log | tail -20
# IP bannati da fail2ban
fail2ban-client status n8n-auth
# Richieste sospette
grep -E "(wp-login|phpmyadmin|.env|.git)" /var/log/nginx/n8n-access.log | \
awk '{print $1}' | sort | uniq -c | sort -rn | head -10
Alert automatici
Crea un workflow n8n che monitora i propri log di sicurezza (si, è meta):
Schedule Trigger (ogni ora)
-> Leggi /var/log/nginx/n8n-access.log (ultime 60 min)
-> Conta tentativi 401/403
-> IF più di 50 tentativi:
-> Notifica Telegram: "Possibile attacco in corso"
-> Allega top 10 IP sospetti
Checklist di sicurezza finale
Critico (giorno 1)
- Nginx reverse proxy configurato
- SSL/TLS attivo e aggiornato
- Autenticazione forte attiva
- Porta 5678 non esposta all'esterno
- Firewall attivo con policy deny-by-default
Importante (prima settimana)
- fail2ban configurato per n8n
- Rate limiting su tutti gli endpoint
- Webhook con validazione firma/secret
- Header di sicurezza impostati
- Cloudflare (o CDN equivalente) attivo
Consigliato (primo mese)
- IP whitelisting per webhook specifici
- Autenticazione a due fattori (Authelia o equivalente)
- Monitoraggio log di sicurezza
- URL webhook non prevedibili
- Test di penetrazione base
La sicurezza non è un progetto una tantum, ma un processo continuo. Rivedi questa checklist ogni trimestre e aggiorna le configurazioni in base alle nuove minacce.
Hai dubbi sulla sicurezza della tua istanza n8n? Contattaci per un audit di sicurezza personalizzato.
Team n8n.it
Specialisti in Automazione
Siamo un team di esperti n8n focalizzati sull'automazione dei processi aziendali e la sicurezza delle implementazioni self-hosted.
Articoli correlati

Audit di sicurezza n8n: checklist per proteggere la tua istanza
Checklist pratica e completa per mettere in sicurezza la tua istanza n8n: reverse proxy, SSL, autent...
Leggi di più
Il costo reale di n8n self-hosted: server, manutenzione e tempo
n8n è gratuito, ma il self-hosting ha costi nascosti. Analizziamo il TCO reale: server, SSL, backup,...
Leggi di più
I 7 errori più comuni con n8n in produzione (e come evitarli)
Errori reali che abbiamo visto in decine di installazioni n8n in produzione: nessun error handling, ...
Leggi di più