Nginx 502 Bad Gateway khi deploy Node.js: Checklist kiểm tra từ ngoài vào trong
Cách tìm nguyên nhân Nginx 502 khi deploy Next.js, NestJS hoặc Node.js bằng curl, ss, systemctl, PM2 và Docker Compose.
Bài viết bởi Phạm Minh Thiện
Front-end Developer, trực tiếp thực hành Next.js, NestJS, Docker, Nginx và VPS Ubuntu.
Bài viết đã được kiểm tra lại ngày 19/7/2026.
Mục lục bài viết
502 Bad Gateway xuất hiện khi Nginx đã nhận được request từ trình duyệt nhưng không lấy được phản hồi hợp lệ từ dịch vụ phía sau, còn gọi là upstream. Với dự án Node.js, upstream thường là Next.js, NestJS, Express, PM2 hoặc một container Docker.
Cách sửa nhanh nhất không phải thay cấu hình ngẫu nhiên, mà là kiểm tra theo đường đi của request:
Trình duyệt → DNS/HTTPS → Nginx → cổng ứng dụng → tiến trình Node.js → database
Bài này tập trung vào phần từ Nginx đến ứng dụng. Output trong ảnh là demo lab; domain, PID và tên container của bạn sẽ khác.

1. Đọc log trước khi sửa
Mở hai terminal. Terminal thứ nhất theo dõi error log:
sudo tail -f /var/log/nginx/error.log
Terminal thứ hai gửi request:
curl -I https://example.com
Một lỗi phổ biến:
connect() failed (111: Connection refused) while connecting to upstream,
upstream: "http://127.0.0.1:3000/"
Thông báo này cho biết Nginx thử kết nối tới 127.0.0.1:3000, nhưng không có dịch vụ chấp nhận kết nối tại đó. Đây thường là lỗi ứng dụng chưa chạy, chạy sai cổng hoặc Nginx trỏ sai địa chỉ.
Nếu log báo upstream timed out, dịch vụ có thể đang chạy nhưng phản hồi quá chậm, bị treo hoặc chờ database.
2. Kiểm tra cấu hình và trạng thái Nginx
Chạy:
sudo nginx -t
sudo systemctl status nginx --no-pager
Kết quả mong muốn:
syntax is ok
test is successful
Active: active (running)
Nếu nginx -t lỗi, sửa cú pháp trước rồi mới reload:
sudo systemctl reload nginx
Không nên dùng restart ngay cho mọi thay đổi. reload cho phép Nginx nạp cấu hình mới mà ít gián đoạn hơn, nhưng chỉ dùng sau khi nginx -t thành công.
3. Bỏ qua Nginx và gọi thẳng upstream
Đây là bước quan trọng nhất:
curl -v http://127.0.0.1:3000/
Có ba kết quả chính.
Trường hợp A: Connection refused
curl: (7) Failed to connect to 127.0.0.1 port 3000
Không có tiến trình lắng nghe tại cổng đó. Kiểm tra:
sudo ss -lntp | grep ':3000'
Trường hợp B: Có HTTP response
HTTP/1.1 200 OK
Ứng dụng đang phản hồi. Khi đó kiểm tra lại proxy_pass, server block, HTTPS hoặc đường dẫn request.
Trường hợp C: Kết nối được nhưng timeout/lỗi 500
Ứng dụng có chạy nhưng gặp lỗi nội bộ. Xem log Node.js, PM2 hoặc container thay vì tiếp tục chỉnh Nginx.

4. Kiểm tra ứng dụng chạy bằng PM2
pm2 status
pm2 logs --lines 100
Nếu app ở trạng thái errored, xem log và khởi động lại sau khi sửa:
pm2 restart ecosystem.config.js
Kiểm tra cổng app thực tế:
pm2 env 0 | grep -E 'PORT|NODE_ENV'
Một lỗi hay gặp là .env đặt PORT=3001 nhưng Nginx vẫn proxy đến 3000.
Với Next.js production, cần build trước khi start:
npm run build
npm run start -- -p 3000
Nếu thiếu build output, ứng dụng sẽ không khởi động đúng và Nginx chỉ nhìn thấy upstream bị từ chối.
5. Kiểm tra ứng dụng chạy bằng Docker Compose
docker compose ps
docker compose logs --tail=150 web
Xem cổng publish:
docker compose port web 3000
Ví dụ Compose:
services:
web:
image: deployeasy-web:1.0
ports:
- "127.0.0.1:3001:3000"
Trong trường hợp này:
- Ứng dụng trong container nghe cổng
3000. - Host VPS nhận request tại
127.0.0.1:3001. - Nginx trên host phải dùng
proxy_pass http://127.0.0.1:3001;.
Nếu bạn viết proxy_pass http://127.0.0.1:3000;, Nginx đang gọi nhầm cổng host.
Nginx cũng chạy trong Docker
Nếu Nginx và app ở cùng Compose network, Nginx nên gọi tên service và cổng container:
location / {
proxy_pass http://web:3000;
}
Trong container Nginx, 127.0.0.1 chính là container Nginx, không phải container web.
6. Cấu hình reverse proxy mẫu
server {
listen 80;
server_name example.com www.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;
}
}
Sau khi sửa:
sudo nginx -t && sudo systemctl reload nginx
Nếu ứng dụng dùng WebSocket, bổ sung header upgrade phù hợp. Không nên thêm hàng loạt directive từ một cấu hình trên mạng khi ứng dụng không cần chúng.
7. App phải lắng nghe đúng interface
Trong container, ứng dụng chỉ bind vào 127.0.0.1 có thể khiến port publish không truy cập được từ bên ngoài container. Thông thường app trong container nên lắng nghe 0.0.0.0.
NestJS:
await app.listen(process.env.PORT ?? 3000, '0.0.0.0');
Express:
app.listen(3000, '0.0.0.0', () => {
console.log('Server listening on port 3000');
});
Trên host chạy PM2, bind 127.0.0.1 vẫn có thể phù hợp khi chỉ muốn Nginx truy cập nội bộ. Điều quan trọng là địa chỉ bind phải phù hợp với vị trí của Nginx.
8. Khi nào firewall liên quan?
Nếu Nginx và ứng dụng cùng nằm trên một VPS và Nginx gọi 127.0.0.1, UFW thường không phải nguyên nhân chính. Đừng mở cổng 3000 ra Internet chỉ để thử.
Nếu upstream nằm trên máy khác, hãy kiểm tra route, firewall và security group giữa hai máy:
nc -vz 10.0.0.12 3000
curl -v http://10.0.0.12:3000/health
Nên giới hạn cổng upstream chỉ cho IP của reverse proxy thay vì mở toàn bộ Internet.
9. Kiểm tra database nếu app khởi động rồi crash
Nginx có thể báo 502 vì Node.js thoát sau khi không kết nối được PostgreSQL. Xem log app:
docker compose logs --tail=200 api
Các dấu hiệu:
password authentication failed
ECONNREFUSED 127.0.0.1:5432
getaddrinfo ENOTFOUND db
Sửa kết nối database ở ứng dụng, không phải ở Nginx. Sau đó xác nhận app trả response trực tiếp trước khi kiểm tra domain.
10. Quy trình kiểm tra 5 phút
sudo nginx -t
sudo systemctl status nginx --no-pager
sudo tail -n 50 /var/log/nginx/error.log
curl -v http://127.0.0.1:3000/
sudo ss -lntp | grep ':3000'
pm2 status
docker compose ps
docker compose logs --tail=100 web
Bạn không cần chạy cả PM2 lẫn Docker nếu dự án chỉ dùng một phương án. Chọn lệnh đúng với kiến trúc thực tế.
11. Checklist xác minh đã sửa
-
nginx -tthành công. - Nginx ở trạng thái running.
-
curltrực tiếp upstream trả HTTP response. - Cổng trong
proxy_passkhớp với cổng host hoặc service Docker. - Ứng dụng không crash trong log.
- Domain trả
200,301hoặc response mong muốn. - HTTPS và HTTP đều chuyển hướng đúng.
12. Câu hỏi thường gặp
Vì sao Nginx chạy nhưng website vẫn 502?
Nginx chỉ là cổng vào. Service Nginx có thể hoạt động bình thường trong khi upstream Node.js đã dừng hoặc chạy sai cổng.
Có nên mở port 3000 bằng UFW?
Không cần nếu Nginx gọi app qua localhost trên cùng VPS. Giữ port ứng dụng nội bộ thường an toàn hơn.
Reload Nginx khác restart thế nào?
Reload nạp cấu hình mới sau khi kiểm tra cú pháp, còn restart dừng và khởi động lại service. Với thay đổi cấu hình thông thường, ưu tiên nginx -t rồi reload.
Nguồn tham khảo
Đọc tiếp
Bài viết liên quan
Nginx Reverse Proxy là gì? Cấu hình Node.js trên VPS
Giải thích Nginx reverse proxy và cách cấu hình proxy_pass cho Node.js, Next.js, NestJS; hỗ trợ HTTPS, WebSocket, upload và nhiều domain.
Đọc bài →Cài SSL HTTPS miễn phí bằng Certbot và Nginx trên Ubuntu
Hướng dẫn cài SSL HTTPS miễn phí bằng Certbot và Nginx trên Ubuntu, kiểm tra DNS, mở port, tự động gia hạn và sửa lỗi ACME challenge.
Đọc bài →Docker bị Killed hoặc Exit Code 137 trên VPS: Cách xác định đúng nguyên nhân
Hướng dẫn kiểm tra Docker Exit Code 137, OOMKilled, RAM, swap và cách tối ưu quá trình build trên VPS cấu hình thấp.
Đọc bài →