Reverse proxy PM2 + Nginx: kết nối ứng dụng Node.js với cổng 80
Trên máy chủ Ubuntu 24.04, chúng tôi dựng cấu hình reverse proxy Node.js PM2 Nginx: PM2 lo việc chạy ứng dụng, còn Nginx chuyển các yêu cầu đến cổng 80 sang ứng dụng. Chúng tôi cũng đã kiểm tra rằng PM2 chạy lại ứng dụng ngay cả khi tiến trình ứng dụng bị buộc dừng hoặc máy chủ khởi động lại.
Các hộp kết quả bên dưới là đầu ra của lệnh chạy bằng người dùng thường deploy. Ở chỗ tên miền dùng giá trị ví dụ example.com, và yêu cầu được gửi từ bên trong máy chủ kèm header Host.
1️⃣ Cài đặt Node.js
Node.js được cài bằng cách đăng ký kho NodeSource vào apt, và chúng tôi chọn Node.js 26, bản LTS tính đến tháng 11 năm 2026.
🔲 NodeSource setup_26.x
export DEBIAN_FRONTEND=noninteractiveNếu bạn đặt giá trị này trước, cửa sổ hỏi cài đặt sẽ không hiện ra trong lúc cài gói. Tùy chọn -E của lệnh sudo bên dưới chuyển giá trị này sang cả lệnh chạy bằng quyền 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 -yKhi script đăng ký kho xong, bạn có thể cài Node.js bằng apt như mọi gói Ubuntu khác.
🔲 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 đi kèm trong gói nodejs nên không cần cài riêng.
2️⃣ Ứng dụng thử Node.js
Để xem PM2 và Nginx đã kết nối đúng chưa, hãy tạo một ứng dụng thử trả về một dòng chữ khi nhận yêu cầu.
🔲 app.js
mkdir -p ~/helloTạo thư mục hello trong thư mục home của bạn và viết tệp app.js bên trong.
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Ứng dụng chỉ nhận yêu cầu ở cổng 3000 của 127.0.0.1 nên không mở trực tiếp được từ bên ngoài máy chủ. Chúng ta sẽ cài để Nginx nhận yêu cầu từ bên ngoài và chuyển sang cổng này.
3️⃣ Chạy ứng dụng bằng PM2 và tự khởi động
PM2 là công cụ quản lý tiến trình, chạy ứng dụng Node.js ở chế độ nền và chạy lại ứng dụng khi tiến trình dừng. Trong phần này chúng ta cũng cài đặt tự khởi động, giúp ứng dụng chạy lại khi máy chủ boot.
🔲 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.2Khi chạy lệnh pm2 lần đầu, daemon PM2 cũng khởi động theo, và PM2 đã cài là phiên bản 7.1.2.
🔲 pm2 start
cd ~/helloChuyển đến thư mục có app.js rồi chạy ứng dụng bằng 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 là tên ứng dụng hiện trong danh sách PM2, và các lệnh sau sẽ chỉ định ứng dụng bằng tên này.
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 ở cột status nghĩa là ứng dụng đang chạy, còn cột ↺ là số lần PM2 đã khởi động lại ứng dụng.
🔲 curl 127.0.0.1:3000
Khi bạn gửi yêu cầu từ bên trong máy chủ, dòng chữ ứng dụng trả về sẽ hiện ra nguyên như vậy.
curl -sS http://127.0.0.1:3000/hello from node🔲 pm2 restart
Sau khi sửa mã ứng dụng, hãy khởi động lại ứng dụng để áp dụng thay đổi.
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/sSau khi khởi động lại, pid đổi từ 17257 sang 17306 và cột ↺ tăng lên 1.
🔲 kill -9
Tình huống tiến trình ứng dụng dừng đột ngột có thể tạo bằng kill -9, và pm2 pid in ra số tiến trình của ứng dụng.
kill -9 "$(pm2 pid hello)"Kiểm tra lại danh sách PM2 sau 3 giây.
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 đã chạy lại ứng dụng với pid mới 17797 ở trạng thái online, và cột ↺ đã tăng lên 2.
🔲 pm2 save
pm2 save[PM2] Saving current process list...
[PM2] Successfully saved in /home/deploy/.pm2/dump.pm2pm2 save lưu danh sách ứng dụng đang chạy vào tệp dump.pm2, và khi boot PM2 chạy lại các ứng dụng theo danh sách này.
🔲 pm2 startup
Để PM2 chạy khi boot, hãy đăng ký PM2 làm dịch vụ systemd. Lệnh bên dưới tạo dịch vụ với tên tài khoản hiện tại và thư mục home của nó.
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 là phần giúp pm2 chạy bằng quyền root tìm được đường dẫn node của tài khoản hiện tại, còn -u và --hp là tài khoản và thư mục home mà dịch vụ dùng để chạy. Dòng ExecStart trong tệp dịch vụ là pm2 resurrect, khôi phục danh sách ứng dụng đã lưu khi boot.
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 nghĩa là dịch vụ chạy khi boot. pm2 startup chỉ đăng ký dịch vụ mà không chạy ngay, nên hiện inactive (dead), và dịch vụ sẽ chạy từ lần boot tiếp theo.
🔲 pm2 list sau khi khởi động lại
sudo systemctl rebootKhoảng 2 phút sau, kết nối lại bằng deploy và kiểm tra trạng thái ứng dụng.
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/sLúc máy chủ bật được 2 phút, ứng dụng đã online được 108 giây: PM2 đã chạy lại ứng dụng cùng lúc với quá trình boot.
4️⃣ Cài đặt reverse proxy Nginx
Vì ứng dụng chỉ nhận yêu cầu ở 127.0.0.1:3000, yêu cầu đến cổng 80 sẽ do Nginx nhận rồi chuyển sang ứng dụng. Cấu hình như vậy, khi máy chủ phía trước nhận yêu cầu rồi chuyển tiếp đến máy chủ phía sau, được gọi là reverse proxy.
🔲 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 trong kho của Ubuntu 24.04 là phiên bản 1.24.0, và khi cài sẽ được đăng ký chạy khi boot luôn.
🔲 /etc/nginx/sites-available/hello
Tạo tệp /etc/nginx/sites-available/hello và đưa nội dung bên dưới vào.
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 là địa chỉ ứng dụng mà yêu cầu được chuyển đến. Các header Host, X-Real-IP, X-Forwarded-For và X-Forwarded-Proto là những giá trị được chuyển đi để ứng dụng biết tên miền của yêu cầu gốc, IP của người truy cập và yêu cầu là http hay https.
proxy_http_version 1.1 cùng các header Upgrade và Connection cần có khi chuyển kết nối WebSocket đến tận ứng dụng. Header Upgrade và Connection không được chuyển tới ứng dụng sau khi qua proxy, nên phải đặt trực tiếp trong cấu hình.
sudo ln -sf /etc/nginx/sites-available/hello /etc/nginx/sites-enabled/helloNginx chỉ đọc những cấu hình có liên kết trong thư mục 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 nginxHãy kiểm tra cú pháp cấu hình bằng nginx -t trước, nếu đạt thì áp dụng cấu hình mới bằng reload mà không cần dừng Nginx.
🔲 Yêu cầu curl kèm header 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: 16Sau khi đọc lại cấu hình, yêu cầu gửi kèm header Host đã nhận được phản hồi của ứng dụng. Dòng Server trong header phản hồi là nginx, và Content-Length 16 bằng kích thước dòng hello from node mà ứng dụng trả về (16 byte tính cả ký tự xuống dòng). Muốn kết nối từ bên ngoài máy chủ thì cổng 80 phải được mở trên tường lửa.
5️⃣ Áp dụng HTTPS bằng Certbot
Với plugin Nginx của Certbot, bạn có thể cấp chứng chỉ và sửa cấu hình Nginx trong một lần. Hãy chạy lệnh bên dưới khi tên miền đã trỏ về IP của máy chủ.
sudo certbot --nginxCách cài Certbot và thử gia hạn tự động có trong bài viết bên dưới; lệnh cấp chứng chỉ trong bài đó dành cho Apache, nên chỉ thay bước cấp chứng chỉ bằng lệnh ở trên.
🔢 FAQ & nội dung đề xuất
Log dịch vụ
Lệnh journalctl: cách dùng, log theo dịch vụ và dọn dung lượng journal
Bài viết về cách đọc log của các dịch vụ systemd như nginx và pm2-deploy theo từng dịch vụ.
ℹ️ Thông báo tiếp thị liên kết
Nội dung trên trang này có chứa liên kết affiliate (tiếp thị liên kết). Khi khách truy cập mua sản phẩm hoặc dịch vụ qua các liên kết này, trang sẽ nhận hoa hồng từ bên bán. Trong quá trình đó, số tiền người mua phải trả (sẽ giảm khi có chương trình khuyến mãi ↓) không hề tăng lên. Giá, khuyến mãi và tình trạng hàng được đăng theo thời điểm viết bài và có thể khác với thực tế, vì vậy hãy kiểm tra lại lần cuối với bên bán trước khi mua. Việc chọn và đánh giá sản phẩm dựa trên tiêu chí riêng của trang; việc có nhận hoa hồng hay không không ảnh hưởng đến thứ tự giới thiệu hay nội dung đánh giá.