Nginx 502 Bad Gateway: nguyên nhân và cách sửa theo từng trường hợp

Khoanh vùng lỗi Nginx 502 bằng log, nginx -T, port/socket, PHP-FPM và curl upstream; sửa đúng nguyên nhân thay vì tăng timeout máy móc.

NHT
· 8 phút đọc
Minh họa khoanh vùng lỗi 502 Bad Gateway giữa Nginx và backend upstream

Câu trả lời ngắn: Nginx trả 502 Bad Gateway khi không nhận được phản hồi hợp lệ từ upstream mà nó đang proxy tới. Upstream có thể là ứng dụng Node/Python ở port TCP, PHP-FPM qua UNIX socket hoặc một backend khác. Cách sửa đúng không phải luôn là tăng timeout; hãy xác định Nginx đang kết nối tới đâu, backend có lắng nghe không, socket có tồn tại và quyền truy cập có đúng không.

Lỗi 502 thường xuất hiện sau khi deploy, đổi phiên bản PHP, đổi port ứng dụng, restart service hoặc sửa virtual host. Bài viết này đưa ra quy trình khoanh vùng theo từng trường hợp: upstream chết, sai port, socket PHP-FPM bị thiếu, quyền socket sai, DNS/backend không phản hồi, ứng dụng trả response không hợp lệ và cấu hình proxy bị lỗi.

1. 502 Bad Gateway nghĩa là gì?

Nginx đóng vai trò gateway. Client gửi request tới Nginx, sau đó Nginx chuyển request tới upstream bằng proxy_pass, fastcgi_pass hoặc một directive tương tự. Nếu Nginx không kết nối được, kết nối bị đóng sớm hoặc nhận phản hồi không hợp lệ, Nginx có thể trả 502 cho client.

Điểm quan trọng là 502 chỉ nói rằng gateway không hoàn thành việc giao tiếp với backend. Nó chưa cho biết nguyên nhân cụ thể. Vì vậy, trang lỗi trên trình duyệt không đủ thông tin; cần đọc error log của Nginx và kiểm tra backend trực tiếp.

2. Quy trình chẩn đoán trong 5 phút

Chạy các lệnh sau trên máy chủ, thay example.com bằng domain thực tế:

sudo nginx -t
sudo tail -n 100 /var/log/nginx/error.log
sudo tail -n 100 /var/log/nginx/access.log
sudo systemctl --failed
sudo ss -lntp
sudo ss -lxnp

Sau đó xác định upstream trong cấu hình đang active:

sudo nginx -T | grep -nE 'server_name|proxy_pass|fastcgi_pass|upstream|listen'
ps aux | grep -E 'php-fpm|node|gunicorn|uvicorn' | grep -v grep

Đừng chỉ mở file bạn nghĩ là đang dùng. nginx -T cho thấy cấu hình sau khi Nginx đã nạp các file include, từ đó tránh chẩn đoán nhầm một virtual host không active.

3. Trường hợp 1: Backend service đã chết

Nếu Nginx proxy tới ứng dụng Node, Python hoặc service systemd, hãy kiểm tra trạng thái service:

sudo systemctl status myapp --no-pager
sudo journalctl -u myapp -n 100 --no-pager
sudo systemctl restart myapp
sudo systemctl status myapp --no-pager

Với ứng dụng chạy bằng process manager, hãy kiểm tra process và port thực tế thay vì chỉ nhìn file cấu hình:

ps aux | grep -E 'node|gunicorn|uvicorn' | grep -v grep
sudo ss -lntp | grep ':3000'
curl -i http://127.0.0.1:3000/health

Nếu curl tới backend trực tiếp cũng thất bại, lỗi nằm ở ứng dụng hoặc service chứ chưa phải Nginx. Hãy đọc log ứng dụng, kiểm tra biến môi trường, port binding và quyền đọc file. Nếu curl trực tiếp pass nhưng domain vẫn 502, chuyển sang kiểm tra cấu hình Nginx và header.

4. Trường hợp 2: Sai port hoặc sai địa chỉ upstream

Ví dụ Nginx đang dùng:

location / {
    proxy_pass http://127.0.0.1:3000;
}

Nhưng ứng dụng thực tế đã chuyển sang port 3001. Nginx vẫn chạy bình thường, nginx -t vẫn pass, nhưng request sẽ 502 vì không có process lắng nghe tại 3000.

sudo ss -lntp | grep -E ':3000|:3001'
curl -i http://127.0.0.1:3000/
curl -i http://127.0.0.1:3001/

Sửa một phía cho khớp: hoặc đưa ứng dụng về port đã quy ước, hoặc cập nhật proxy_pass rồi kiểm tra:

sudo nginx -t
sudo systemctl reload nginx
curl -I https://example.com/

Nếu dùng hostname trong upstream, kiểm tra DNS và resolver. Với backend cùng máy, dùng loopback hoặc UNIX socket thường dễ kiểm soát hơn, nhưng vẫn phải nhất quán với service thực tế.

5. Trường hợp 3: PHP-FPM socket không tồn tại

PHP site thường dùng:

location ~ \.php$ {
    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}

Sau khi đổi từ PHP 8.2 sang 8.3, socket cũ có thể không còn tồn tại:

ls -la /run/php/
sudo systemctl status php8.3-fpm --no-pager
sudo systemctl status php8.2-fpm --no-pager
sudo ss -lxnp | grep php

So sánh chính xác tên socket trong fastcgi_pass với socket do service tạo. Nếu service chưa chạy, khởi động hoặc sửa nguyên nhân khiến service fail:

sudo php-fpm8.3 -t 2>/dev/null || true
sudo systemctl restart php8.3-fpm
sudo nginx -t
sudo systemctl reload nginx

Không nên tạo symlink socket thủ công chỉ để che lỗi version. Khi PHP-FPM restart, socket có thể bị tạo lại và symlink làm quy trình khó hiểu hơn. Hãy sửa fastcgi_pass về endpoint thật, hoặc dùng một naming convention rõ ràng do hệ thống quản lý.

6. Trường hợp 4: Quyền UNIX socket sai

PHP-FPM documentation cho phép cấu hình listen.owner, listen.grouplisten.mode. Nginx worker phải có quyền truy cập socket; nếu không, error log thường có thông báo connect() to unix socket failed (13: Permission denied).

stat /run/php/php8.3-fpm.sock
namei -l /run/php/php8.3-fpm.sock
id www-data
sudo grep -R --line-number -E '^listen|^listen.owner|^listen.group|^listen.mode' \
  /etc/php/8.3/fpm/pool.d/

Ví dụ cấu hình pool có thể cần đồng bộ user/group:

listen = /run/php/php8.3-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

Chỉ mở quyền ở mức cần thiết. Không dùng chmod 777 cho socket để “sửa nhanh”, vì cách đó làm yếu mô hình phân quyền và không giải quyết nguyên nhân quản lý user/group.

7. Trường hợp 5: Backend phản hồi quá chậm

Nếu backend vẫn sống nhưng xử lý request lâu, error log có thể cho thấy timeout. Nginx có các directive như proxy_connect_timeout, proxy_read_timeoutproxy_send_timeout. Tuy nhiên, tăng timeout chỉ hợp lý sau khi biết request nào cần nhiều thời gian và backend có đủ tài nguyên.

location /reports/ {
    proxy_pass http://127.0.0.1:3000;
    proxy_connect_timeout 5s;
    proxy_read_timeout 60s;
    proxy_send_timeout 60s;
}

Trước khi tăng timeout, kiểm tra CPU, RAM, database query, queue và log application. Nếu mọi endpoint đều chậm, gốc rễ có thể là database hoặc process pool cạn, không phải cấu hình Nginx. Với tác vụ dài, cân nhắc đưa công việc sang queue và trả response bất đồng bộ.

8. Trường hợp 6: Header và cấu hình proxy không đầy đủ

NGINX documentation minh họa việc truyền Host và địa chỉ IP client:

location / {
    proxy_pass http://127.0.0.1:3000;
    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;
}

Thiếu header thường gây lỗi ứng dụng, redirect sai hoặc tạo URL không đúng scheme; nó không phải nguyên nhân duy nhất của 502. Hãy phân biệt log của Nginx với log của application để không sửa nhầm.

9. Trường hợp 7: upstream group và server dự phòng

Với nhiều backend, có thể định nghĩa upstream:

upstream app_backend {
    server 127.0.0.1:3000 max_fails=3 fail_timeout=10s;
    server 127.0.0.1:3001 backup;
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://app_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

NGINX upstream module hỗ trợ backend TCP, hostname và UNIX socket. Weighted round-robin là cơ chế mặc định; max_failsfail_timeout ảnh hưởng cách backend bị coi là unavailable. Dù có server dự phòng, vẫn cần monitor để biết backend chính đang fail vì deploy lỗi, quá tải hay lỗi mạng.

10. Cách kiểm tra sau khi sửa

Mỗi lần sửa chỉ nên thay đổi một nhóm nguyên nhân, sau đó kiểm tra theo thứ tự:

sudo nginx -t
sudo systemctl reload nginx
curl -sS -o /dev/null -w '%{http_code}\n' https://example.com/
curl -i http://127.0.0.1:3000/health
sudo tail -n 50 /var/log/nginx/error.log

Nếu endpoint health không tồn tại, dùng một endpoint nhẹ và an toàn. Với PHP, có thể kiểm tra một file health tối giản nhưng không để lộ biến môi trường, đường dẫn nội bộ hoặc thông tin database.

11. Rollback khi bản sửa làm lỗi nặng hơn

Trước khi sửa production, lưu cấu hình và ghi lại version:

sudo nginx -T > ~/nginx-config-before-502-fix.txt
sudo cp -a /etc/nginx/sites-available/example.com \
  ~/example.com.before-502-fix.$(date +%Y%m%d-%H%M%S)

Nếu thay đổi gây lỗi, khôi phục file gần nhất, chạy nginx -t rồi reload. Không restart Nginx nếu syntax chưa pass. Với PHP-FPM hoặc application service, rollback package/version là quyết định riêng; trước tiên hãy khôi phục endpoint và socket mà Nginx đang trỏ tới.

12. Checklist chống tái diễn 502

  • Deploy phải kiểm tra service backend và endpoint health.
  • Port/socket trong Nginx phải được đối chiếu với process thực tế.
  • PHP version mới phải đi kèm PHP-FPM service và socket đúng version.
  • Quyền socket phải cấp cho đúng user/group của Nginx.
  • Log Nginx và log backend phải được lưu đủ để đối chiếu timestamp.
  • Mọi thay đổi Nginx phải chạy nginx -t trước reload.
  • Không tăng timeout hoặc mở quyền rộng khi chưa xác định nguyên nhân.

Kết luận

Nginx 502 Bad Gateway là tín hiệu về đường giao tiếp giữa gateway và backend. Quy trình đáng tin cậy là đọc error log, xem cấu hình active bằng nginx -T, kiểm tra process/port/socket, gọi backend trực tiếp, sửa đúng endpoint rồi mới reload. Cách làm này nhanh hơn việc thử ngẫu nhiên nhiều directive và an toàn hơn việc cấp quyền rộng hoặc restart liên tục.

Để hiểu bối cảnh routing nhiều website, hãy xem bài cấu hình Nginx reverse proxy cho nhiều website trên cùng VPS. Nếu lỗi xuất hiện sau khi đổi PHP, bài chạy nhiều phiên bản PHP với Nginx và PHP-FPM sẽ giúp bạn đối chiếu pool và socket.

Nguồn tham khảo chính thức