DeployEasy
Menu
NginxTrung bình

Sửa lỗi Nginx 502 Bad Gateway khi deploy Node.js và Docker

Quy trình tìm nguyên nhân Nginx 502 Bad Gateway: upstream chưa chạy, sai port, bind interface, socket, Docker network, timeout và log.

· 9 phút đọc · 1.773 từ
Mục lục bài viết

502 Bad Gateway là một trong những lỗi production phổ biến khi Nginx đứng trước Node.js, PM2 hoặc Docker.

Thông báo này thường không có nghĩa Nginx bị hỏng. Nó thường cho biết Nginx không nhận được phản hồi hợp lệ từ upstream.

Kiến trúc

Client

Nginx

Upstream app

Nginx trả 502 khi có vấn đề giữa:

Nginx ↔ Upstream

Ví dụ:

  • App chưa chạy.
  • Sai port.
  • App crash.
  • Connection refused.
  • Upstream đóng kết nối.
  • Socket permission.
  • DNS/container name sai.
  • Protocol HTTP/HTTPS sai.
  • Response header không hợp lệ.

Bước 1: Xác định server block

Xem toàn bộ config:

sudo nginx -T

Tìm domain:

sudo nginx -T 2>/dev/null |
grep -n "api.deployeasy.vn"

Kiểm tra:

server_name api.deployeasy.vn;

và:

proxy_pass http://127.0.0.1:3001;

Đảm bảo request đang vào đúng server block.

Bước 2: Xem Nginx error log

sudo tail -n 100 \
  /var/log/nginx/error.log

Nếu có log riêng:

sudo tail -n 100 \
  /var/log/nginx/deployeasy-api.error.log

Realtime:

sudo tail -f /var/log/nginx/error.log

Các thông báo quan trọng:

connect() failed (111: Connection refused)
upstream timed out
no live upstreams
permission denied
upstream prematurely closed connection
host not found in upstream

Log thường chỉ thẳng nhóm nguyên nhân.

Connection refused

Ví dụ:

connect() failed (111: Connection refused)
while connecting to upstream

Nginx đã tới IP/port nhưng không có process lắng nghe.

Kiểm tra:

sudo ss -ltnp | grep :3001

Test trực tiếp:

curl -v http://127.0.0.1:3001

Nếu curl cũng fail, sửa app trước, không sửa Nginx.

App chưa chạy

PM2:

pm2 list
pm2 logs deployeasy-api --lines 200

systemd:

sudo systemctl status deployeasy-api
sudo journalctl -u deployeasy-api -n 200

Docker:

docker compose ps
docker compose logs --tail=200 api

Sai port

Nginx:

proxy_pass http://127.0.0.1:3001;

Nhưng app log:

Listening on port 3000

Sửa một trong hai và giữ tài liệu rõ ràng.

Kiểm tra:

sudo ss -ltnp

App bind sai interface

App trên host

Nếu Nginx cùng host, bind:

127.0.0.1:3001

là phù hợp.

App trong container

App bên trong container thường cần bind:

0.0.0.0:3000

Nếu bind 127.0.0.1 trong container, Docker port mapping có thể không truy cập được.

NestJS:

await app.listen(3000, "0.0.0.0");

Host chỉ publish:

ports:
  - "127.0.0.1:3001:3000"

Nginx proxy:

proxy_pass http://127.0.0.1:3001;

Docker container không publish port

Compose:

services:
  api:
    ports:
      - "127.0.0.1:3001:3000"

Kiểm tra:

docker compose ps
docker port CONTAINER_NAME

Nếu Nginx chạy trên host, nó không tự truy cập được container-only port nếu không có network/port phù hợp.

Nginx cũng chạy trong Docker

Nếu Nginx và API cùng Compose network:

proxy_pass http://api:3000;

Không dùng:

proxy_pass http://127.0.0.1:3000;

vì localhost trong Nginx container là chính Nginx container.

Compose:

services:
  nginx:
    networks:
      - app

  api:
    networks:
      - app

networks:
  app:

Kiểm tra DNS trong Nginx container:

docker compose exec nginx getent hosts api

Test:

docker compose exec nginx \
  curl -v http://api:3000/health

Image Nginx có thể không có curl; dùng wget hoặc debug container phù hợp.

host not found in upstream

Ví dụ:

host not found in upstream "api"

Nguyên nhân:

  • Service name sai.
  • Không cùng Docker network.
  • Nginx start trước DNS/service và config resolve theo cách không phù hợp.
  • Container đã đổi tên.
  • Dùng hostname không phân giải được.

Kiểm tra:

docker compose config
docker network inspect NETWORK_NAME

HTTP và HTTPS upstream sai

App chỉ chạy HTTP:

http://127.0.0.1:3001

Nhưng Nginx dùng:

proxy_pass https://127.0.0.1:3001;

TLS handshake sẽ fail.

Hoặc app chạy HTTPS nội bộ nhưng Nginx dùng HTTP.

Chọn đúng scheme:

proxy_pass http://127.0.0.1:3001;

Thông thường HTTPS kết thúc tại Nginx và upstream local dùng HTTP.

Unix socket permission

Nginx có thể proxy tới socket:

proxy_pass http://unix:/run/deployeasy/api.sock;

Lỗi:

permission denied while connecting to upstream

Kiểm tra:

ls -l /run/deployeasy/api.sock
namei -l /run/deployeasy/api.sock

Nginx worker user cần quyền traverse directory và truy cập socket.

Không sửa bằng chmod 777; dùng owner/group đúng.

SELinux hoặc AppArmor

Trên một số distro, MAC policy có thể chặn Nginx kết nối upstream.

Ubuntu thường dùng AppArmor; RHEL-family thường dùng SELinux.

Kiểm tra log:

sudo journalctl -k --since "30 minutes ago"

Không tắt toàn bộ security module để sửa nhanh. Tìm policy cần điều chỉnh.

upstream timed out

Ví dụ:

upstream timed out
while reading response header from upstream

Nginx kết nối được nhưng app phản hồi quá lâu.

Kiểm tra:

time curl -v http://127.0.0.1:3001/slow-endpoint

Nguyên nhân:

  • Query database chậm.
  • Deadlock.
  • API bên thứ ba.
  • CPU/RAM cạn.
  • Event loop bị block.
  • Timeout app.
  • File upload/xử lý đồng bộ.

Tạm điều chỉnh:

proxy_connect_timeout 10s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;

Không tăng timeout vô hạn. Sửa nguyên nhân app chậm.

upstream prematurely closed connection

App đóng connection trước khi Nginx nhận response hoàn chỉnh.

Kiểm tra app log cùng thời điểm:

pm2 logs
sudo journalctl -u deployeasy-api
docker compose logs api

Nguyên nhân:

  • App crash.
  • Unhandled exception.
  • OOM kill.
  • Process restart.
  • Response quá lớn/lỗi.
  • Timeout trong app.
  • Client aborted và app xử lý sai.

App bị OOM

Kiểm tra:

free -h
sudo journalctl -k |
grep -i -E 'out of memory|killed process'

Docker:

docker inspect CONTAINER \
  --format '{{.State.OOMKilled}}'

Nếu app bị kill, Nginx sẽ không kết nối được và trả 502.

PM2 restart loop

pm2 list
pm2 describe deployeasy-api

Xem restart count và log.

App có thể chạy vài giây rồi crash, khiến 502 xuất hiện không liên tục.

Health endpoint

Tạo endpoint nhẹ:

GET /health

Kiểm tra upstream:

curl -f http://127.0.0.1:3001/health

Nginx:

curl -f https://api.deployeasy.vn/health

Nếu upstream health fail, lỗi nằm trước Nginx public layer.

Kiểm tra Host header

Một số app cần domain đúng.

curl -v \
  -H 'Host: api.deployeasy.vn' \
  http://127.0.0.1:3001

Nginx:

proxy_set_header Host $host;

Header WebSocket

Nếu lỗi chỉ xảy ra với WebSocket:

proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

Kiểm tra cả route và CORS/origin của WebSocket server.

Buffer và response header

Nginx có giới hạn buffer cho response header. Cookie quá lớn hoặc nhiều header có thể gây lỗi.

Log có thể báo:

upstream sent too big header

Nguyên nhân thường là cookie/header quá lớn. Tốt hơn là giảm header thay vì tăng buffer vô hạn.

Có thể điều chỉnh:

proxy_buffer_size 16k;
proxy_buffers 8 16k;

Nhưng phải đo và hiểu.

DNS của upstream ngoài

Nếu proxy tới hostname:

proxy_pass https://backend.example.com;

DNS resolution và certificate upstream có thể gây lỗi.

Kiểm tra từ VPS:

dig backend.example.com
curl -Iv https://backend.example.com

Nginx config chưa reload

Sau khi sửa:

sudo nginx -t
sudo systemctl reload nginx

Kiểm tra config active:

sudo nginx -T

Đừng chỉ nhìn file vừa sửa; có thể Nginx đang dùng file khác.

Default site xung đột

Ubuntu có thể có:

/etc/nginx/sites-enabled/default

Nếu server block không khớp, request rơi vào default.

Có thể disable sau khi chắc chắn:

sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

Không xóa file trước khi hiểu cấu hình.

Quy trình chẩn đoán 502

Tầng 1: DNS và Nginx

dig api.deployeasy.vn
sudo systemctl status nginx
sudo nginx -t

Tầng 2: Upstream local

curl -v http://127.0.0.1:3001/health
sudo ss -ltnp | grep :3001

Tầng 3: Process manager

PM2:

pm2 list
pm2 logs --lines 200

systemd:

sudo systemctl status deployeasy-api
sudo journalctl -u deployeasy-api -n 200

Docker:

docker compose ps
docker compose logs --tail=200 api

Tầng 4: Tài nguyên

free -h
df -h
df -i
uptime

Tầng 5: Database/dependency

pg_isready
curl -Iv https://dependency.example.com

Ví dụ lỗi Docker

Compose:

services:
  api:
    ports:
      - "3000:3000"

App bind:

app.listen(3000, "127.0.0.1");

Host curl fail.

Sửa:

app.listen(3000, "0.0.0.0");

Host chỉ publish local:

ports:
  - "127.0.0.1:3000:3000"

Ví dụ sai port

Nginx:

proxy_pass http://127.0.0.1:3000;

PM2 app:

PORT=3001

Sửa một nguồn cấu hình và dùng healthcheck để xác minh.

Ví dụ app crash do env

Log:

DATABASE_URL is required

Sửa EnvironmentFile/Compose env, rồi restart:

sudo systemctl restart deployeasy-api

hoặc:

docker compose up -d api

Không nên làm

Restart mọi thứ ngay

Restart có thể xóa trạng thái/log tạm và không tìm được nguyên nhân.

Tăng timeout vô hạn

Che app chậm và giữ connection lâu.

chmod 777

Không sửa được sai port hoặc app crash, đồng thời tăng rủi ro.

Ép status 200 trong Nginx

Che lỗi thay vì sửa upstream.

Checklist

[ ] Đúng server block
[ ] Xem error log
[ ] curl upstream trực tiếp
[ ] Port đúng
[ ] Process đang chạy
[ ] App bind đúng interface
[ ] Docker publish/network đúng
[ ] HTTP/HTTPS scheme đúng
[ ] Socket permission đúng
[ ] Health endpoint
[ ] Không OOM
[ ] Database hoạt động
[ ] Timeout được đo
[ ] nginx -t + reload
[ ] Monitoring restart count

Câu hỏi thường gặp

502 có phải do DNS không?

DNS sai thường đưa request tới server khác hoặc không kết nối. 502 thường phát sinh sau khi request tới reverse proxy nhưng upstream gặp vấn đề.

Restart Nginx có sửa 502 không?

Chỉ khi Nginx/config có vấn đề. Nếu app không chạy, restart Nginx không giải quyết gốc.

502 và 504 khác nhau thế nào?

502 thường là phản hồi upstream không hợp lệ hoặc kết nối lỗi; 504 thường liên quan timeout chờ upstream. Log là nguồn xác nhận.

App local chạy nhưng domain 502?

Kiểm tra Nginx proxy port, interface, Docker network, scheme và server block.

Kết luận

Cách sửa 502 nhanh nhất là đi theo luồng request và test từng điểm. Bắt đầu từ error log và curl trực tiếp upstream, thay vì thay đổi nhiều cấu hình cùng lúc.

Nguồn tham khảo

Đọc tiếp