AI Automation

Bảo mật n8n tự host: chặn truy cập, xoay credential, giới hạn IP

Bạn vừa cài xong n8n trên VPS, workflow chạy ngon lành, webhook nhận request đều đặn. Nhưng bạn đã bao giờ tự hỏi: nếu ai đó đoán được URL editor, hoặc credential Google Sheets của bạn bị lộ, thì chuyện gì xảy ra? Với n8n self-hosted, bảo mật không phải option, nó là lớp sống còn. Bài này đi thẳng vào 5 biện pháp phòng thủ: basic auth, SSO, firewall theo IP, xoay credential định kỳ và tách webhook path. Tất cả đều chạy được trên VPS n8n với Linux.

Trước khi bắt đầu, bạn cần: một VPS Linux (Ubuntu 24.04 hoặc Debian 12) đã cài Docker và Docker Compose, n8n đang chạy, quyền sudo, và domain trỏ về VPS nếu muốn dùng SSL. Các bước dưới đây giả định bạn dùng Nginx làm reverse proxy, nếu dùng Caddy hoặc Traefik, điều chỉnh tương tự.

Vì sao n8n lại cần lớp bảo vệ riêng?

n8n là công cụ tự động hoá mạnh, nó kết nối hàng trăm dịch vụ qua credential (API key, OAuth token, mật khẩu). Một workflow sai có thể gửi email hàng loạt, xoá dữ liệu, hoặc gọi API thanh toán. Nếu editor của n8n lộ ra ngoài mà không có xác thực, bất kỳ ai cũng có thể đọc, sửa, chạy workflow của bạn. Đó là lý do lớp bảo mật đầu tiên phải là chặn truy cập vào giao diện quản trị.

Bước 1, Chặn public editor bằng basic auth hoặc SSO

1.1 Basic auth qua Nginx

Cách đơn giản nhất để chặn editor là thêm lớp xác thực cơ bản ngay tại reverse proxy. Nginx hỗ trợ sẵn module ngx_http_auth_basic_module, không cần cài thêm gì.

Tạo file mật khẩu:

sudo apt install apache2-utils -y
sudo htpasswd -c /etc/nginx/.htpasswd admin
# Nhập mật khẩu mạnh, ít nhất 16 ký tự

Sau đó thêm vào block server trong file cấu hình Nginx cho n8n (thường ở /etc/nginx/sites-available/n8n):

location / {
    auth_basic "n8n Admin";
    auth_basic_user_file /etc/nginx/.htpasswd;
    proxy_pass http://localhost:5678;
    ...
}

Verify: sudo nginx -t && sudo systemctl reload nginx. Vào trình duyệt, bạn sẽ thấy popup yêu cầu tên, mật khẩu trước khi vào editor. Nếu thấy trang n8n hiện ra ngay mà không hỏi, kiểm tra lại vị trí đặt auth_basic, nó phải nằm trong location / chứ không phải location /webhook.

1.2 SSO với OAuth2 Proxy

Basic auth tiện nhưng thiếu kiểm soát phiên, dễ bị brute force nếu bạn không có fail2ban. SSO (ví dụ dùng Google Workspace, GitHub, GitLab) vừa an toàn hơn vừa có thể tích hợp xác thực 2 yếu tố (2FA) từ bên thứ ba. Công cụ phổ biến là oauth2-proxy.

# Chạy oauth2-proxy như container Docker
docker run -d \
  --name oauth2-proxy \
  --restart=always \
  -p 4180:4180 \
  -e OAUTH2_PROXY_PROVIDER=google \
  -e OAUTH2_PROXY_CLIENT_ID=your-client-id \
  -e OAUTH2_PROXY_CLIENT_SECRET=your-client-secret \
  -e OAUTH2_PROXY_COOKIE_SECRET=random-32-bytes-hex \
  -e OAUTH2_PROXY_EMAIL_DOMAINS=example.com \
  quay.io/oauth2-proxy/oauth2-proxy

Sửa cấu hình Nginx để trỏ location / qua oauth2-proxy:

location / {
    proxy_pass http://127.0.0.1:4180;
    proxy_set_header Host $host;
    ...
}

Bạn cần tạo OAuth client ID/secret từ Google Cloud Console, GitHub OAuth Apps, hoặc GitLab. Lưu ý: n8n cũng hỗ trợ SSO riêng cho bản Enterprise (có license phí). Cách trên dùng proxy là miễn phí, chạy với bất kỳ bản n8n nào.

Bước 2, Giới hạn IP truy cập vào editor bằng firewall

Bạn có một dải IP tĩnh từ văn phòng hoặc VPN? Hãy chặn mọi IP không nằm trong danh sách trắng. Làm điều này ở hai lớp: Nginx và firewall hệ thống (UFW/nftables) để dự phòng.

2.1 Lớp Nginx

Thêm vào block location / trong config Nginx cho n8n:

location / {
    allow 192.168.1.0/24;   # dải IP văn phòng
    allow 103.21.244.0/22;  # IP VPN của bạn
    deny all;
    auth_basic "n8n Admin";
    auth_basic_user_file /etc/nginx/.htpasswd;
    proxy_pass http://localhost:5678;
}

2.2 Lớp UFW (Ubuntu)

sudo ufw allow from 192.168.1.0/24 to any port 443
sudo ufw allow from 103.21.244.0/22 to any port 443
sudo ufw deny 443   # chặn mọi IP khác vào port HTTPS

Verify: curl -I https://n8n.yourdomain.com từ một máy không trong danh sách trắng, phải trả về 403 Forbidden hoặc timeout tuỳ lớp nào chặn trước. Nếu bạn dùng VPN, nhớ kiểm tra IP thật của VPN (có thể thay đổi theo khu vực).

Lưu ý quan trọng: webhook endpoint mặc định là /webhook/, không để chung location với editor. Hãy tách riêng (bước 5).

Bước 3, Xoay credential định kỳ (và cách làm tự động)

Credential trong n8n (API key, OAuth token, mật khẩu) có thể bị lộ qua log, qua workflow fail, hoặc bị đánh cắp từ máy dev. Xoay chúng định kỳ là thói quen an toàn nhưng thường bị bỏ qua vì thủ công. Dưới đây là cách xoay credential với Google OAuth, các dịch vụ khác (Slack, GitHub, Twitter) làm tương tự.

3.1 Xoay thủ công

  1. Vào n8n editor → Settings → Credentials → chọn credential cần xoay.
  2. Nhấn "Reconnect" hoặc nhập lại API key / OAuth token mới.
  3. Chạy workflow test để đảm bảo credential mới hoạt động.
  4. Thu hồi credential cũ ở dịch vụ gốc (vd Google Cloud Console → APIs & Services → Credentials → Revoke).

3.2 Xoay tự động (dùng n8n workflow + webhook)

Bạn có thể tạo một workflow n8n riêng chạy mỗi tháng để tự động refresh OAuth token. Ví dụ với Google OAuth:

1. Trigger: Schedule (mỗi 30 ngày)
2. HTTP Request: POST đến Google OAuth endpoint với refresh_token
   - URL: https://oauth2.googleapis.com/token
   - Body: client_id, client_secret, refresh_token, grant_type=refresh_token
3. Code node: parse JSON response, lấy access_token mới
4. n8n Credential node: gọi internal API của n8n để cập nhật credential
   - Dùng HTTP Request với Basic Auth (admin user) tới /rest/credentials/:id
   - Body: { "data": { "oauthTokenData": { "access_token": "new_token" } } }

Verify: workflow chạy thành công, credential cũ vẫn còn trong database n8n nhưng token đã hết hạn. Kiểm tra bằng cách chạy một workflow dùng credential đó, nếu báo lỗi "token expired" thì đã xoay thành công.

Để workflow này tự động, bạn cần bảo mật nó bằng một webhook secret riêng (bước 5), tránh người lạ gọi lại endpoint update credential.

Bước 4, Tách webhook path ra khỏi editor

Mặc định, n8n dùng cùng một domain và port cho cả editor lẫn webhook (ví dụ https://n8n.yourdomain.com/webhook/order-received). Điều này có nghĩa nếu bạn bật firewall chặn toàn bộ, webhook cũng bị chặn theo. Giải pháp: dùng subdomain riêng cho webhook, ví dụ webhook.yourdomain.com, và chỉ mở port 443 ở firewall cho subdomain này.

# Trong Nginx, tạo server block riêng cho webhook:
server {
    listen 443 ssl;
    server_name webhook.yourdomain.com;

    location / {
        proxy_pass http://localhost:5678;
        # Không có auth_basic, để webhook nhận request từ bên ngoài
    }
}

# Và server block cho editor thì vẫn giữ auth + IP whitelist:
server {
    listen 443 ssl;
    server_name n8n.yourdomain.com;

    location / {
        auth_basic "n8n Admin";
        auth_basic_user_file /etc/nginx/.htpasswd;
        proxy_pass http://localhost:5678;
    }
}

Lưu ý: n8n nhận diện webhook qua URL path, không qua hostname. Việc dùng subdomain khác chỉ giúp firewall tách biệt; n8n vẫn chạy một instance riêng biệt. Bạn cần trỏ cả hai subdomain về cùng IP và port 5678.

Verify: curl -X POST https://webhook.yourdomain.com/webhook/test, trả về 200. curl https://n8n.yourdomain.com, yêu cầu auth. Nếu cả hai đều hỏi auth, kiểm tra xem webhook location có bị kế thừa auth từ server block khác không (dùng auth_basic off; trong webhook location nếu cần).

Bước 5, Những lưu ý quan trọng khác

5.1 Không expose database trực tiếp

Mặc định, n8n dùng SQLite (file ~/.n8n/database.sqlite). Nếu bạn migrate sang PostgreSQL cho production, đừng expose port database ra ngoài, chỉ cho phép kết nối từ container n8n. Kiểm tra Docker Compose: PostgreSQL service không được publish port ra host (không có ports: "5432:5432").

5.2 Bảo vệ encryption key

n8n dùng N8N_ENCRYPTION_KEY để mã hoá credential trong database. Nếu key lộ, kẻ tấn công đọc được toàn bộ credential dù có xoay cũng vô ích. Lưu key ở nơi an toàn (ví dụ vault, hoặc file chỉ root đọc được), không commit lên git.

# Tạo key mạnh
openssl rand -hex 32
# Lưu vào .env của n8n
echo 'N8N_ENCRYPTION_KEY=your-64-char-hex-key' >> .env
chmod 600 .env

5.3 Audit log

Bản n8n community không có audit log tích hợp. Bạn có thể bật log truy cập Nginx và dùng journalctl -u n8n (nếu chạy systemd) hoặc docker logs n8n để xem ai đã gọi API nào. Kết hợp với fail2ban để chặn IP gọi sai nhiều lần.

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

Lỗi 1: Basic auth không hiện, vào thẳng editor

Nguyên nhân: Nginx đang serve một static page hoặc redirect khác trước khi proxy. Kiểm tra file config có dòng auth_basic trong đúng location / không, và auth_basic_user_file trỏ đến file .htpasswd có thật. Reload Nginx sau mỗi lần sửa.

Lỗi 2: Webhook báo 403 dù đã tách subdomain

Nếu bạn dùng chung server block mà dùng location / cho editor (có auth) và location /webhook cho webhook (không auth), n8n gửi request vào /webhook/, Nginx sẽ match đúng location và auth không kích hoạt. Nhưng nếu bạn dùng subdomain khác và vẫn 403, kiểm tra firewall UFW có đang chặn tất cả IP vào port 443 không.

Lỗi 3: Docker container không restart sau khi cập nhật credential

Một số credential (như database connection) yêu cầu restart container để load lại. Dùng docker restart n8n sau khi xoay. Với PostgreSQL credential, n8n tự động reconnect nên không cần restart.

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

Có cần chạy n8n trên máy chủ riêng (dedicated) không?

Với n8n cá nhân hoặc team nhỏ (dưới 10 workflow chạy đồng thời), VPS Linux với 2-4 GB RAM là đủ. Nếu bạn có hàng trăm workflow hoặc cần gửi email transactional số lượng lớn, nên cân nhắc thuê máy chủ riêng để đảm bảo tài nguyên không bị tranh chấp. N8n không yêu cầu nhiều CPU, nhưng RAM và I/O NVMe ảnh hưởng trực tiếp đến tốc độ xử lý workflow.

Basic auth có an toàn không? Hay nên dùng SSO?

Basic auth qua HTTPS an toàn nếu bạn dùng mật khẩu mạnh (20+ ký tự ngẫu nhiên) và kết hợp fail2ban để chặn brute force. Tuy nhiên, nếu team bạn dùng Google Workspace hoặc GitHub, SSO tiện hơn vì quản lý user tập trung và có thể bắt buộc 2FA.

Làm sao để biết ai đã truy cập editor?

Bật access log của Nginx (/var/log/nginx/access.log), mỗi request đều ghi IP, thời gian, user-agent. Nếu cần chi tiết hơn, tích hợp oauth2-proxy (bước 1.2) vì proxy này log email của người dùng sau khi xác thực.

Xoay credential có làm gián đoạn workflow không?

Nếu bạn xoay OAuth token bằng refresh token, workflow vẫn chạy bình thường vì token mới được cập nhật trước khi token cũ hết hạn. Với API key tĩnh, bạn phải tạo key mới, cập nhật credential trên n8n, workflow sẽ dùng key mới ngay lần chạy kế tiếp, không downtime.

Tôi dùng n8n để nhận webhook từ Stripe, có cần bảo mật gì thêm?

Stripe có thể gửi webhook từ nhiều IP khác nhau. Đừng whitelist IP cho webhook endpoint, thay vào đó, xác thực chữ ký webhook (Stripe signature) ngay trong n8n workflow bằng node "Crypto" để verify HMAC. Điều này quan trọng hơn firewall.

N8N credential bị lộ, tôi nên làm gì ngay lập tức?

Bước 1: Revoke tất cả credential đã lộ ở dịch vụ gốc (Google Cloud, Slack, v.v.). Bước 2: Xoá hoặc vô hiệu hoá workflow dùng credential đó. Bước 3: Thay đổi encryption key n8n và reset database nếu cần (mất dữ liệu workflow nhưng an toàn). Bước 4: Kiểm tra log Nginx và docker logs để xem IP nào đã truy cập, triển khai firewall ngay.

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ế.