PM2 + Nginx 反向代理:把 Node.js 應用程式接到 80 埠

測試環境Debian 系列Ubuntu 24.04.5 LTS一般使用者帳號
已確認 Node.js 應用程式運作與反向代理設定

我們在 Ubuntu 24.04 伺服器上建立 Node.js PM2 Nginx 反向代理架構,由 PM2 負責執行應用程式,並讓 Nginx 把送到 80 埠的請求轉給應用程式。也確認了即使強制結束應用程式的處理程序或重新開機,PM2 都會再次啟動應用程式。

下方的結果框是以一般使用者 deploy 執行的輸出。網域處放的是範例值 example.com,並在伺服器內加上 Host 標頭送出請求。

🎯 重點
🗂️ 請求流程
📂 Nginx反向代理
📄 CertbotHTTPS 憑證
📂 PM2應用程式執行與自動啟動
📄 Node.js 應用程式
用 PM2 執行應用程式並在開機時自動啟動
Nginx 把 80 埠的請求轉給應用程式
前提條件
伺服器已完成初始設定的 Ubuntu 24.04sudo 權限的一般使用者帳號套用 HTTPS 時需要網域

1️⃣ 安裝 Node.js

Node.js 是把 NodeSource 套件庫加入 apt 後安裝,版本選用 2026 年 11 月時的 LTS 版 Node.js 26。

🔲 NodeSource setup_26.x

export DEBIAN_FRONTEND=noninteractive

先設定這個值,安裝套件時就不會跳出詢問設定的視窗。下方 sudo 指令的 -E 選項會把這個值也原樣傳給以 root 權限執行的指令。

curl -fsSL https://deb.nodesource.com/setup_26.x | sudo -E bash -
2026-11-07 16:08:16 - Repository configured successfully.
2026-11-07 16:08:16 - To install Node.js, run: apt install nodejs -y

指令碼加好套件庫後,就能像其他 Ubuntu 套件一樣用 apt 安裝 Node.js。

🔲 apt-get install nodejs

sudo -E apt-get install -y nodejs
Setting up libatomic1:amd64 (14.2.0-4ubuntu2~24.04.1) ...
Setting up nodejs (26.13.0-1nodesource1) ...
Processing triggers for libc-bin (2.39-0ubuntu8.9) ...
Processing triggers for man-db (2.12.0-4build2) ...

🔲 node -v, npm -v

node -v
v26.13.0
npm -v
11.20.0

npm 已包含在 nodejs 套件中,不必另外安裝。

2️⃣ Node.js 測試應用程式

為了確認 PM2 和 Nginx 是否正確連接,先建立一個收到請求就回傳一行文字的測試應用程式。

🔲 app.js

mkdir -p ~/hello

在家目錄下建立 hello 資料夾,並在裡面撰寫 app.js 檔案。

cat > ~/hello/app.js <<'APP'
const http = require('http');
http.createServer((req, res) => {
  console.log(req.method, req.url);
  res.end('hello from node\n');
}).listen(3000, '127.0.0.1');
APP

應用程式只在 127.0.0.1 的 3000 埠接收請求,所以無法從伺服器外部直接開啟。外部的請求會設定由 Nginx 接收後轉到這個埠。

3️⃣ 用 PM2 執行應用程式與自動啟動

PM2 是在背景執行 Node.js 應用程式、並在應用程式處理程序結束時再次啟動它的處理程序管理工具。伺服器開機時讓應用程式重新啟動的自動啟動,也在這一章設定。

🔲 npm install -g pm2

sudo npm install -g pm2
added 45 packages in 5s
pm2 -v
[PM2] Spawning PM2 daemon with pm2_home=/home/deploy/.pm2
[PM2] PM2 Successfully daemonized
7.1.2

第一次執行 pm2 指令時,PM2 常駐程式也會一起啟動,安裝的 PM2 版本是 7.1.2。

🔲 pm2 start

cd ~/hello

移到 app.js 所在的資料夾後,用 PM2 執行應用程式。

pm2 start app.js --name hello
[PM2] Starting /home/deploy/hello/app.js in fork_mode (1 instance)
[PM2] Done.
┌────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name     │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
├────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0  │ hello    │ default     │ N/A     │ fork    │ 17257    │ 0s     │ 0    │ online    │ 0%       │ 26.3mb   │ deploy   │ disabled │
└────┴──────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘

--name 是顯示在 PM2 清單上的應用程式名稱,之後的指令都用這個名稱指定應用程式。

pm2 list
┌────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name     │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
├────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0  │ hello    │ default     │ N/A     │ fork    │ 17257    │ 2s     │ 0    │ online    │ 0%       │ 58.5mb   │ deploy   │ disabled │
└────┴──────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
host metrics | cpu: 0% | ram usage: 38.8% | disk: ⇓ 0mb/s ⇑ 0mb/s

status 欄的 online 表示應用程式正在執行,↺ 欄則是 PM2 重新啟動應用程式的次數。

🔲 curl 127.0.0.1:3000

在伺服器內送出請求,應用程式回傳的那一行文字就會原樣出現。

curl -sS http://127.0.0.1:3000/
hello from node

🔲 pm2 restart

修改應用程式程式碼後,重新啟動應用程式讓修改生效。

pm2 restart hello
Use --update-env to update environment variables
[PM2] Applying action restartProcessId on app [hello](ids: [ 0 ])
[PM2] [hello](0) ✓
┌────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name     │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
├────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0  │ hello    │ default     │ N/A     │ fork    │ 17306    │ 0s     │ 1    │ online    │ 0%       │ 19.3mb   │ deploy   │ disabled │
└────┴──────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
host metrics | cpu: 0% | ram usage: 38.8% | disk: ⇓ 0mb/s ⇑ 0mb/s
pm2 list
┌────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name     │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
├────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0  │ hello    │ default     │ N/A     │ fork    │ 17306    │ 2s     │ 1    │ online    │ 0%       │ 58.3mb   │ deploy   │ disabled │
└────┴──────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
host metrics | cpu: 0% | ram usage: 38.8% | disk: ⇓ 0mb/s ⇑ 0mb/s

重新啟動後,pid 從 17257 變成 17306,↺ 欄升到 1。

🔲 kill -9

應用程式處理程序突然結束的情況可以用 kill -9 製造,pm2 pid 會輸出應用程式的處理程序編號。

kill -9 "$(pm2 pid hello)"

3 秒後再檢查一次 PM2 清單。

pm2 list
┌────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name     │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
├────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0  │ hello    │ default     │ N/A     │ fork    │ 17797    │ 3s     │ 2    │ online    │ 0%       │ 58.6mb   │ deploy   │ disabled │
└────┴──────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
host metrics | cpu: 51.2% | ram usage: 35.8% | eth0: ⇓ 1kb/s ⇑ 1kb/s | eth1: ⇓ 19kb/s ⇑ 0mb/s | disk: ⇓ 728kb/s ⇑ 3.1mb/s

PM2 已用新的 pid 17797 再次啟動應用程式並處於 online 狀態,↺ 欄升到 2。

🔲 pm2 save

pm2 save
[PM2] Saving current process list...
[PM2] Successfully saved in /home/deploy/.pm2/dump.pm2

pm2 save 會把目前執行中的應用程式清單存進 dump.pm2 檔案,開機時 PM2 會照這份清單再次啟動應用程式。

🔲 pm2 startup

要讓 PM2 在開機時執行,就把 PM2 註冊成 systemd 服務。下方指令會帶入目前的帳號名稱和家目錄建立服務。

sudo env PATH="$PATH" pm2 startup systemd -u "$(id -un)" --hp "$HOME"
ExecStart=/usr/lib/node_modules/pm2/bin/pm2 resurrect
ExecReload=/usr/lib/node_modules/pm2/bin/pm2 reload all
ExecStop=/usr/lib/node_modules/pm2/bin/pm2 kill

[Install]
WantedBy=multi-user.target

Target path
/etc/systemd/system/pm2-deploy.service
Command list
[ 'systemctl enable pm2-deploy' ]
[PM2] Writing init configuration in /etc/systemd/system/pm2-deploy.service
[PM2] Making script booting at startup...
[PM2] [-] Executing: systemctl enable pm2-deploy...
Created symlink /etc/systemd/system/multi-user.target.wants/pm2-deploy.service → /etc/systemd/system/pm2-deploy.service.
[PM2] [v] Command successfully executed.

env PATH 是讓以 root 權限執行的 pm2 找到目前帳號 node 路徑的部分,-u 和 --hp 則是執行服務的帳號與家目錄。服務檔案的 ExecStart 行是 pm2 resurrect,會在開機時還原儲存的應用程式清單。

systemctl status pm2-deploy --no-pager | head -n 5
○ pm2-deploy.service - PM2 process manager
     Loaded: loaded (/etc/systemd/system/pm2-deploy.service; enabled; preset: enabled)
     Active: inactive (dead)
       Docs: https://pm2.keymetrics.io/

enabled 表示服務會在開機時執行。pm2 startup 只註冊服務而不會立刻執行,所以顯示 inactive (dead),服務會從下次開機開始執行。

🔲 重新開機後的 pm2 list

sudo systemctl reboot

約 2 分鐘後,以 deploy 重新連線並檢查應用程式狀態。

uptime -p
up 2 minutes
pm2 list
┌────┬──────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name     │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
├────┼──────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0  │ hello    │ default     │ N/A     │ fork    │ 976      │ 108s   │ 0    │ online    │ 0%       │ 63.0mb   │ deploy   │ disabled │
└────┴──────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
host metrics | cpu: 0.3% | ram usage: 35.1% | disk: ⇓ 4kb/s ⇑ 4kb/s

伺服器開機 2 分鐘時,應用程式已經 online 了 108 秒,表示 PM2 隨著開機再次啟動了應用程式。

4️⃣ Nginx 反向代理設定

應用程式只在 127.0.0.1:3000 接收請求,所以送到 80 埠的請求由 Nginx 接收後轉給應用程式。像這樣由前面的伺服器接收請求、再轉送給後面伺服器的架構,就叫做反向代理。

🔲 apt-get install nginx

sudo -E apt-get install -y nginx
Setting up nginx-common (1.24.0-2ubuntu7.19) ...
Created symlink /etc/systemd/system/multi-user.target.wants/nginx.service → /usr/lib/systemd/system/nginx.service.
Setting up nginx (1.24.0-2ubuntu7.19) ...
nginx -v
nginx version: nginx/1.24.0 (Ubuntu)

Ubuntu 24.04 套件庫的 Nginx 是 1.24.0,安裝時就會註冊為開機時執行。

🔲 /etc/nginx/sites-available/hello

建立 /etc/nginx/sites-available/hello 檔案,並放入下方內容。

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

proxy_pass 是要轉送請求的應用程式位址。Host、X-Real-IP、X-Forwarded-For、X-Forwarded-Proto 標頭是讓應用程式得知原始請求的網域、訪客 IP,以及是 http 還是 https 而傳遞的值。

proxy_http_version 1.1 與 Upgrade、Connection 標頭,是把 WebSocket 連線一路傳到應用程式時需要的設定。Upgrade 和 Connection 標頭經過代理後不會再傳給應用程式,所以要在設定中直接加上。

sudo ln -sf /etc/nginx/sites-available/hello /etc/nginx/sites-enabled/hello

Nginx 只讀取在 sites-enabled 資料夾中建立了連結的設定。

🔲 nginx -t

sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
sudo systemctl reload nginx

先用 nginx -t 檢查設定語法,通過後再用 reload 在不停止 Nginx 的情況下套用新設定。

🔲 加上 Host 標頭的 curl 請求

curl -sS -i -H 'Host: example.com' http://127.0.0.1/ | head -n 4
HTTP/1.1 200 OK
Server: nginx/1.24.0 (Ubuntu)
Date: Sat, 07 Nov 2026 08:09:38 GMT
Content-Length: 16

重新讀取設定後,加上 Host 標頭送出的請求收到了應用程式的回應。回應標頭的 Server 行是 nginx,Content-Length 16 與應用程式回傳的 hello from node 這一行(含換行共 16 位元組)大小相同。要從伺服器外部連線,防火牆必須開放 80 埠。

🧭 Node.js 應用程式的請求與重新啟動
說明圖解
🧭 情境
請求流程
🌐瀏覽器example.com 請求
➜
🧱Nginx80 埠,proxy_pass
➜
🟢Node.js 應用程式127.0.0.1:3000
處理程序管理
⚙️systemdpm2-deploy 服務
➜
🔁PM2dump.pm2 應用程式清單
➜
🟢Node.js 應用程式hello
瀏覽器的請求由 80 埠的 Nginx 接收,轉給 proxy_pass 中寫的 127.0.0.1:3000,再把應用程式的回應原樣傳回。
應用程式處理程序結束時,PM2 會立刻再次啟動同一個應用程式。用 kill -9 結束後 3 秒查看 pm2 list,應用程式已用新的 pid 處於 online 狀態。
伺服器開機時,systemd 會執行 pm2-deploy 服務,PM2 再照 pm2 save 儲存的清單再次啟動應用程式。

5️⃣ 用 Certbot 套用 HTTPS

使用 Certbot 的 Nginx 外掛,就能一次完成憑證簽發與 Nginx 設定修改。請在網域已指向伺服器 IP 的狀態下執行下方指令。

sudo certbot --nginx

Certbot 的安裝與自動續期測試可在下方文章中確認;該文章的簽發指令是以 Apache 為準,所以只把簽發步驟換成上方指令執行。

🔢 FAQ & 推薦內容

Nginx 無法連到 proxy_pass 中寫的應用程式位址時,會回傳 502 Bad Gateway。用 pm2 stop hello 停掉應用程式後送出同樣的請求,結果是 502,/var/log/nginx/error.log 中留下了 [connect() failed (111: Connection refused) while connecting to upstream] 這一行,以及嘗試連線的位址 http://127.0.0.1:3000/。請先在 pm2 list 確認應用程式是否 online,以及應用程式接收請求的埠是否與 proxy_pass 位址相同。重新啟動應用程式後,同樣的請求回傳了 200。

PM2 會把應用程式的輸出,依應用程式名稱存成檔案,放在執行它的帳號家目錄下的 .pm2/logs 資料夾。執行 pm2 logs hello --nostream --lines 5 後,出現了 /home/deploy/.pm2/logs/hello-out.log 和 hello-error.log 兩個檔案的最後幾行,out 檔案中有應用程式記錄的 GET / 請求。不加 --nostream 執行時,會持續輸出新增的日誌。

自動啟動服務用 pm2 unstartup systemd 刪除。新增或刪除應用程式後,要再執行一次 pm2 save,開機時才會照更改後的清單執行應用程式。執行 pm2 startup 時,輸出最後也一併提示了這兩個指令。

ℹ️ 聯盟行銷說明
本站內容包含聯盟行銷連結。訪客透過這些連結購買商品或服務時,本站會從賣家獲得佣金。在這個過程中,購買者支付的金額(遇到活動折扣時金額會降低↓)並不會因此提高。刊登的價格、折扣與庫存資訊以撰寫當時為準,可能與實際情況不同,購買前請向賣家做最後確認。商品的挑選與評價依本站自訂的標準撰寫,是否支付佣金不會影響介紹順序或評價內容。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

Prove your humanity: 4   +   3   =