
Mục lục · 20
Hướng dẫn dựng n8n 2.42.5 trên hai máy thật, một máy có IP public và một máy không, kèm cấu hình VPS nào chạy được, cách khóa lỗ hổng của bản cài mặc định, so sánh Caddy, frp và Cloudflare Tunnel, và chuỗi lỗi khi chuyển n8n sang máy mới.
Tự host n8n là chạy n8n trên máy chủ của bạn bằng Docker thay vì thuê bản n8n Cloud: dữ liệu và credential nằm trên máy bạn, không giới hạn số workflow, chỉ tốn tiền máy chủ. Đổi lại, bạn tự lo bảo mật, HTTPS, sao lưu và cập nhật.
Những điều cần nắm trước khi đọc tiếp:
Lệnh cài chính thức
curl -fsSL https://get.n8n.io | shchạy xong trong 63 đến 69 giây, nhưng mở cổng 5678 cho cả Internet: trên máy có IP public, người lạ vào được trang tạo tài khoản quản trị.VPS 1 CPU, 1GB RAM vẫn chạy được, xử lý 10 webhook mỗi giây; 2 CPU lên 18.
Có IP public: Caddy cấp HTTPS tự động sau 23 giây. Không có IP tĩnh: frp qua một VPS rẻ chậm thêm khoảng 5ms, Cloudflare quick tunnel chậm hơn gần 3 lần và đổi địa chỉ mỗi lần khởi động lại.
Chuyển máy thì chép nguyên volume dữ liệu (1,6MB, khôi phục 29 giây). Export rồi import sang máy mới làm credential không giải mã được.
Mình làm tất cả trên hai máy Ubuntu 24.04 thuê ở AIDev Cloud ngày 08 và 09/10/2026, n8n 2.42.5, Docker 29.6.1. Nếu bạn chưa biết n8n làm được gì, đọc bài n8n là gì trước.
Tự host n8n cần VPS cấu hình thế nào?
Mình chạy n8n trong container giới hạn CPU và RAM bằng docker --cpus và --memory để giả lập VPS giá rẻ, rồi bắn 1.000 webhook với 20 luồng cùng lúc:

Cấu hình | Request/giây | Trễ p95 | Ghi chú |
|---|---|---|---|
1 CPU, 1GB RAM | 10 | 2,5 giây | RAM cao nhất 626MB, không bị kill |
2 CPU, 2GB RAM | 18 | 1,3 giây | RAM cao nhất vẫn 626MB |
25 nhân, SQLite | 37 | 0,6 giây | bộ cài get.n8n.io đủ 5 container |
25 nhân, Postgres | 46 | 0,48 giây | Postgres dùng thêm 58MB RAM |
Workflow dùng để đo gồm Webhook, Code tính tiền và Respond to Webhook. Kết luận của mình: n8n cần thêm CPU hơn là thêm RAM. Bộ cài một dòng có thêm sandbox cho n8n Assistant, tài liệu chính thức ghi cần ít nhất 4GB RAM và 2 vCPU cho bộ này; chỉ cần n8n thì 1GB vẫn chạy.
Ổ đĩa: bộ image chiếm 3,68GB. n8n tự dọn lịch sử chạy sau 14 ngày hoặc khi vượt 10.000 lần chạy (EXECUTIONS_DATA_MAX_AGE=336, EXECUTIONS_DATA_PRUNE_MAX_COUNT=10000). Trên máy mình, khoảng 1.200 lần chạy chiếm 22MB cơ sở dữ liệu.
Cài n8n bằng Docker trên VPS như thế nào?
Từ n8n 3.0 (dự kiến tháng 10/2026), n8n chỉ phát hành qua Docker, npm install n8n không còn dùng cho bản mới. Cách cài được tài liệu khuyến nghị hiện nay là một dòng lệnh, chạy trên máy đã có Docker và Docker Compose v2:
docker compose version
curl -fsSL https://get.n8n.io | sh
Script tạo thư mục n8n/ gồm compose.yml, .env (mật khẩu nội bộ sinh ngẫu nhiên) và searxng-settings.yml, rồi kéo 5 container. Trên máy có IP public mình đo 63 giây, máy còn lại 69 giây:

Các lệnh dùng hằng ngày, chạy trong thư mục chứa n8n/:
docker compose -f ./n8n/compose.yml down # dừng
docker compose -f ./n8n/compose.yml up -d # chạy lại
docker compose -f ./n8n/compose.yml logs n8n --tail 50
Vì sao phải khóa cổng 5678 ngay sau khi cài?
File compose.yml mặc định có dòng - '5678:5678', nghĩa là n8n nghe trên mọi địa chỉ của máy. Trên máy có IP public, ngay sau khi cài, mình đứng từ một máy khác gọi vào và thấy showSetupOnFirstLoad = True: trang tạo tài khoản owner đang mở cho bất kỳ ai biết IP.
Sửa bằng cách chỉ cho n8n nghe ở 127.0.0.1, mọi truy cập từ ngoài đi qua reverse proxy có HTTPS ở bước sau:
cd n8n
sed -i "s#- '5678:5678'#- '127.0.0.1:5678:5678'#" compose.yml
docker compose up -d n8n

Làm bước này trước cả khi tạo tài khoản. Cũng đừng mở cổng của container sandbox-runner, vì nó chạy Docker-in-Docker với quyền privileged; script cài có in sẵn cảnh báo này.
Gắn tên miền và HTTPS cho n8n bằng Caddy
Mình dùng Caddy (77.533 sao trên GitHub) vì nó tự xin và gia hạn chứng chỉ Let's Encrypt. Trỏ bản ghi A của tên miền về IP máy chủ, rồi tạo n8n/Caddyfile:
n8n-lab.forai.dev {
reverse_proxy n8n:5678
}
Thêm service caddy vào cuối khối services: trong compose.yml, và thêm caddy-data: vào khối volumes: ở đầu file:
caddy:
image: caddy:2.11.7
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
depends_on:
- n8n
Lần đầu mình chèn nhầm đoạn này vào khối volumes: (file của n8n đặt volumes lên trước services), Compose báo lỗi:
validating /root/n8n/compose.yml: additional properties 'caddy' not allowed
Cuối cùng báo cho n8n biết nó đang chạy sau proxy, thêm vào .env:
N8N_WEBHOOK_URL=https://n8n-lab.forai.dev/
N8N_EDITOR_BASE_URL=https://n8n-lab.forai.dev/
N8N_PROXY_HOPS=1
Nếu bạn từng đặt biến WEBHOOK_URL thì lưu ý: theo tài liệu reverse proxy của n8n, từ bản 2.35.0 biến đúng là N8N_WEBHOOK_URL, biến cũ chỉ còn được hỗ trợ kèm cảnh báo. Thiếu biến này, URL webhook trong editor vẫn là localhost, dịch vụ bên ngoài không gọi vào được:

Chạy docker compose up -d, Caddy lấy chứng chỉ trong 23 giây; http:// tự chuyển sang https:// (mã 308), chứng chỉ Let's Encrypt có hạn 90 ngày và Caddy tự gia hạn. Mở lại node Webhook, URL đã thành tên miền:

Gọi webhook từ một máy ở Hà Nội qua tên miền: lần đầu 2,09 giây, các lần sau 62 đến 67 mili giây.
Không có IP tĩnh thì có làm HTTPS cho n8n được không?
Được. Mình để n8n trên một máy không có IP public (giống máy ở nhà hay ở công ty sau NAT) rồi thử hai cách đưa nó ra Internet, đo độ trễ webhook từ Hà Nội:

Cách | Cần gì | Trễ trung vị | Nhược điểm mình gặp |
|---|---|---|---|
IP public + Caddy | VPS có IP | 65ms | Máy n8n phải có IP public |
frp qua VPS cầu nối | 1 VPS rẻ có IP + tên miền | 70ms | Phải giữ thêm một máy và token |
Cloudflare quick tunnel | Không cần gì | 187ms | Đi vòng Hồng Kông, đổi URL mỗi lần khởi động |
Cách 1: frp qua một VPS rẻ
frp (109.781 sao) mở một đường hầm từ máy ở nhà ra VPS. Trên VPS, chạy frps cạnh Caddy trong cùng compose.yml và cho Caddy chuyển cổng 8443 vào đường hầm.
File n8n/frp/frps.toml trên VPS:
bindPort = 7000
auth.token = "chuỗi bí mật dài"
Thêm vào Caddyfile, và thêm service frps (image alpine chạy file frps, mở cổng 7000) vào compose.yml:
n8n-lab.forai.dev:8443 {
reverse_proxy frps:6000
}
Trên máy ở nhà, tải frpc v0.71.0 từ trang release và tạo /etc/frp/frpc.toml:
serverAddr = "IP_CUA_VPS"
serverPort = 7000
auth.token = "chuỗi bí mật dài"
[[proxies]]
name = "n8n-nha"
type = "tcp"
localIP = "127.0.0.1"
localPort = 5678
remotePort = 6000
Đặt N8N_WEBHOOK_URL=https://n8n-lab.forai.dev:8443/ ở máy nhà rồi khởi động lại n8n. Cổng 6000 chỉ nằm trong mạng Docker trên VPS, người ngoài không gọi thẳng vào được:

Cách 2: Cloudflare quick tunnel
Không cần VPS hay tài khoản: cài cloudflared 2026.10.0 rồi chạy cloudflared tunnel --url http://127.0.0.1:5678, sau 4 giây có ngay một địa chỉ https://….trycloudflare.com. Máy của mình được nối qua máy chủ Cloudflare ở Hồng Kông nên mỗi lần gọi mất khoảng 187ms.

Vấn đề là khởi động lại cloudflared thì địa chỉ đổi, địa chỉ cũ trả lỗi 530, mọi webhook đã đăng ký với dịch vụ khác hỏng theo. Chính cloudflared cũng ghi tunnel không tài khoản "no uptime guarantee". Muốn địa chỉ cố định phải tạo named tunnel gắn tên miền trong tài khoản Cloudflare; mình chưa làm phần đó nên không có số đo.
Nên dùng SQLite hay Postgres cho n8n?
Bộ cài một dòng dùng SQLite, đủ cho một người hoặc nhóm nhỏ. Khi đổi sang Postgres 18 theo hướng dẫn chính thức, máy mình xử lý 46 webhook/giây thay vì 37, trễ trung vị 48ms thay vì 69ms.
Chỗ cần cẩn thận: đổi DB_TYPE sang postgresdb là n8n khởi động với một cơ sở dữ liệu trống. Nó chạy 286 migration, tạo 148 bảng, 0 workflow, và trang tạo tài khoản owner mở lại. Máy của mình lúc đó đang ra Internet qua frp nên mình phải vào tạo lại tài khoản ngay. Tài liệu có ghi dữ liệu SQLite không tự chuyển sang, nhưng đọc lướt rất dễ bỏ qua.
Thứ tự mình làm để chuyển dữ liệu, mất 17 giây. Trước khi đổi DB, export ra một thư mục nằm trong volume:
docker compose exec n8n mkdir -p /home/node/.n8n/truoc-pg
docker compose exec n8n n8n export:workflow --all --output=/home/node/.n8n/truoc-pg/wf.json
docker compose exec n8n n8n export:credentials --all --output=/home/node/.n8n/truoc-pg/cred.json
Sau đó thêm service postgres và các biến DB_* theo tài liệu, chạy docker compose up -d, tạo lại tài khoản owner. Cuối cùng import và bật lại workflow:
docker compose exec n8n n8n import:workflow --input=/home/node/.n8n/truoc-pg/wf.json
docker compose exec n8n n8n import:credentials --input=/home/node/.n8n/truoc-pg/cred.json
docker compose exec n8n n8n publish:workflow --id=ID_WORKFLOW
docker compose restart n8n
Import tự tắt mọi workflow ("Deactivating workflow…"), và lệnh publish:workflow báo phải khởi động lại n8n mới có hiệu lực. Với Postgres 18 phải giữ dòng PGDATA: /var/lib/postgresql/data trong service postgres, nếu không Postgres ghi dữ liệu ra ngoài volume.
Sao lưu và chuyển n8n sang máy khác thế nào?
Credential trong n8n được mã hóa bằng một khóa sinh lúc chạy lần đầu, lưu ở file /home/node/.n8n/config trong volume. File export credential cũng là dữ liệu đã mã hóa. Mình thử chuyển sang máy mới bằng export rồi import, kết quả là một chuỗi lỗi:

Import báo thành công nhưng credential không đọc được. Đặt N8N_ENCRYPTION_KEY cũ vào .env thì n8n không khởi động vì khóa trong file config không khớp. Ghi đè file config bằng tài khoản root thì gặp EACCES: permission denied, vì n8n chạy bằng user uid 1000. Sửa quyền xong vẫn lỗi signing.hmac, vì n8n đã mã hóa khóa nội bộ bằng khóa mới ngay lần chạy đầu.
Cách chạy được ngay: chép nguyên volume. Volume của mình nén còn 1,6MB, dừng và đóng gói mất 3 giây.
Trên máy cũ:
docker compose stop n8n
docker run --rm -v n8n_n8n-data:/data:ro -v /root:/out alpine tar czf /out/n8n-data.tgz -C /data .
docker compose start n8n
Chép file n8n-data.tgz sang máy mới đã cài bằng get.n8n.io, rồi:
docker compose stop n8n && docker compose rm -f n8n
docker volume rm n8n_n8n-data && docker volume create n8n_n8n-data
docker run --rm -v n8n_n8n-data:/data -v /root:/in alpine tar xzf /in/n8n-data.tgz -C /data
docker compose up -d n8n
Khôi phục trên máy mới mất 29 giây, đủ 3 workflow, credential giải mã được. Nếu vẫn muốn export/import, đặt N8N_ENCRYPTION_KEY bằng khóa cũ trước khi n8n trên máy mới chạy lần đầu. Lệnh export cũng không tự tạo thư mục, gõ sai đường dẫn sẽ gặp ENOENT: no such file or directory.
Cập nhật n8n lên bản mới thế nào?
Với bộ cài một dòng, chạy lại script kèm --upgrade ở thư mục cha của n8n/. Mình chạy nhầm bên trong thư mục n8n/ và nhận:
Error: no existing install found in ./n8n — run without --upgrade to install.
Chạy đúng chỗ thì script chỉ sửa hai dòng N8N_VERSION và N8N_SANDBOX_VERSION trong .env; phần extra_hosts và Caddy mình thêm vào compose.yml vẫn còn nguyên. Trước khi lên bản 3.0, mở Settings → Migration report để xem workflow nào dùng node sắp bị xóa, chi tiết trong bài n8n 3.0 có gì mới.
Những lỗi mình gặp khi dựng n8n trên VPS
Lỗi | Nguyên nhân | Cách sửa |
|---|---|---|
Người lạ vào được trang tạo owner | Cổng 5678 nghe mọi địa chỉ |
|
| Chèn service vào khối | Đặt dưới |
URL webhook vẫn là | Thiếu | Đặt biến, khởi động lại n8n |
| Import sang máy có khóa khác | Chép nguyên volume |
| Đặt khóa cũ sau khi đã chạy | Đặt trước lần chạy đầu |
| Chạy | Chạy ở thư mục cha |
Webhook 404 ngay sau khi khởi động lại | Workflow chưa đăng ký lại xong | Chờ vài giây rồi gọi lại |
Hai thông báo lỗi nguyên văn mình gặp khi chuyển máy:
Credentials could not be decrypted. The likely reason is that a different "encryptionKey" was used to encrypt the data.
Error: Mismatching encryption keys. The encryption key in the settings file /home/node/.n8n/config does not match the N8N_ENCRYPTION_KEY env var. Please make sure both keys match.
Nếu bạn nối n8n với Ollama chạy trên cùng máy để làm trợ lý AI, còn một lỗi nữa về 127.0.0.1 trong container, cách sửa có trong bài n8n là gì.
Khi nào không nên tự host n8n?
Không ai trong nhóm biết SSH, Docker để xử lý khi máy lỗi hoặc cần cập nhật. Khi đó n8n Cloud đỡ rủi ro hơn.
Workflow giữ dữ liệu quan trọng mà bạn chưa có lịch sao lưu volume ra máy khác.
Bạn định cho khách tự dựng workflow trên n8n của mình. Giấy phép bản miễn phí không cho phép việc này.
Câu hỏi thường gặp
Tự host n8n có mất phí không?
Bản tự host miễn phí theo giấy phép Sustainable Use License, bạn chỉ trả tiền máy chủ. Giới hạn chính: không được bán n8n như một dịch vụ cho người khác tự dựng workflow.
Có cài n8n không cần Docker được không?
Hiện vẫn cài được bằng npm, nhưng tài liệu n8n ghi từ bản 3.0 sẽ không phát hành gói npm chạy được nữa. Cài mới thì nên dùng Docker ngay từ đầu.
Cài n8n trên Windows được không?
Được, qua Docker Desktop với WSL2. Tài liệu khuyên để thư mục n8n trong ổ của WSL (ví dụ ~/n8n), không đặt dưới /mnt/c, vì chép qua lại giữa hai hệ thống file chậm và dễ lỗi quyền.
Dùng ngrok thay cho frp hay Cloudflare được không?
Về nguyên lý giống quick tunnel: máy ở nhà mở đường hầm ra một máy chủ trung gian. Mình chưa đo ngrok nên không có con số để so.
Đọc tiếp
n8n là gì: workflow đầu tiên, trợ lý AI chạy Ollama, cái bẫy thinking.
n8n 3.0 có gì mới: node bị xóa, Migration report, thử bản nightly.
n8n MCP: nối Claude Code vào n8n vừa dựng, cho AI chạy và dựng workflow.
Supabase là gì, tự host bằng Docker: dựng backend Postgres cạnh n8n, cái bẫy RLS.
Nextcloud AIO: tự host thay Google Drive: ổ đĩa chung cho công ty, sao lưu Borg, đo trên máy thật.
Muốn trợ lý trong n8n chạy model trên GPU riêng mà máy chưa có card, AIDev Cloud cho thuê máy GPU theo giờ; hai máy Tesla P40 dùng trong bài có giá 7.000 đồng/giờ (thêm IP public là 8.000 đồng/giờ) ngày 08/10/2026.
Đo trên hai máy Tesla P40 của AIDev Cloud ngày 08 và 09/10/2026: n8n 2.42.5, Docker 29.6.1, Docker Compose 5.2.0, Caddy 2.11.7, frp 0.71.0, cloudflared 2026.10.0, Postgres 18.6, Ubuntu 24.04.
Chuyên mục
AI AutomationVề tác giả
Founder AIDev
Thạc sĩ AI MSE, tốt nghiệp thủ khoa với điểm 9,4/10. Đồng sáng lập, CTO E-University, SVOnline: số hóa 10+ đại học, 40.000+ sinh viên. Sáng lập AIDev và VietPlatform, xây hệ thống AI cho doanh nghiệp. Anh trực tiếp biên soạn và giảng dạy toàn bộ lộ trình AI Engineer.


