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
- VPS chạy Ubuntu 24.04, có VPS Linux full root.
- n8n đã cài bằng Docker Compose (bài cài đặt n8n trên VPS Ubuntu 24.04 bằng Docker Compose).
- Nginx reverse proxy + SSL Let’s Encrypt đã chạy (bài cấu hình Nginx reverse proxy và SSL cho n8n).
- User
sudonon-root.
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 rulesetthấ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.logthấy request. - Test layer 3 (fail2ban): gửi 4 request POST webhook lỗi → IP bị ban.
sudo fail2ban-client status n8n-webhookthấy IP trong danh sách.
Xử lý lỗi thường gặp
| Lỗi | Nguyên nhân | Cách khắc phục |
|---|---|---|
nft: 'list' command syntax error | nftables.conf sai cú pháp | Kiểm tra nft -c -f /etc/nftables.conf |
| Webhook luôn trả 500 dù có header đúng | Node Code không truy cập được env | Trong 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 nftables | Policy forward drop chặn container ra ngoài | Thêm iif docker0 accept; oif docker0 accept và |
| fail2ban không bắt được IP | Log Nginx ở vị trí khác hoặc regex không khớp | Kiể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
- Cài đặt n8n trên VPS Ubuntu 24.04 bằng Docker Compose
- Cấu hình Nginx reverse proxy và SSL Let's Encrypt cho n8n
- Webhook n8n không nhận request: checklist xử lý theo thứ tự
- Cấu hình fail2ban bảo vệ nhiều dịch vụ trên VPS


