Torna al Blog
Guida

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.

Team n8n.it- Specialisti in Automazione30 marzo 20269 min read
Come mettere in sicurezza n8n esposto su internet

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

  1. Proxy attivo (nuvola arancione): attiva per il dominio n8n
  2. SSL mode: Full (Strict) - Cloudflare verificherà il tuo certificato SSL
  3. Minimum TLS version: 1.2
  4. Always Use HTTPS: attivo
  5. 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.

T

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