PM2 + Nginx 反向代理:把 Node.js 應用程式接到 80 埠
我們在 Ubuntu 24.04 伺服器上建立 Node.js PM2 Nginx 反向代理架構,由 PM2 負責執行應用程式,並讓 Nginx 把送到 80 埠的請求轉給應用程式。也確認了即使強制結束應用程式的處理程序或重新開機,PM2 都會再次啟動應用程式。
下方的結果框是以一般使用者 deploy 執行的輸出。網域處放的是範例值 example.com,並在伺服器內加上 Host 標頭送出請求。
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 nodejsSetting 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 -vv26.13.0npm -v11.20.0npm 已包含在 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 pm2added 45 packages in 5spm2 -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/sstatus 欄的 online 表示應用程式正在執行,↺ 欄則是 PM2 重新啟動應用程式的次數。
🔲 curl 127.0.0.1:3000
在伺服器內送出請求,應用程式回傳的那一行文字就會原樣出現。
curl -sS http://127.0.0.1:3000/hello from node🔲 pm2 restart
修改應用程式程式碼後,重新啟動應用程式讓修改生效。
pm2 restart helloUse --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/spm2 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/sPM2 已用新的 pid 17797 再次啟動應用程式並處於 online 狀態,↺ 欄升到 2。
🔲 pm2 save
pm2 save[PM2] Saving current process list...
[PM2] Successfully saved in /home/deploy/.pm2/dump.pm2pm2 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 -pup 2 minutespm2 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 nginxSetting 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 -vnginx 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/helloNginx 只讀取在 sites-enabled 資料夾中建立了連結的設定。
🔲 nginx -t
sudo nginx -tnginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successfulsudo systemctl reload nginx先用 nginx -t 檢查設定語法,通過後再用 reload 在不停止 Nginx 的情況下套用新設定。
🔲 加上 Host 標頭的 curl 請求
curl -sS -i -H 'Host: example.com' http://127.0.0.1/ | head -n 4HTTP/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 埠。
5️⃣ 用 Certbot 套用 HTTPS
使用 Certbot 的 Nginx 外掛,就能一次完成憑證簽發與 Nginx 設定修改。請在網域已指向伺服器 IP 的狀態下執行下方指令。
sudo certbot --nginxCertbot 的安裝與自動續期測試可在下方文章中確認;該文章的簽發指令是以 Apache 為準,所以只把簽發步驟換成上方指令執行。
🔢 FAQ & 推薦內容
ℹ️ 聯盟行銷說明
本站內容包含聯盟行銷連結。訪客透過這些連結購買商品或服務時,本站會從賣家獲得佣金。在這個過程中,購買者支付的金額(遇到活動折扣時金額會降低↓)並不會因此提高。刊登的價格、折扣與庫存資訊以撰寫當時為準,可能與實際情況不同,購買前請向賣家做最後確認。商品的挑選與評價依本站自訂的標準撰寫,是否支付佣金不會影響介紹順序或評價內容。