如何使用 Docker 在 VPS 自架 n8n 並設定 HTTPS
本指南教您使用 Docker Compose 與 Postgres 在 VPS 部署 n8n。解決常見的 WEBHOOK_URL 設定錯誤、加密金鑰遺失問題,並確保反向代理正確處理 HTTPS 流量,避免容器因記憶體不足遭 OOM killer 終止。
您將建置的系統
n8n 是一款工作流程自動化工具:透過視覺化編輯器,讓觸發條件(如 Webhook、排程或表單提交)啟動一連串節點,進而呼叫 API、重塑資料並寫入其他系統。由於它能與各種模型供應商及資料庫對接,且無需撰寫程式碼,因此已成為 AI 代理工作流程的標準整合工具。透過一個 docker run 指令,兩分鐘內即可取得可用的編輯器。本指南將著重於其餘 90% 的實作:將預設的 SQLite 檔案替換為 Postgres 以提升耐用性、透過 HTTPS 存取,以及最容易出錯的部分——確保 Webhook 能提供外部網路可存取的 URL。
最終架構為運行於同一個 Docker 網路中的兩個容器:n8n 本體,以及儲存工作流程與憑證的 Postgres 資料庫。主機上的反向代理負責處理 TLS termination 並將請求轉發至 localhost 的 n8n,確保所有流量皆經由代理層進入,無服務直接暴露於網際網路。此架構可與 2026 自架服務清單 中的其他服務並存。
先決條件與實際限制
您需要一台至少具備 1 GB RAM 的 VPS;若工作流程開始實際運作,請規劃 2 GB RAM,因為執行程序加上 Node.js 執行環境會消耗大量記憶體,若因記憶體不足導致容器在執行中被 OOM killer 終止,將會是極為糟糕的學習經驗。單核心 vCPU 起步即可。若此伺服器還需執行其他較吃資源的服務,請優先考量該服務的需求:照片庫通常是主要負擔,PhotoPrism 與 Immich 的實際 RAM 最低需求遠高於 n8n 的需求。媒體伺服器亦同:Jellyfin 伺服器加上如 Halcyon(將媒體庫重建成 90 年代錄影帶出租店風格) 這類瀏覽前端,在 n8n 察覺到資源壓力前,就會先佔用掉 RAM 與轉碼所需的餘裕。
您需要一個網域或子網域(例如 n8n.example.com),並設定 A 記錄指向 VPS 的公開 IP,且必須在申請憑證前完成解析。連接埠 80 與 443 必須對代理伺服器開放;n8n 本身的連接埠 5678 絕對不可直接暴露於網際網路。您需要安裝 Docker Engine 與 Compose 外掛程式;若執行 docker compose version 時出現 docker: 'compose' is not a docker command 錯誤,代表您安裝的是舊版獨立二進位檔,請改用 sudo apt install docker-compose-plugin。
測試環境可用 SQLite,正式環境請使用 Postgres
n8n 預設的資料庫是位於 /home/node/.n8n/database.sqlite 的 SQLite 檔案。若只是為了初步測試,不掛載 volume 即可使用,但容器一旦重建資料就會遺失,這本身就是一項教訓。改用 Postgres 的原因並非單純為了原始效能,而是因為 SQLite 採用單一寫入鎖定機制;當一個執行個體同時運行多個工作流程,或是您最終會用到的佇列模式(queue mode)時,在高併發下會拋出 SQLITE_BUSY: database is locked 錯誤。Postgres 沒有這類限制,且能透過 pg_dump 進行乾淨的備份,這也是 n8n 官方文件針對正式伺服器所建議的配置。若日後才要切換,必須手動遷移資料,因此若此伺服器具備重要性,請直接從 Postgres 開始部署。
DNS 與防火牆
請先設定 DNS 記錄並開啟連接埠,確保後續申請憑證時,網域名稱已能正確解析。
dig +short n8n.example.com
curl -s ifconfig.me
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow OpenSSH
sudo ufw enable請勿開啟 5678 埠。docker-compose 檔案已將 n8n 綁定至 127.0.0.1:5678,僅限主機上的反向代理可存取該服務,若設定 ufw allow 5678 將會破壞此隔離機制。
Compose 檔案
建立一個工作目錄並新增一個 docker-compose.yml。這包含了整個堆疊,包含兩個服務、一個私有網路以及兩個具名儲存卷。
services:
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: n8n
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: n8n
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- n8n_net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U n8n -d n8n"]
interval: 10s
timeout: 5s
retries: 5
n8n:
image: docker.n8n.io/n8nio/n8n:2.29.10
restart: unless-stopped
ports:
- "127.0.0.1:5678:5678"
environment:
- N8N_HOST=n8n.example.com
- N8N_PORT=5678
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://n8n.example.com/
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
- N8N_PROXY_HOPS=1
- GENERIC_TIMEZONE=Europe/London
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_PORT=5432
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- n8n_data:/home/node/.n8n
networks:
- n8n_net
depends_on:
postgres:
condition: service_healthy
volumes:
postgres_data:
n8n_data:
networks:
n8n_net:有幾項設計決策需要明確說明。DB_POSTGRESDB_HOST=postgres 是服務名稱,Docker 會在共享網路上解析此名稱,而非 localhost(在 n8n 容器內部,後者僅代表 n8n 本身)。帶有 condition: service_healthy 的 depends_on 可防止 n8n 在開機時與 Postgres 發生競態;若無此設定,n8n 會在啟動後因找不到資料庫而退出。位於 /home/node/.n8n 的具名儲存卷 n8n_data 用於存放加密金鑰,若使用 SQLite 則包含資料庫檔案,這是絕對不能遺失的目錄。請將映像檔版本鎖定在特定版本,切勿使用 latest;原因請參閱下方的升級章節。
Secrets 檔案
請勿將密碼寫入 compose 檔案。請將其放入旁邊的 .env 檔案中,讓 Compose 自動讀取,並透過產生方式確保其具備隨機性。
printf 'POSTGRES_PASSWORD=%s\n' "$(openssl rand -hex 24)" > .env
printf 'N8N_ENCRYPTION_KEY=%s\n' "$(openssl rand -hex 32)" >> .env
chmod 600 .envN8N_ENCRYPTION_KEY 是此處最重要的字串,它是加密所有儲存憑證的關鍵。請明確設定此值,而非讓 n8n 自動產生,因為您自行產生的數值可以記錄並用於還原。一旦 n8n 使用此金鑰加密了第一個憑證,變更該金鑰將導致所有憑證無法解密,因此請務必在初始設定時設定一次,之後切勿更動該行。
決定 webhook 是否正常運作的環境變數
有 4 個變數會控制 n8n 對外宣告自身的方式。這些變數設定錯誤,是 n8n 支援問題中最常見的一類。
N8N_HOST是公開主機名稱,即n8n.example.com。在 proxy 後方仍保留預設值localhost時,editor 會嘗試在您的瀏覽器中,從localhost載入自身的 API,因而失敗。N8N_PROTOCOL=https告訴 n8n 服務透過 TLS 提供,因此 n8n 會將工作階段 cookie 標記為Secure,並建立https://URL。N8N_PORT=5678是 n8n 在容器內部監聽的連接埠。這不是公開連接埠;proxy 會使用 443。WEBHOOK_URL=https://n8n.example.com/最容易造成問題。n8n 會根據這些值建立 webhook 位址,供您貼到 Stripe、GitHub 或其他外部呼叫端。若未設定或設定錯誤,n8n 會退回使用N8N_HOST:N8N_PORT,並提供https://n8n.example.com:5678/webhook/...,甚至更糟的http://localhost:5678/webhook/...。這些位址會在沒有錯誤訊息的情況下顯示,看似合理,卻無法從網際網路連線,因此呼叫端的請求會悄悄地無法送達。請將它設定為完全正確的公開基底 URL,並保留結尾的斜線。接著確認 webhook node 顯示的 URL 不含連接埠。
N8N_PROXY_HOPS=1 會告知 n8n 的 Express server 信任前方的 1 個 proxy。因此,速率限制及任何讀取 client IP 的功能,都會取得實際位址,而不是 proxy 的位址。此處刻意不設定的變數是 N8N_RUNNERS_ENABLED。Task runners 會在獨立於主要程序的程序中執行 n8n 的 Code-node 邏輯。在 1.x 系列中,這項功能一直採選擇性啟用,變數預設為 false,必須將其設定為 true。n8n 2.0 預設啟用 Task runners,因此在 2.x 系列中,本指南固定的這個變數已淘汰,n8n 文件也表示不再需要設定它。
首次啟動
docker compose up -d
docker compose ps
docker compose logs -f n8n健康的首次啟動應以 Editor is now accessible via: 行結束,且上方應有 n8n ready on ..., port 5678 行。docker compose ps 應顯示兩個容器 Up,並將 postgres 標記為 (healthy)。若 n8n 陷入 Restarting 迴圈,請閱讀日誌;這通常是資料庫連線或下文所述的儲存卷權限問題。
使用反向代理配置 TLS
n8n 本身透過 5678 埠使用純 HTTP 協定;需在前端配置 HTTPS 終止。以下提供兩種簡潔的選擇。
若您已運行多個容器,可將 n8n 部署在 自動簽發 TLS 憑證的 Traefik 反向代理 後方。只需設定幾個標籤(labels),Traefik 就會自動為您申請並續期憑證。
若此伺服器僅運行此應用程式,使用 nginx 虛擬主機搭配 Let's Encrypt 憑證會更簡單。請參考 適用於 Ubuntu 24.04 的 Certbot 與 nginx TLS 設定 來取得憑證,接著使用以下 server 區塊設定:
server {
listen 443 ssl;
server_name n8n.example.com;
ssl_certificate /etc/letsencrypt/live/n8n.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/n8n.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:5678;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600;
client_max_body_size 16m;
}
}Upgrade 與 Connection "upgrade" 標頭為必要設定。n8n 透過 WebSocket 將即時執行更新推送到編輯器,若缺少這兩行,登入頁面載入後會因連線中斷而卡住。X-Forwarded-Proto $scheme 是 N8N_PROXY_HOPS=1 的配套設定:它會告知 n8n 原始請求為 HTTPS,即使代理伺服器是透過純 HTTP 與其連線;此舉可避免 n8n 誤判連線不安全而拒絕其自身的 Cookie。proxy_read_timeout 3600 則能防止長時間執行的任務被 nginx 預設的 60 秒逾時限制所中斷。
讓第一個工作流程真正運作
開啟 https://n8n.example.com/,建立擁有者帳戶(下一節會說明),並建立能驗證流程可正常運作的最小工作流程:接收 webhook、發出 HTTP 呼叫,再傳回回應。
- 新增 Webhook 節點。將方法設為
POST,路徑可設為hello。畫面會顯示 2 個 URL:Test URL 和 Production URL。這正是許多「webhook 無法運作」問題的來源。Test URL 只會回應 1 次,而且只有在你按下 Listen for test event 時才有效;之後便會失效。你發布工作流程後,Production URL 才會註冊,之後即可持續回應。 - 在它後方新增 HTTP Request 節點,將其指向任何公開的 JSON API。對
https://api.github.com/zen發出 GET 請求會回傳單行字串,已足以驗證流程。 - 新增 Respond to Webhook 節點,並將 Webhook 節點的 Respond 選項設為「Using Respond to Webhook node」,讓呼叫端取得 HTTP 節點的輸出。
- 按一下右上角的 Publish,然後呼叫
curl -X POST https://n8n.example.com/webhook/hello。你應會收到 zen 字串。整個流程是接收 POST、呼叫 API,再傳回回應,也是大多數實際自動化流程的基本結構。較舊的指南會要求你在這裡切換 Active 開關;n8n 2.0 已使用 Publish 和 Unpublish 取代該開關。
排程版本會以 Schedule Trigger 取代 Webhook 節點,改為呼叫模型端點。從 在同一台 VPS 上執行 Ollama 這類自架服務取得模型,是建立每日夜間摘要器的簡潔方式。
使用者管理,而非基本驗證
舊版的 n8n 指南會要求您設定 N8N_BASIC_AUTH_ACTIVE=true。這些變數已在 n8n 1.0 版本中移除,目前已無作用。現今的驗證機制採用擁有者帳號 (owner account):當您首次載入編輯器時,n8n 會要求您建立一組電子郵件與密碼作為擁有者,此步驟為強制性,不再提供匿名模式。請在首次啟動後立即建立帳號,再將 URL 提供給他人:在 docker compose up 與完成首次表單提交之間,任何存取該實例的人皆可將其據為己有。在反向代理層加裝基本驗證 (basic auth) 是合理的額外防護,但這僅屬於第二層驗證,並非真正的身分驗證機制。本指南中的擁有者帳號與其他功能皆適用於免費的社群版;若您日後需要具備細部權限的角色管理或 SSO 功能,建議在規劃前先閱讀 哪些 n8n 功能需要付費授權。
備份:先處理加密金鑰,再處理資料庫
有兩項資料需要備份,且兩者的可替代性並不相同。
N8N_ENCRYPTION_KEY。您在 n8n 中儲存的每一項憑證、API 權杖、資料庫密碼與 OAuth 密鑰,皆以此金鑰進行靜態加密(encryption at rest)。若沒有這把金鑰,Postgres 中的工作流程將毫無用處:若將資料庫還原至使用不同金鑰的新伺服器,n8n 將無法解密任何憑證,且無法復原或重設。您的 .env 檔案存放著此金鑰;請在建立金鑰當天,將其複製到伺服器以外的地方,存入密碼管理員是理想的做法。這是最關鍵的備份。
Postgres 資料庫,用於存放工作流程、執行紀錄以及加密後的憑證本身:
docker compose exec -T postgres pg_dump -U n8n -d n8n \
| gzip > n8n-db-$(date +%F).sql.gz請排程執行上述指令,並將備份檔複製到伺服器外。若要在全新的 VPS 上還原:請先啟動一次 stack 以建立資料庫,接著停止 n8n,使用 psql 載入備份檔,將相同的 N8N_ENCRYPTION_KEY 放入 .env,最後啟動 n8n。擁有相同的金鑰加上備份檔,即可運作實例;若使用新金鑰,則工作流程將無法使用任何憑證。
升級:固定標籤
compose 檔案刻意固定使用 n8nio/n8n:2.29.10,而不是 latest。n8n 多數週都會發布新的次要版本,版本之間偶爾也會變更資料庫結構或節點行為,因此使用 latest 表示未經人員介入的 pull 可能會取得一個在啟動時立即遷移資料庫的版本。請固定版本,升級前閱讀發行說明。n8n 會在其中列出重大變更,並以明確步驟執行升級:
docker compose exec -T postgres pg_dump -U n8n -d n8n | gzip > pre-upgrade.sql.gz
# edit the image tag in docker-compose.yml, then:
docker compose pull n8n
docker compose up -d n8n
docker compose logs -f n8n跨主要版本升級時,這點尤其重要。例如 2.0 系列預設將 N8N_BLOCK_ENV_ACCESS_IN_NODE 改為 true,因此任何讀取 process.env 的 Code 節點都會在未顯示警告的情況下失去存取權,直到將其改回 false 為止;同一版本也開始對設定檔強制執行嚴格權限,預設啟用 task runners,並將 workflow 的 Activate 切換鈕替換為 Publish。跨越主要版本邊界前,請閱讀2.0 重大變更頁面。n8n 會在啟動時自動執行必要的資料庫遷移,這正是升級前的 pg_dump 不可省略的原因。由於認證資料使用 .env 中的金鑰加密,且資料儲存在 Postgres,容器可以直接替換:升級時替換容器,回復時固定使用先前的標籤並還原 dump。
常見失效情況與畫面中的訊息
The requested webhook "POST hello" is not registered. 呼叫尚未發布工作流程的 webhook 時,或在沒有人監聽時呼叫測試路徑,會收到 404。測試路徑(/webhook-test/...)只有在你按下「Listen for test event」時才會回應;正式路徑(/webhook/...)則只有在工作流程發布後才會回應。相鄰的 This webhook is not registered for GET requests. Did you mean to make a POST request? 表示方法錯誤:節點要求 POST,但你傳送了 GET。
webhook URL 顯示 :5678 或 localhost。 節點會顯示 https://n8n.example.com:5678/webhook/... 或 http://localhost:5678/...。WEBHOOK_URL 未設定或設定錯誤,因此 n8n 使用 N8N_HOST:N8N_PORT 建立位址,而不是使用你的公開基底 URL。設定 WEBHOOK_URL=https://n8n.example.com/,使用 docker compose up -d 重建容器,該埠號就會消失。
瀏覽器中出現 There was a problem loading init data。編輯器已載入,但無法連線至自己的後端 API。若位於代理伺服器後方,幾乎總是 N8N_HOST 或 WEBHOOK_URL 設定錯誤、代理伺服器缺少 WebSocket Upgrade 標頭,或 N8N_PROTOCOL 與你的連線方式不一致。確認對外公開的 4 個變數,以及代理伺服器是否轉送 Upgrade 和 Connection。
日誌中出現 password authentication failed for user "n8n",且容器持續重新啟動。n8n 傳送的密碼與資料庫初始化時使用的密碼不一致。注意:Postgres 只會在初始化空白資料目錄時讀取 POSTGRES_PASSWORD。先啟動堆疊一次,再在 .env 中變更 POSTGRES_PASSWORD,但現有的 postgres_data volume 仍保留舊密碼。將設定改回原始密碼;或者,如果不需要保留任何資料,則 docker compose down 並 docker volume rm postgres volume,然後重新啟動。
啟動時出現 EACCES: permission denied, open '/home/node/.n8n/config'。n8n 以 node 使用者(UID 1000)執行,無法寫入其設定目錄。若將主機資料夾繫結掛載(./n8n_data:/home/node/.n8n),且該資料夾由 root 擁有,就容易發生此問題。使用上方所示的 named volume;若必須使用 bind mount,請先執行 sudo chown -R 1000:1000 ./n8n_data。
Permissions 0644 for n8n settings file /home/node/.n8n/config are too wide. Changing permissions to 0600.. 從 2.x 系列開始,n8n 預設會在該設定檔上強制套用 0600,並在啟動時自動修正。這行日誌表示 n8n 已完成修正,通常發生於 bind mount,或還原作業將權限過於寬鬆的檔案複製回來之後。不需要採取任何行動;只有在檔案系統確實無法支援權限設定時,才設定 N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=false。
Mismatching encryption keys,完整訊息會指出設定檔中的加密金鑰 /home/node/.n8n/config 與環境中的 N8N_ENCRYPTION_KEY 不一致。環境中的金鑰與 n8n 先前執行時寫入資料 volume 的金鑰不同,最常見的原因是變數未設定時,n8n 在早期啟動過程中產生了隨機金鑰,之後你又設定了另一個金鑰。將原始金鑰放回 .env;或者,只有在確定不需要保留任何已儲存的認證資料時,才刪除 n8n_data volume 內的 config 檔案,讓 n8n 重新產生金鑰,但現有認證資料將無法讀取。
登入橫幅顯示安全 cookie 警告: Your n8n server is configured to use a secure cookie, however you are either visiting this via an insecure URL, or using Safari. 你已設定 N8N_PROTOCOL=https,但透過未加密的 HTTP 存取 n8n,通常是直接連線至 IP 和埠號,而不是透過 HTTPS 代理伺服器。請透過 https://n8n.example.com/ 存取。只有在確實無法使用 HTTPS 時,才應設定 N8N_SECURE_COOKIE=false,而且絕對不要在對外提供網際網路服務的主機上使用。
若要在這些工作流程中加入語言模型,請參閱 使用 Claude 和 n8n 建立 AI 工作流程。
FAQ
n8n 應使用 SQLite 還是 Postgres?
SQLite(預設值)適合用來試用 n8n,也適合一次只執行一個工作流程的個人環境。凡是依賴 n8n 的環境,都應改用 Postgres:SQLite 的單一寫入鎖在並行執行時會產生 database is locked,而 Postgres 可使用 pg_dump 乾淨地備份。之後再遷移需要手動處理,因此只要這台伺服器重要,就應一開始使用 Postgres。
為什麼 n8n 的 webhook 從不觸發?
幾乎總是因為 WEBHOOK_URL。如果未設定或設定錯誤,n8n 會根據 N8N_HOST:N8N_PORT 產生 webhook 位址。這些位址通常包含 :5678 或 localhost,看似有效,實際上卻無法從網際網路存取,因此呼叫端的請求永遠無法抵達。設定 WEBHOOK_URL=https://n8n.example.com/,並確認節點顯示的 URL 不含連接埠。第二個原因是呼叫尚未發布之工作流程的正式 URL,該 URL 會回傳 The requested webhook ... is not registered.
n8n 必須備份哪些內容?
有兩項。第一項是 .env 檔案中的 N8N_ENCRYPTION_KEY,因為所有已儲存的憑證都使用它加密。遺失後便永久無法解密,因此建立當天就應將它複製到伺服器以外的位置。第二項是 Postgres 資料庫的 pg_dump,其中包含工作流程、執行記錄與憑證。還原時兩者都需要:相同的金鑰與該傾印檔。
如何讓 n8n 使用 HTTPS?
n8n 會在 port 5678 上提供純 HTTP;前方的反向代理負責 TLS termination。將 n8n 綁定至 127.0.0.1:5678,讓只有代理可以連線,接著使用 Traefik 自動申請憑證,或使用搭配 Let's Encrypt 憑證的 nginx。設定 N8N_PROTOCOL=https 與 WEBHOOK_URL=https://your-host/,並確認代理轉送 WebSocket 的 Upgrade 標頭,否則編輯器會無法正常載入。
如何安全地升級 n8n?
指定特定的映像標籤,不要使用 latest。先建立 pg_dump,因為 n8n 啟動時會自動執行資料庫遷移。接著閱讀版本發布說明,確認是否有不相容變更,再更新標籤並執行 docker compose pull n8n && docker compose up -d n8n。容器可直接捨棄,因此只要指定先前的標籤,並還原升級前的傾印檔,即可回復。