n8n là công cụ workflow automation mã nguồn mở (n8n.io) đang được dân dev cực kỳ ưa chuộng để nối API, dựng bot, và đặc biệt là làm các AI workflow kiểu webhook nhận sự kiện - gọi LLM xử lý - đẩy kết quả đi. Bản cloud của n8n giới hạn số lần chạy (execution) và tính tiền theo gói. Self-host trên VPS thì khác hẳn: dữ liệu của riêng bạn, execution không giới hạn, tự cắm LLM nào tùy thích. Bài này hướng dẫn dựng full stack n8n bằng Docker trên VPS Ubuntu, có Postgres, HTTPS, và một ví dụ AI workflow thật.
Vì sao nên self-host n8n thay vì dùng cloud?
| Tiêu chí | n8n Cloud | Self-host trên VPS |
|---|---|---|
| Số execution | Giới hạn theo gói, vượt là phải nâng | Không giới hạn (chỉ phụ thuộc sức VPS) |
| Dữ liệu | Nằm trên hạ tầng n8n | Nằm trong Postgres của bạn, trên VPS của bạn |
| Chi phí khi chạy nặng | Tăng theo bậc thang | Cố định theo cấu hình VPS |
| Tùy biến | Hạn chế | Toàn quyền: community nodes, biến môi trường, tích hợp sâu |
Với dân vibe coder chạy AI workflow tốn nhiều execution (mỗi webhook là một lần chạy), self-host gần như luôn rẻ và linh hoạt hơn. Đổi lại bạn phải tự lo vận hành - nhưng với Docker thì không khó như bạn nghĩ.
n8n cần VPS cỡ nào?
n8n + Postgres khá nhẹ ở mức cơ bản. Một VPS 2GB RAM chạy được cho workflow vừa phải. Nhưng nếu workflow của bạn xử lý payload lớn, gọi LLM song song, hoặc có nhiều webhook bắn cùng lúc, nên có RAM dư.
Bước 1: Chuẩn bị VPS và Docker
Khởi tạo VPS Ubuntu 22.04/24.04, SSH vào, cài Docker + Docker Compose:
sudo apt update && sudo apt upgrade -y
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
# logout/login lại để áp dụng quyền docker
docker compose version # kiểm tra compose đã có
Trỏ một domain (ví dụ n8n.tenban.com) về IP VPS qua bản ghi A. HTTPS sau này cần domain này.
Bước 2: Tạo file môi trường (.env)
Tạo thư mục và file .env. Có hai biến CỰC KỲ quan trọng:
mkdir -p ~/n8n && cd ~/n8n
nano .env
Nội dung .env:
# Domain công khai - phải khớp URL HTTPS thật
N8N_HOST=n8n.tenban.com
WEBHOOK_URL=https://n8n.tenban.com/
# Khoá mã hoá credentials - TẠO MỘT LẦN, GIỮ CỐ ĐỊNH
N8N_ENCRYPTION_KEY=thay-bang-chuoi-ngau-nhien-32-ky-tu
# Postgres
POSTGRES_USER=n8n
POSTGRES_PASSWORD=mat-khau-manh-cua-ban
POSTGRES_DB=n8n
# Timezone
GENERIC_TIMEZONE=Asia/Ho_Chi_Minh
Sinh khoá mã hoá ngẫu nhiên bằng:
openssl rand -hex 16
N8N_ENCRYPTION_KEY dùng để mã hoá toàn bộ credentials lưu trong Postgres. Đặt một lần và giữ nguyên mãi mãi. Nếu sau này bạn đổi khoá này, n8n sẽ không giải mã được credentials cũ - mọi kết nối API, LLM key bạn lưu sẽ hỏng hết. Backup file .env này cẩn thận.Bước 3: docker-compose.yml
n8n khuyên dùng PostgreSQL ngay từ đầu nếu bạn làm webhook hoặc scheduled run - SQLite mặc định chỉ hợp để nghịch thử, gặp workflow bận là gục. Tạo docker-compose.yml:
services:
postgres:
image: postgres:16-alpine
restart: always
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
n8n:
image: n8nio/n8n:latest
restart: always
ports:
- "127.0.0.1:5678:5678"
environment:
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_PORT=5432
- DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
- DB_POSTGRESDB_USER=${POSTGRES_USER}
- DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
- N8N_HOST=${N8N_HOST}
- N8N_PROTOCOL=https
- WEBHOOK_URL=${WEBHOOK_URL}
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
- GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
volumes:
- n8ndata:/home/node/.n8n
depends_on:
postgres:
condition: service_healthy
volumes:
pgdata:
n8ndata:
Vài điểm cần hiểu, không copy mù:
DB_TYPE=postgresdblà biến BẮT BUỘC để n8n dùng Postgres thay vì SQLite. Quên nó là lý do số 1 gây lỗi "connection refused".image: postgres:16-alpine- n8n hỗ trợ Postgres 13 đến 17; bản 16 cân bằng tốt giữa ổn định và LTS.ports: "127.0.0.1:5678:5678"- chỉ bind vào localhost, KHÔNG phơi port 5678 ra Internet. Reverse proxy sẽ lo phần public.- Hai volume
pgdatavàn8ndatagiữ dữ liệu Postgres và thư mục/home/node/.n8n(chứa khoá + config). Thiếu volume là mất sạch state mỗi lần restart container.
Khởi động stack:
docker compose up -d
docker compose ps # kiểm tra cả 2 container đang chạy
Bước 4: Reverse proxy + HTTPS với Caddy
n8n chạy ở 127.0.0.1:5678, giờ cần đưa ra Internet qua HTTPS. Cách gọn nhất là Caddy - nó tự xin và tự gia hạn chứng chỉ Let's Encrypt, không phải đụng tay. Cài Caddy:
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install -y caddy
Sửa /etc/caddy/Caddyfile cực kỳ gọn:
n8n.tenban.com {
reverse_proxy 127.0.0.1:5678
}
Reload Caddy:
sudo systemctl reload caddy
Caddy sẽ tự xin chứng chỉ HTTPS cho domain. Mở https://n8n.tenban.com là vào được giao diện n8n, tạo tài khoản admin đầu tiên.
Bước 5: Cắm LLM để làm AI workflow
Đây là phần thú vị nhất. n8n có sẵn node cho các LLM phổ biến - OpenAI, Google Gemini, Anthropic Claude - và cả node AI Agent / LangChain. Cách cắm:
- Vào Credentials trong n8n, thêm API key của provider bạn dùng (ví dụ OpenAI API key, Google Gemini API key, hoặc Anthropic Claude API key).
- Trong workflow, kéo node tương ứng (OpenAI Chat Model / Google Gemini / Anthropic), chọn credential vừa tạo.
- Truyền prompt vào, lấy output đẩy sang node tiếp theo.
Mọi API key này được mã hoá bằng N8N_ENCRYPTION_KEY rồi lưu trong Postgres của bạn - không gửi đi đâu khác. Đó chính là cái lợi của self-host.
Ví dụ AI workflow: Webhook -> LLM -> Action
Một pattern kinh điển, cực hữu dụng cho vibe coder:
[ Webhook node ] <- nhận POST từ hệ thống của bạn
| (ví dụ: tin nhắn khách hàng, ticket support)
v
[ LLM node (Gemini/GPT/Claude) ] <- phân loại / tóm tắt / soạn câu trả lời
|
v
[ IF / Switch node ] <- rẽ nhánh theo kết quả LLM
|
v
[ Action node ] <- gửi Telegram, ghi Google Sheet, tạo ticket...
Cụ thể: Webhook node sinh ra một URL công khai (chính là lý do WEBHOOK_URL trong .env phải đúng domain HTTPS thật, nếu không URL webhook sinh ra sẽ sai). Bạn POST dữ liệu vào URL đó, n8n đẩy qua node LLM để LLM phân loại hoặc soạn nội dung, rồi node IF rẽ nhánh, cuối cùng một action node thực thi (gửi tin, ghi DB, gọi API khác). Toàn bộ chạy tự động, không giới hạn số lần.
Hai chế độ chạy: main vs queue mode
Mặc định n8n chạy ở "main mode" - một tiến trình lo hết: giao diện, webhook, và thực thi workflow. Với cá nhân và nhóm nhỏ, đây là đủ và đơn giản nhất. Nhưng khi workflow AI của bạn chạy nặng (gọi LLM mất vài giây mỗi lần, nhiều webhook bắn cùng lúc), một tiến trình duy nhất dễ bị dồn việc, webhook mới phải chờ.
Lúc đó n8n có "queue mode": tách phần nhận webhook và phần thực thi (worker) ra riêng, dùng Redis làm hàng đợi. Bạn có thể chạy nhiều worker để xử lý song song. Đây là chuyện nâng cấp về sau, không cần ngay từ đầu - cứ bắt đầu bằng main mode, khi nào thấy nghẽn thì mới thêm Redis + worker. Đừng over-engineer ngay từ ngày một.
Một vài lỗi self-host n8n hay gặp
| Triệu chứng | Nguyên nhân thường gặp | Cách xử lý |
|---|---|---|
| "connection refused" tới DB | Quên DB_TYPE=postgresdb nên n8n vẫn cố dùng SQLite, hoặc Postgres chưa sẵn sàng |
Kiểm tra biến môi trường, dùng depends_on với healthcheck như trong compose ở trên |
| URL webhook sai (trỏ localhost) | WEBHOOK_URL không khớp domain HTTPS thật |
Đặt WEBHOOK_URL=https://domain-cua-ban/ rồi restart container |
| Mất hết credentials sau restart | Không mount volume /home/node/.n8n, hoặc đã đổi N8N_ENCRYPTION_KEY |
Giữ volume, giữ khoá mã hoá cố định, khôi phục từ backup .env |
| Vào được HTTP nhưng không HTTPS | Domain chưa trỏ A về VPS nên Caddy không xin được chứng chỉ | Kiểm tra DNS A record, chờ propagate, reload Caddy |
Vận hành: backup và update
Ba việc tối thiểu để n8n self-host sống lâu:
- Backup Postgres định kỳ:
docker compose exec postgres pg_dump -U n8n n8n > backup.sql - Backup file .env (chứa
N8N_ENCRYPTION_KEY) - mất khoá là mất hết credentials. - Update:
docker compose pull && docker compose up -dđể kéo image mới nhất.
Muốn xem cách dựng cả một hệ sinh thái tool tự động hóa AI trên VPS, đọc thêm bài VPS cho vibe coder của bọn mình.
Tổng kết
Self-host n8n cho bạn execution không giới hạn, dữ liệu của riêng mình, và toàn quyền cắm bất kỳ LLM nào. Công thức: Docker Compose (n8n + Postgres) + Caddy lo HTTPS, nhớ giữ N8N_ENCRYPTION_KEY cố định và chỉ bind n8n vào localhost. Khi đã chạy, pattern Webhook -> LLM -> Action mở ra vô số automation thực dụng. VPS 30 đủ để bắt đầu, lên VPS 50 khi workflow AI chạy nặng.
Dựng n8n self-host trong vài phút
Cloud VPS SSD TND: RAM ECC, Ceph SSD NVMe (Postgres đọc ghi mượt), khởi tạo 60 giây. VPS 30 (2 vCPU/2GB/30GB - 299k/tháng) cho n8n cá nhân, VPS 50 (4 vCPU/4GB) cho AI workflow chạy nặng. Đặt tại Việt Nam, webhook ping thấp.

