如何在 VPS 使用 Docker 部署 n8n 並設定 HTTPS
本指南教您使用 Docker Compose 搭配 Postgres 部署 n8n,並解決設定 WEBHOOK_URL 與 encryption-key 時常見的錯誤。透過反向代理實現 HTTPS 連線,避免因 SQLite 單一寫入鎖定導致的系統不穩定,確保 AI Agent 工作流在 VPS 上穩定運行。
建立目標
n8n 是一款工作流自動化工具:透過視覺化編輯器,由觸發器(例如 webhook、排程或表單提交)啟動一系列節點,進而呼叫 API、轉換資料並寫入其他系統。由於 n8n 能直接與各種模型供應商及資料庫溝通,無需自行撰寫服務,因此已成為 AI Agent 工作流的首選整合工具。只需 docker run 即可在兩分鐘內完成編輯器設定。本指南將著重於其餘 90% 的進階配置:將預設的 SQLite 檔案改為使用 Postgres 以提升穩定性、透過 HTTPS 進行連線,以及最關鍵的一步——確保 webhook 能提供外部網路可存取的 URL。
完成後的架構包含同一個 Docker network 下的兩個容器:n8n 本體,以及儲存工作流與憑證的 Postgres 資料庫。主機上的反向代理(reverse proxy)負責終止 TLS 並將請求轉發至 localhost 的 n8n,因此除了該代理伺服器外,沒有任何服務直接暴露於網際網路。此方案與 2026 self-hosting shortlist 中的其他服務並列。
前置作業與限制說明
您需要一台至少 1 GB RAM 的 VPS;當工作流程進入實際運作階段時,建議規劃 2 GB,因為執行程序與 Node.js 執行環境會消耗大量記憶體,若容器在執行中被 OOM killer 終止,學習過程會非常挫折。初期使用單核 vCPU 即可。
您需要一個網域或子網域(例如 n8n.example.com),且必須設定 A record 指向 VPS 的公用 IP,並確保在申請憑證前已完成解析。必須對代理伺服器開放 80 與 443 埠;n8n 的 5678 埠不應直接對外開放。您需要安裝 Docker Engine 與 Compose plugin;若執行 docker compose version 時出現 docker: 'compose' is not a docker command 錯誤,代表您使用的是舊版獨立 binary,而 plugin 為 sudo apt install docker-compose-plugin。
SQLite is fine for a test, Postgres for anything you rely on
n8n's default database is a SQLite file at /home/node/.n8n/database.sqlite. For kicking the tyres it is fine — mount no volume and you lose it on the first container recreate, which is its own lesson. The reason to move to Postgres is not raw speed; it is that SQLite holds a single writer lock, so an instance running several workflows at once, or the queue mode you will eventually want, throws SQLITE_BUSY: database is locked under concurrency. Postgres has none of that ceiling, backs up cleanly with pg_dump, and is what n8n's own docs assume for a server you depend on. Switching later means migrating data by hand, so if this box matters, start on 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。由於 compose 檔案將 n8n 綁定至 127.0.0.1:5678,因此僅能透過主機的 reverse proxy 存取,若開啟 ufw allow 5678 會破壞此隔離機制。
Compose 檔案
建立一個工作目錄與一個 docker-compose.yml。此檔案定義了整個堆疊:包含兩個服務、一個私有網路與兩個具名磁碟卷 (named volumes)。
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 是 service name,Docker 會透過共用網路解析此名稱;而非 localhost,在 n8n 容器內,該名稱代表 n8n 本身。使用 condition: service_healthy 的 depends_on 可避免 n8n 在啟動時與 Postgres 發生競爭狀況;若缺少此設定,n8n 會因找不到資料庫而結束執行。位於 /home/node/.n8n 的具名磁碟卷 n8n_data 用於儲存加密金鑰,若使用 SQLite 則會儲存資料庫——這是絕對不能遺失的目錄。請將 image 固定在特定版本,切勿使用 latest;原因詳見下方的升級章節。
The secrets file
切勿將密碼寫在 compose file 中。請將密碼存放在旁邊的 .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 是否運作的環境變數
有四個變數控制 n8n 向外部呈現的身分,設定錯誤是 n8n 最常見的支援問題。
N8N_HOST是公開主機名稱,即n8n.example.com。若在代理伺服器後方將其保持為預設值localhost,編輯器會嘗試從您的瀏覽器載入其自身的 API 位址localhost,導致載入失敗。N8N_PROTOCOL=https告知 n8n 服務正透過 TLS 執行,因此它會標記 Session CookieSecure並建構https://URL。N8N_PORT=5678是 n8n 在容器內部監聽的連接埠。這不是公開連接埠;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/...—— 這些位址看起來很合理且不會報錯,但從網際網路無法連線,導致外部請求無法抵達。請將其設定為包含結尾斜線的完整公開 Base URL,並確認 Webhook 節點顯示的 URL 不含連接埠。
N8N_PROXY_HOPS=1 告知 n8n 的 Express 伺服器信任前端的單一代理伺服器,如此一來,流量限制(rate-limiting)與任何讀取用戶端 IP 的功能都能取得真實位址而非代理伺服器的位址。這裡有一個您刻意不設定的變數:N8N_RUNNERS_ENABLED。自 1.69 版本起,Task runners(在獨立沙盒程序中執行 Code-node 邏輯)已成為預設設定,且在本指南指定的 2.x 版本中為強制設定,因此舊有的選用(opt-in)方式已廢棄。若現在設定它,n8n 只會記錄一條通知,要求您將其移除。
First start
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 迴圈,請檢查日誌 — 通常是下文提到的資料庫連線或 volume 權限問題。
使用反向代理進行 TLS
n8n 本身在 5678 埠使用 plain HTTP;前端需配置元件來終止 HTTPS 連線。有兩種常見方案。
若您已運行多個 container,請在 n8n 前方部署 使用 Traefik reverse proxy 並自動核發 TLS 憑證,並加上少量 labels —— Traefik 會自動為您請求並更新憑證。
若此主機僅運行此應用程式,使用搭配 Let's Encrypt 憑證的 nginx virtual host 會更簡單。請參考 Ubuntu 24.04 的 Certbot 與 nginx TLS 設定 來取得憑證,接著配置此 server block:
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" header 為必要設定。n8n 透過 WebSocket 將執行狀態即時傳送至編輯器,若缺少這兩行,登入頁面載入後會因連線遺失而卡住。proxy_read_timeout 3600 可防止執行中的工作因 nginx 預設的 60 秒逾時而中斷。X-Forwarded-Proto $scheme header 與 N8N_PROXY_HOPS=1 配合使用:它告知 n8n 原始請求為 HTTPS,即便 proxy 是透過 plain HTTP 連線,如此 n8n 才不會判定連線不安全並拒絕其 cookie。
實作第一個工作流
開啟 https://n8n.example.com/,建立擁有者帳戶(詳見下一節),並建立一個最簡單的工作流來驗證路徑是否正確:接收 Webhook、發送 HTTP 請求,最後回傳回應。
- 新增一個 Webhook 節點。將 method 設定為
POST,並將 path 設定為如hello。系統會顯示兩個 URL:Test URL 與 Production URL —— 這也是導致半數「我的 Webhook 無法運作」回報的原因。Test URL 僅在點擊 Listen for test event 時,且僅能處理一次請求,隨後即會失效。Production URL 則在工作流處於 Active 狀態時持續運作。 - 在其後方新增一個 HTTP Request 節點,指向任何公開的 JSON API —— 例如對
https://api.github.com/zen發送 GET 請求並回傳單行字串,這已足夠測試。 - 新增一個 Respond to Webhook 節點,並將 Webhook 節點的 Respond 選項設定為 "Using Respond to Webhook node",以便將 HTTP 節點的輸出回傳給呼叫端。
- 開啟右上角的 Active 開關並呼叫該 URL:
curl -X POST https://n8n.example.com/webhook/hello。你應該會收到該行字串 —— 流程為:接收 POST、發送 API 請求、回傳回應,這正是大多數實際自動化流程的核心結構。
排程版本的變體是將 Webhook 節點替換為 Schedule Trigger,並改為呼叫模型端點 —— 使用 在同一台 VPS 上運行的 Ollama 是建立每日摘要功能的簡便方法。
使用者管理,而非 basic auth
舊版的 n8n 指南建議設定 N8N_BASIC_AUTH_ACTIVE=true。這些變數已在 n8n 1.0 版本中移除,目前已無作用。現在的身份驗證是以 owner account 為核心:當您首次載入編輯器時,n8n 會要求您建立一個包含電子郵件與密碼的 owner 帳戶,且此步驟是強制性的 — 不支援匿名模式。請在首次啟動後立即建立該帳戶,並在將 URL 提供給他人之前完成:在 docker compose up 與首次提交表單之間,任何優先存取該實例的人都能取得控制權。在上方額外建立一個 reverse-proxy basic-auth 層是合理的額外防護,但這僅是第二因素驗證,而非真正的身份驗證。
Backups: 先備份加密金鑰,再備份資料庫
備份包含兩項內容,且兩者的重要性並不相同。
N8N_ENCRYPTION_KEY。您在 n8n 中儲存的所有憑證(例如 API tokens、資料庫密碼、OAuth secrets)在靜態儲存時皆使用此金鑰進行加密。若缺少此金鑰,Postgres 中的工作流將無法使用:若將資料庫還原至使用不同金鑰的新主機,n8n 將無法解密任何憑證,且無法進行復原或重設。您的 .env 檔案即為金鑰;請在建立金鑰的當天,將其複製到伺服器以外的安全位置(建議存放在密碼管理員中)。這才是真正關鍵的備份。
Postgres 資料庫,用於備份工作流、執行紀錄以及加密後的憑證本身:
docker compose exec -T postgres pg_dump -U n8n -d n8n \
| gzip > n8n-db-$(date +%F).sql.gz請設定排程執行此指令,並將 dump 檔複製到主機之外。若要在新的 VPS 上進行還原:請先啟動一次 stack 以建立資料庫,停止 n8n,使用 psql 將 dump 檔載入,將相同的 N8N_ENCRYPTION_KEY 放入 .env,然後啟動 n8n。使用相同的金鑰搭配 dump 檔即可還原運作中的實例;若使用新金鑰,則工作流將無法使用任何憑證。
Upgrades: pin the tag
compose 檔案刻意使用 n8nio/n8n:2.29.10 而非 latest。n8n 每週都會發布新的 minor 版本,且版本間偶爾會變更資料庫 schema 或 node 行為,因此使用 latest 可能導致自動 pull 的版本在啟動時直接進行資料庫遷移。請固定版本,並在升級前閱讀 release notes(n8n 會在其中說明 breaking changes),並進行有計畫的升級:
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大版本跳躍(Major-version jumps)是此操作最關鍵的時刻。例如,2.0 版本預設將 N8N_BLOCK_ENV_ACCESS_IN_NODE 改為 true,因此任何讀取 process.env 的 Code node 會在未手動將其設回 false 的情況下,靜默失去存取權限;同一個版本也開始對 settings file 強制執行嚴格權限。在跨越 major 版本界限前,請閱讀 2.0 breaking-changes page。n8n 會在啟動時自動執行必要的資料庫遷移,這正是升級前執行 pg_dump 為必要步驟的原因。由於憑證(credentials)是使用 .env 中的金鑰進行加密儲存,且資料儲存在 Postgres 中,因此容器是可棄用的(disposable):你可以透過替換容器來進行升級,並透過固定前一個 tag 與還原 dump 來進行回滾(roll back)。
Failure modes, with the strings you will see
The requested webhook "POST hello" is not registered. 呼叫工作流未處於 Active 狀態的 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 而非您的公開基礎位址來建構位址。設定 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 與您的連線方式不符。請確認四個對外公開的變數,並確認代理伺服器有轉發 Upgrade 與 Connection。
日誌出現 password authentication failed for user "n8n" 且容器不斷重啟。 n8n 發送的密碼與資料庫初始化時的密碼不符。陷阱在於:Postgres 僅在初始化空白資料目錄時才會讀取 POSTGRES_PASSWORD。請先啟動一次 stack,接著修改 .env 中的 POSTGRES_PASSWORD,此時現有的 postgres_data volume 仍保留舊密碼。請將其改回原始值;若您不需要保留資料,請刪除並重新建立 (docker compose down and docker volume rm) postgres volume,然後重新啟動。
啟動時出現 EACCES: permission denied, open '/home/node/.n8n/config'。 n8n 以 node 使用者 (UID 1000) 執行,無法寫入其設定目錄。這常發生在將 root 擁有的主機資料夾 (./n8n_data:/home/node/.n8n) 進行 bind-mount 時。請使用上述的 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 並在啟動時自行修正 — 此日誌行表示它已修正權限模式,通常發生在 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 但卻透過 plain HTTP 連線至 n8n — 通常是因為直接存取 IP 與連接埠,而非透過 HTTPS 代理。請透過 https://n8n.example.com/ 連線。僅在您確實無法使用 HTTPS 時才設定 N8N_SECURE_COOKIE=false,且絕不要在暴露於網際網路的機器上這樣做。
若要在工作流中使用語言模型,請參閱 使用 Claude 與 n8n 建立 AI 工作流。
FAQ
n8n 應該使用 SQLite 還是 Postgres?
若僅用於測試 n8n,或個人使用且每次僅執行一個 workflow,使用 SQLite (預設值) 即可。若用於正式環境,請改用 Postgres:SQLite 的單一寫入鎖 (single writer lock) 在高併發時會導致 database is locked,而 Postgres 可透過 pg_dump 進行完整備份。由於後續遷移需手動操作,若該伺服器至關重要,請直接從 Postgres 開始。
為什麼我的 n8n webhooks 從未觸發?
幾乎都是因為 WEBHOOK_URL。若未設定或設定錯誤,n8n 會根據 N8N_HOST:N8N_PORT 產生 webhook 位址,其中常包含 :5678 或 localhost;這些位址看似有效,但從網際網路無法連線,導致請求無法送達。請設定 WEBHOOK_URL=https://n8n.example.com/ 並確認節點顯示的 URL 不含 port。另一個原因是呼叫了未開啟 Active 狀態的 workflow,這會回傳 The requested webhook ... is not registered.。
n8n 必須備份哪些內容?
兩項內容。一是來自 .env 檔案的 N8N_ENCRYPTION_KEY,因為所有儲存的憑證皆使用該檔案加密,遺失將導致無法解密——請在建立當天即將其複製到伺服器之外。二是 Postgres 資料庫的 pg_dump,用於備份 workflow、歷史紀錄與憑證。還原時需要兩者:相同的 key 以及 dump 檔案。
如何為 n8n 配置 HTTPS?
n8n 在 port 5678 提供 plain HTTP 服務;請在前端使用 reverse proxy 來終止 TLS。將 n8n 綁定至 127.0.0.1:5678 以確保僅 proxy 可存取,接著使用具備自動憑證功能的 Traefik,或使用帶有 Let's Encrypt 憑證的 nginx。請設定 N8N_PROTOCOL=https 與 WEBHOOK_URL=https://your-host/,並確保 proxy 有轉發 WebSocket Upgrade headers,否則編輯器會當機。
如何安全地升級 n8n?
請指定特定的 image tag 而非使用 latest;升級前請先進行 pg_dump,因為 n8n 會在啟動時自動執行 migrations;閱讀 release notes 以確認是否有 breaking changes,接著更新 tag 並執行 docker compose pull n8n && docker compose up -d n8n。容器本身是可捨棄的,若需回滾,請改用原先的 tag 並還原升級前的 dump 檔案。