PM2 + Nginx รีเวิร์สพร็อกซี: เชื่อมแอป Node.js เข้ากับพอร์ต 80
เราติดตั้งโครงสร้างรีเวิร์สพร็อกซี Node.js PM2 Nginx บนเซิร์ฟเวอร์ Ubuntu 24.04 โดยให้ PM2 ดูแลการรันแอป และให้ Nginx ส่งคำขอที่เข้ามาทางพอร์ต 80 ต่อไปยังแอป และยังตรวจสอบแล้วว่าแม้จะบังคับปิดโปรเซสของแอปหรือรีบูตเซิร์ฟเวอร์ PM2 ก็รันแอปขึ้นมาใหม่ได้
กล่องผลลัพธ์ด้านล่างเป็นเอาต์พุตจากคำสั่งที่รันด้วยผู้ใช้ทั่วไป deploy ตรงตำแหน่งโดเมนใช้ค่าตัวอย่าง example.com และส่งคำขอจากภายในเซิร์ฟเวอร์โดยใส่เฮดเดอร์ Host
1️⃣ ติดตั้ง Node.js
Node.js ติดตั้งโดยลงทะเบียนรีโพสิทอรีของ NodeSource ไว้ใน apt และเราเลือก Node.js 26 ซึ่งเป็นเวอร์ชัน LTS ณ เดือนพฤศจิกายน 2026
🔲 NodeSource setup_26.x
export DEBIAN_FRONTEND=noninteractiveถ้าคุณกำหนดค่านี้ไว้ก่อน หน้าต่างที่ถามการตั้งค่าจะไม่ขึ้นระหว่างติดตั้งแพ็กเกจ ตัวเลือก -E ของคำสั่ง sudo ด้านล่างจะส่งค่านี้ต่อไปยังคำสั่งที่รันด้วยสิทธิ์ 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เมื่อสคริปต์ลงทะเบียนรีโพสิทอรีเสร็จ คุณก็ติดตั้ง Node.js ด้วย apt ได้เหมือนแพ็กเกจของ Ubuntu
🔲 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แอปรับคำขอเฉพาะที่พอร์ต 3000 ของ 127.0.0.1 จึงเปิดจากภายนอกเซิร์ฟเวอร์โดยตรงไม่ได้ เราจะตั้งให้ 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/sonline ในคอลัมน์ status หมายถึงแอปกำลังทำงาน และคอลัมน์ ↺ คือจำนวนครั้งที่ 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)"ตรวจรายการของ PM2 อีกครั้งหลังผ่านไป 3 วินาที
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 เป็นส่วนที่ทำให้ pm2 ที่รันด้วยสิทธิ์ root หาพาธของ 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)Nginx ในรีโพสิทอรีของ Ubuntu 24.04 คือเวอร์ชัน 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
🔲 คำขอ curl ที่ใส่เฮดเดอร์ Host
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️⃣ ใช้ HTTPS ด้วย Certbot
ถ้าใช้ปลั๊กอิน Nginx ของ Certbot คุณจะออกใบรับรองและแก้การตั้งค่า Nginx ได้ในครั้งเดียว ให้รันคำสั่งด้านล่างเมื่อโดเมนชี้มาที่ IP ของเซิร์ฟเวอร์แล้ว
sudo certbot --nginxการติดตั้ง Certbot และการทดสอบต่ออายุอัตโนมัติดูได้ในบทความด้านล่าง คำสั่งออกใบรับรองในบทความนั้นเป็นแบบ Apache จึงเปลี่ยนเฉพาะขั้นตอนออกใบรับรองเป็นคำสั่งด้านบน
🔢 FAQ & เนื้อหาแนะนำ
log ของบริการ
คำสั่ง journalctl: วิธีใช้ ล็อกแยกตามบริการ และการจัดการขนาด journal
บทความที่อธิบายวิธีอ่าน log ของบริการ systemd เช่น nginx และ pm2-deploy แยกตามบริการ
ℹ️ ประกาศเรื่องลิงก์แอฟฟิลิเอต
เนื้อหาของเว็บไซต์นี้มีลิงก์แอฟฟิลิเอต หากผู้เข้าชมซื้อสินค้าหรือบริการผ่านลิงก์เหล่านี้ เว็บไซต์นี้จะได้รับค่าคอมมิชชันจากผู้ขาย ในกระบวนการนี้ ราคาที่ผู้ซื้อจ่าย(ราคาจะลดลงเมื่อมีโปรโมชัน↓)จะไม่สูงขึ้นแต่อย่างใด ราคา ส่วนลด และข้อมูลสต็อกที่แสดงเป็นข้อมูล ณ เวลาที่เขียนและอาจต่างจากความเป็นจริง โปรดตรวจสอบกับผู้ขายอีกครั้งก่อนซื้อ การเลือกและการประเมินสินค้าเขียนตามเกณฑ์ของเราเอง และการได้รับค่าคอมมิชชันหรือไม่ไม่มีผลต่อลำดับการแนะนำหรือเนื้อหาการประเมิน