AI Automation

Bảo mật n8n self-host: khoá webhook lạ và siết firewall

Webhook n8n không được bảo vệ là một lỗ hổng phổ biến khi tự host. Sau vài tháng chạy n8n, bạn sẽ thấy các request webhook đến từ những IP lạ, bot quét cổng, thử payload SQLi, hoặc gửi POST rỗng vào endpoint. Nếu chạy n8n kết nối với nhiều dịch vụ bên ngoài (Zapier, Stripe, Telegram, email) thì rủi ro còn cao hơn: ai đó chỉ cần biết URL webhook của bạn là có thể kích hoạt workflow, đốt credit API hoặc gửi email giả.

Bài này dành cho bạn, sysadmin đã cài VPS n8n trên Ubuntu 24.04 bằng Docker Compose. Mình sẽ chỉ cách khoá webhook bằng header + query param kiểm tra lúc runtime, siết firewall bằng nftables (cho cả dải Docker bridge), và gắn kết fail2ban để tự động cấm IP khi bị quét.

  • Môi trường áp dụng: Ubuntu 24.04 LTS, n8n v1.70+ (Docker Compose), nftables, fail2ban.
  • Kết quả đạt được: endpoint webhook chỉ chấp nhận request từ IP/header đã biết; các request lạ bị drop ở firewall hoặc n8n reject; brute-force bị ban sau 3 lần sai.

Yêu cầu trước khi bắt đầu

Vì sao cần cả khoá webhook lẫn siết firewall?

Nhiều người nghĩ "chỉ cần đường cầu nối VPN là an toàn" hoặc "Nginx filter hết rồi", thực tế không đơn giản. Webhook n8n thường được expose ra public Internet vì các service bên ngoài (GitHub, Stripe, Telegram) cần reach được. Nhưng trên cùng server đó, request lạ cũng đến được endpoint.

Cách làm của mình là hai lớp bảo vệ song song: lớp đầu tiên (firewall) loại bỏ IP lạ khỏi dải không cần biết; lớp thứ hai (runtime check trong n8n) là hàng rào cuối, nếu request vượt firewall thì vẫn có cơ chế kiểm tra để reject. Kết hợp fail2ban để tự động blacklist IP quét.

Bước 1, Khoá webhook bằng header kiểm tra trong n8n

Cách đơn giản và an toàn nhất: mỗi webhook workflow nhận request, bạn thêm một node Code ở đầu để kiểm tra một header do bạn tự định nghĩa (ví dụ x-webhook-key). Nếu header không đúng → throw error, workflow dừng ngay, không đụng đến node xử lý dữ liệu.

Thay vì chỉ 1 key, bạn nên tạo 1 secret dài (ít nhất 32 ký tự) trong tệp .env của n8n Docker Compose. Ví dụ:

# Thêm vào .env
N8N_WEBHOOK_KEY=zkj3h9d8fjk23hd89f2h3k4jdhf9823h4

Trong n8n, ở workflow cần bảo vệ, thêm node Code ngay sau Webhook trigger:

// Node Code: verifyWebhookKey
const expectedKey = process.env.N8N_WEBHOOK_KEY; // môi trường n8n có env này
const receivedKey = $input.first().json.headers['x-webhook-key'];

if (receivedKey !== expectedKey) {
  throw new Error('Invalid webhook key');
}
return $input.first();

Verify: Gửi request thử bằng curl.

# Request hợp lệ (có header)
curl -X POST https://n8n.example.com/webhook/test \
  -H "x-webhook-key: zkj3h9d8fjk23hd89f2h3k4jdhf9823h4" \
  -H "Content-Type: application/json" \
  -d '{"ok":1}'
# Kết quả: workflow chạy bình thường

# Request không header
curl -X POST https://n8n.example.com/webhook/test \
  -H "Content-Type: application/json" \
  -d '{"ok":1}'
# Kết quả: HTTP 500, "Invalid webhook key"

Một biến thể khác: dùng query parameter thay header. Với các service chỉ cho phép gửi query param (vd Zapier webhook), bạn kiểm tra $input.first().json.queryParams.key.

Bước 2, Siết nftables cho dải Docker bridge và public interface

nftables thay thế iptables trên Ubuntu 24.04. Bạn cần cấu hình để chỉ cho phép một số dải IP cố định gọi đến port 443 (webhook) hoặc các port khác.

2.1. Cấu hình nftables cơ bản

Tạo tệp /etc/nftables.conf:

#!/usr/sbin/nft -f

flush ruleset

table inet filter {
  chain input {
    type filter hook input priority 0; policy drop;

    # loopback interface - cho phép toàn bộ
    iif lo accept

    # Established / Related
    ct state established,related accept

    # SSH (thay bằng port của bạn nếu khác)
    tcp dport { 22 } accept

    # Webhook HTTPS - chỉ cho phép từ dải tin cậy
    # Ví dụ cho VPS thueVPS, bạn thay dải IP thực tế
    # Nếu không cần chặn IP cụ thể, dòng dưới cho phép tất cả
    tcp dport { 443 } accept

    # Docker expose port khác (nếu có) thì thêm
    # tcp dport { 5678 } accept

    # ICMP (ping) - nên cho phép để debug
    ip protocol icmp icmp type { echo-request, echo-reply } accept
  }

  chain forward {
    type filter hook forward priority 0; policy drop;
    # cho phép docker forward
    iif docker0 accept
    oif docker0 accept
  }
}

Giải thích: policy drop ở input và forward. Nếu bạn dùng VPS có dải IP riêng (ví dụ bên thueVPS có IPv4 Việt Nam), bạn có thể thêm dòng tcp dport 443 ip saddr { 123.45.67.0/24 } accept thay vì accept toàn bộ.

Áp dụng:

sudo cp /etc/nftables.conf /etc/nftables.conf.bak
sudo nft -f /etc/nftables.conf
sudo systemctl enable --now nftables
sudo systemctl restart nftables

Verify:

sudo nft list ruleset

2.2. Ghi chú với Docker và nftables

Docker tự động thêm rules vào nftables (qua iptables-legacy). Nếu bạn set policy drop ở forward chain, container mới không ra ngoài Internet được. Cách giải quyết: cho phép forward từ docker bridge.

Đã có dòng iif docker0 accept; oif docker0 accept trong forward ở trên, đủ cho n8n container giao tiếp ra ngoài.

Bước 3, Kết hợp fail2ban để tự động ban IP quét webhook

fail2ban giám sát log của Nginx (hoặc n8n) và ban IP khi có quá nhiều lỗi trong khoảng thời gian ngắn.

3.1. Tạo filter cho webhook n8n

fail2ban mặc định không có filter cho n8n. Bạn tạo filter dựa trên log Nginx (vì n8n chạy reverse proxy).

sudo nano /etc/fail2ban/filter.d/n8n-webhook.conf

Nội dung:

[Definition]
failregex = ^.*"POST /webhook/.*" 500 .*$
ignoreregex =

Filter này bắt các request POST đến /webhook/ trả về HTTP 500 (tức header webhook sai).

3.2. Cấu hình jail cho n8n webhook

sudo nano /etc/fail2ban/jail.local

Thêm:

[n8n-webhook]
enabled  = true
port     = http,https
filter   = n8n-webhook
logpath  = /var/log/nginx/access.log
maxretry = 3
findtime = 600
bantime  = 3600

Giải thích filter: nếu trong 10 phút (600s) có 3 request POST webhook lỗi 500 → ban IP 1 giờ.
Nếu bạn có log n8n riêng (volume mount), đổi logpath thành đường dẫn log n8n.

Restart fail2ban:

sudo systemctl restart fail2ban
sudo fail2ban-client status n8n-webhook

Bước 4, Test toàn bộ pipeline bảo mật

Kịch bản test: request webhook lạ (không header, IP random) → nftables drop toàn bộ (nếu cấu hình whitelist IP), hoặc nếu IP lọt qua → nginx log ghi 500 → fail2ban bắt.

  • Test layer 1 (nftables): từ máy ngoài dải cho phép, curl https://n8n.example.com → timeout hoặc connection refused. sudo nft list ruleset thấy policy drop hoạt động.
  • Test layer 2 (header): curl thiếu header → HTTP 500. Kiểm tra log Nginx: tail -f /var/log/nginx/access.log thấy request.
  • Test layer 3 (fail2ban): gửi 4 request POST webhook lỗi → IP bị ban. sudo fail2ban-client status n8n-webhook thấy IP trong danh sách.

Xử lý lỗi thường gặp

LỗiNguyên nhânCách khắc phục
nft: 'list' command syntax errornftables.conf sai cú phápKiểm tra nft -c -f /etc/nftables.conf
Webhook luôn trả 500 dù có header đúngNode Code không truy cập được envTrong n8n container, env phải được set. Kiểm tra docker logs n8n | grep WEBHOOK xem có load không
Container không ra Internet sau khi bật nftablesPolicy forward drop chặn container ra ngoàiThêm iif docker0 accept; oif docker0 accept
fail2ban không bắt được IPLog Nginx ở vị trí khác hoặc regex không khớpKiểm tra log sudo fail2ban-client status n8n-webhook. Sửa logpath và chạy sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/n8n-webhook.conf

Câu hỏi thường gặp

Có cần bật header kiểm tra ở tất cả workflow không?

Bạn nên bật ở mọi workflow dùng webhook public, kể cả webhook từ các service quen (GitHub, Stripe). Các service này có thể cấu hình gửi header tuỳ chỉnh, bạn tạo key riêng cho từng service hoặc dùng 1 key chung và quản lý trong env.

nftables có thể whitelist một dải IP không?

Có. Thêm dòng ip saddr { 103.21.244.0/22, 103.22.200.0/22 } accept trước dòng accept chung. Thay dải bằng dải IP của Cloudflare (nếu dùng CDN) hoặc VPN của team.

Cách backup config nftables trước khi thay đổi?

Dùng sudo nft list ruleset > /root/nft.backup.$(date +%Y%m%d).conf. Khi cần restore: sudo nft -f /root/nft.backup...conf.

Có thể dùng iptables thay nftables không?

Có, nhưng Ubuntu 24.04 khuyến nghị nftables. Nếu bạn dùng script iptables cũ, cài iptables-nftables-compat hoặc chạy update-alternatives --set iptables /usr/sbin/iptables-legacy.

Bài viết liên quan

Lưu ý: Bài viết mang tính tham khảo, tổng hợp kiến thức chung. Mỗi hệ thống, hạ tầng và nhu cầu có đặc thù riêng, nên kiểm thử trong môi trường an toàn và tham vấn kỹ sư trước khi triển khai thực tế.