Xử lý sự cố

Website trả lỗi 502 Bad Gateway: cách khoanh vùng Nginx và upstream

Cách khoanh vùng lỗi 502 qua Nginx error log, socket hoặc port, trạng thái backend, container và tài nguyên VPS trước khi restart dịch vụ.

Khoanh vùng lỗi HTTP 502
Mục lục

Trang trắng với dòng chữ 502 Bad Gateway từ Nginx thực chất mang một ý nghĩa rất cụ thể trong mô hình reverse proxy: bản thân Nginx vẫn đang sống, vẫn nhận được request từ Internet gửi tới cổng 80/443, nhưng ở chặng tiếp theo khi chuyển tiếp gói tin tới dịch vụ ứng dụng nội bộ (PHP-FPM, Node.js, Python hoặc container Docker), kết nối đã bị từ chối hoặc bị ngắt đột ngột giữa chừng.

Mã lỗi này không khẳng định máy chủ đã cạn kiệt tài nguyên, cũng không có nghĩa Nginx bị lỗi cấu hình. Nó đơn giản là bằng chứng cho thấy chuỗi giao tiếp giữa tầng proxy biên và tầng xử lý nghiệp vụ phía sau đã bị đứt gãy.

Một lỗi crash do thiếu biến môi trường, một tệp Unix socket bị phân quyền sai sau lần deploy, hoặc một tiến trình backend vừa bị nhân Linux tắt ngang đều có thể tạo ra cùng một trang 502 trên trình duyệt. Để xử lý dứt điểm mà không cần khởi động lại máy chủ trong mù mờ, bạn cần lần theo đúng đường truyền của gói tin từ Nginx tới upstream để bóc tách từng lớp nguyên nhân.

Luồng điều tra lỗi 502 từ client qua reverse proxy và upstream tới tài nguyên

Đi từ ngoài vào trong và dừng tại lớp đầu tiên có bằng chứng bất thường.

502 Bad Gateway khác gì với 504 Gateway Timeout?

Cả hai mã lỗi này đều xuất phát từ reverse proxy, nhưng phản ánh hai trạng thái hoàn toàn khác nhau của dịch vụ backend:

  • 502 Bad Gateway (Lỗi kết nối hoặc phản hồi không hợp lệ): Nginx cố gắng bắt tay với backend nhưng bị từ chối ngay lập tức (tiến trình backend chưa bật, port sai, socket không tồn tại), hoặc backend bị crash đột ngột và đóng kết nối giữa chừng trước khi kịp gửi dữ liệu trả về.
  • 504 Gateway Timeout (Hết thời gian chờ): Nginx đã kết nối thành công tới backend, nhưng backend xử lý quá chậm chạp (do câu truy vấn database bị treo, deadlock hoặc nghẽn tính toán) khiến thời gian chờ vượt quá giới hạn cấu hình proxy_read_timeout hay fastcgi_read_timeout của Nginx. Quy trình xử lý lỗi này được trình bày riêng tại bài Lỗi 504 Gateway Timeout: nguyên nhân thường nằm ở đâu?.

Việc phân biệt rõ hai mã lỗi này giúp bạn không mắc sai lầm phổ biến là đi tăng thời gian timeout của Nginx trong khi backend thực tế đã sập từ trước.

Bước 1: Xác nhận phạm vi và thời điểm xảy ra lỗi

Trước khi can thiệp vào máy chủ, hãy dành 1 phút để thu thập các đặc điểm nhận diện:

  • Lỗi xuất hiện trên toàn bộ website hay chỉ xảy ra ở một đường dẫn API cụ thể?
  • Lỗi xảy ra liên tục 100% hay chỉ xuất hiện ngẫu nhiên theo từng đợt truy cập?
  • Trang tĩnh (tệp ảnh, CSS, JS do Nginx trực tiếp phục vụ) có tải được không? Nếu trang tĩnh vẫn tải bình thường mà chỉ có trang động trả về 502, bạn có thể khẳng định Nginx hoàn toàn khỏe mạnh và sự cố nằm ở tầng ứng dụng backend.
  • Lỗi bắt đầu phát sinh từ thời điểm nào? Có trùng khớp với một đợt deploy code mới hoặc cập nhật cấu hình hệ thống hay không?

Nếu sự cố diễn ra trên diện rộng và gây gián đoạn dịch vụ nghiêm trọng, hãy tuân thủ nguyên tắc điều phối tại bài Quy trình 30 phút đầu khi website production gặp sự cố.

Bước 2: Đọc error log của Nginx quanh đúng thời điểm lỗi

Giao diện trang báo lỗi 502 trên trình duyệt chỉ là kết quả bề mặt. Tệp tin nhật ký lỗi của Nginx (/var/log/nginx/error.log) mới là nơi ghi lại chính xác lý do vì sao Nginx từ chối phục vụ request.

Chạy lệnh trích xuất các dòng log lỗi mới nhất:

bash
sudo tail -n 50 /var/log/nginx/error.log

Dưới đây là bảng giải mã các thông báo lỗi kinh điển trong log Nginx và hướng xử lý tương ứng:

Thông báo trong Nginx error.logÝ nghĩa kỹ thuậtHướng kiểm tra ưu tiên
connect() failed (111: Connection refused)Cổng TCP mà Nginx trỏ tới không có bất kỳ tiến trình nào đang lắng ngheBackend chưa bật, bị crash, hoặc Nginx trỏ nhầm số cổng (ví dụ app nghe 3000 nhưng proxy trỏ 8080)
connect() to unix:... failed (2: No such file or directory)Tệp tin Unix socket không tồn tại trên đường dẫn chỉ địnhDịch vụ backend chưa tạo socket, socket bị xóa, hoặc đường dẫn socket trong cấu hình Nginx bị gõ sai
connect() to unix:... failed (13: Permission denied)Tệp tin socket có tồn tại nhưng user chạy Nginx không có quyền đọc/ghiQuyền sở hữu socket (owner/group) không cho phép user www-data truy cập
recv() failed (104: Connection reset by peer)Kết nối TCP đã thiết lập nhưng backend bất ngờ đóng kết nối đột ngộtỨng dụng backend bị crash giữa chừng do lỗi mã nguồn, thiếu RAM (OOM), hoặc tràn bộ đệm
upstream sent invalid response / headerBackend phản hồi dữ liệu rác, không tuân thủ chuẩn HTTPỨng dụng trả về output trước HTTP header, hoặc cấu hình giao thức giữa Nginx và backend bị lệch (ví dụ nói chuyện gRPC/HTTP2 bằng proxy thường)
no live upstreams while connecting to upstreamToàn bộ các máy chủ trong khối upstream đều bị Nginx đánh dấu là không khả dụngTất cả backend đều sập hoặc cơ chế health check của Nginx loại bỏ toàn bộ cụm

Chỉ riêng thông báo lỗi trong log đã giúp bạn loại bỏ tới 80% các phỏng đoán sai lầm.

Bước 3: Thử nghiệm kết nối trực tiếp vào upstream

Sau khi biết địa chỉ mà Nginx đang cố gắng kết nối tới (qua cổng TCP cục bộ như 127.0.0.1:3000 hoặc Unix socket), hãy bỏ qua Nginx và kiểm tra trực tiếp dịch vụ backend ngay trên máy chủ:

Nếu backend lắng nghe qua cổng TCP:

bash
# Thử gửi request HTTP trực tiếp tới backend
curl -Iv http://127.0.0.1:3000/health

Nếu backend sử dụng Unix domain socket (như PHP-FPM):

bash
# Kiểm tra sự tồn tại của tệp socket
ls -la /run/php/php8.2-fpm.sock

Kết quả của thao tác này chia vấn đề làm 2 nhánh rõ ràng:

  • Trường hợp 1: Kết nối trực tiếp thành công và trả về mã 200. Nghĩa là ứng dụng backend hoàn toàn khỏe mạnh. Vấn đề nằm ở cấu hình Nginx (proxy_pass sai, thiếu các header Host, X-Real-IP, hoặc mạng nội bộ giữa Nginx và container bị chặn).
  • Trường hợp 2: Kết nối trực tiếp báo lỗi hoặc từ chối kết nối. Nghĩa là bản thân ứng dụng backend đang gặp sự cố. Bạn không cần bận tâm đến Nginx nữa mà chuyển toàn bộ sự tập trung sang kiểm tra tiến trình ứng dụng.

Bước 4: Kiểm tra trạng thái và vòng lặp khởi động lại của Backend

Khi xác định lỗi nằm ở ứng dụng, hãy kiểm tra trạng thái hoạt động của tiến trình:

Đối với dịch vụ quản lý bởi systemd (như PHP-FPM, Gunicorn):

bash
sudo systemctl status php8.2-fpm

Hãy chú ý đến thời gian hoạt động (Active: active (running) since...). Nếu dịch vụ mới chỉ chạy được vài giây, rất có thể nó liên tục bị sập và khởi động lại ngầm. Đọc log chi tiết của dịch vụ:

bash
sudo journalctl -u php8.2-fpm -n 50 --no-pager

Đối với ứng dụng chạy trong Docker container:

bash
docker compose ps

Quan sát cột trạng thái: nếu container hiển thị Restarting (1) hoặc thời gian Up 5 seconds, ứng dụng của bạn đang rơi vào vòng lặp khởi động lại (crash loop). Hãy đọc log của container để xem lỗi sập:

bash
docker compose logs --tail=100 backend

Các nguyên nhân phổ biến khiến backend crash ngay khi khởi động:

  • Thiếu biến môi trường hoặc secret kết nối cơ sở dữ liệu.
  • Không thể kết nối tới máy chủ Database hoặc Redis khi khởi tạo ứng dụng.
  • Lỗi cú pháp mã nguồn chưa được phát hiện trước khi build.

Bước 5: Kiểm tra phân quyền truy cập Unix Socket

Nếu kiến trúc của bạn dùng Unix domain socket (rất phổ biến với PHP-FPM và Nginx trên cùng VPS), quyền truy cập tệp tin socket là nguyên nhân hàng đầu gây ra lỗi Permission denied.

Hãy kiểm tra quyền sở hữu của socket:

bash
ls -la /run/php/php8.2-fpm.sock
# Kết quả mong muốn:
# srw-rw---- 1 www-data www-data 0 Oct 12 10:00 /run/php/php8.2-fpm.sock

User mặc định chạy Nginx trên Ubuntu là www-data. Do đó, tệp tin socket của PHP-FPM cũng phải thuộc sở hữu của user/group www-data và có quyền đọc/ghi (rw-rw---- hoặc mode 0660).

Nếu thấy socket thuộc sở hữu của root hoặc quyền chỉ dành cho chủ sở hữu, hãy kiểm tra lại file cấu hình pool của PHP-FPM (thường ở /etc/php/8.2/fpm/pool.d/www.conf):

ini
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

Tuyệt đối không sử dụng lệnh chmod 777 lên tệp socket như một giải pháp tạm bợ. Việc này vi phạm nguyên tắc bảo mật và quyền sẽ bị ghi đè trở lại mỗi khi dịch vụ PHP-FPM khởi động lại.

Bước 6: Đối chiếu các thay đổi deploy và cấu hình mạng gần nhất

Nếu lỗi 502 xuất hiện ngay sau khi bạn vừa cập nhật mã nguồn hoặc chỉnh sửa cấu hình:

  • Kiểm tra cú pháp Nginx: Chạy sudo nginx -t để đảm bảo không có sai sót cấu hình trước khi reload.
  • Kiểm tra mạng Docker: Trong môi trường Docker Compose, nếu Nginx và backend nằm ở hai service khác nhau, chúng bắt buộc phải nằm chung một Docker network do người dùng tự định nghĩa. Nếu cấu hình thiếu network, Nginx sẽ không thể phân giải tên miền nội bộ của service (ví dụ http://backend:3000) dẫn tới lỗi 502 Bad Gateway hoặc lỗi DNS nội bộ. Chi tiết xem tại bài Docker network cho website nhỏ.
  • Kiểm tra xung đột cổng mạng: Đảm bảo cổng mới được chỉ định không bị chiếm dụng bởi một tiến trình cũ chưa được tắt hoàn toàn.

Bước 7: Điều tra tài nguyên hệ thống (RAM OOM và Disk Inode)

Chỉ kiểm tra tài nguyên phần cứng sau khi đã loại trừ các nguyên nhân về cấu hình và kết nối ở trên. Trong thực tế, có hai hiện tượng tài nguyên dẫn thẳng tới lỗi 502:

1. Nhân Linux kích hoạt OOM Killer khai tử Backend

Khi bộ nhớ RAM của VPS cạn kiệt, Linux Kernel sẽ tự động chọn một tiến trình tốn nhiều RAM nhất nhưng không ảnh hưởng tới sự sống còn của hệ điều hành để cưỡng bức tắt nhằm bảo vệ máy chủ. Tiến trình backend (Node.js, PHP worker, Java app) thường là đối tượng bị chọn.

Khi backend bị tắt đột ngột, Nginx vẫn còn sống nhưng mất kết nối tới upstream, dẫn tới lỗi 502 kèm log Connection refused hoặc Connection reset.

Kiểm tra xem hệ thống có vừa kích hoạt OOM Killer hay không:

bash
sudo dmesg -T | grep -i -E 'oom|killed process'

Nếu phát hiện log báo backend bị kill do thiếu bộ nhớ, hãy tham khảo quy trình điều tra tại bài VPS hết RAM và OOM Killer: dấu hiệu, kiểm tra và phòng ngừa.

2. Hết dung lượng đĩa cứng hoặc cạn kiệt Inode

Khi ổ đĩa đầy 100%, hoặc số lượng tệp tin vượt quá giới hạn Inode (df -i), ứng dụng backend sẽ không thể ghi tệp session, ghi cache hoặc tạo tệp Unix socket lúc khởi động, dẫn tới việc tiến trình bị crash ngay lập tức. Hãy kiểm tra nhanh bằng:

bash
df -h
df -i

Khi nào khởi động lại dịch vụ là giải pháp phù hợp?

Khởi động lại (restart) dịch vụ backend là một biện pháp hạ nhiệt chấp nhận được nếu:

  1. Bạn đã lưu lại tệp log lỗi mới nhất của Nginx và backend để phục vụ điều tra sau này.
  2. Tiến trình backend bị kẹt trong một deadlock hoặc rò rỉ bộ nhớ tạm thời mà bạn chưa thể sửa code ngay trong giờ cao điểm.
  3. Bạn chỉ khởi động lại đúng dịch vụ bị lỗi (sudo systemctl restart php8.2-fpm hoặc docker compose restart backend), không khởi động lại Nginx và không reboot máy chủ.

Nếu sau khi restart, ứng dụng chạy lại được nhưng chỉ sau 10-15 phút lỗi 502 lại xuất hiện, bạn cần ngừng việc restart liên tục và bắt tay vào điều tra nguyên nhân sâu xa (như rò rỉ bộ nhớ hoặc câu truy vấn làm nghẽn worker pool).

Quy trình kiểm thử nghiệm thu sau khi khắc phục

Sau khi đã xử lý xong nguyên nhân, hãy xác nhận hệ thống hoạt động ổn định qua 4 bước:

  1. Kiểm tra upstream trực tiếp: Gửi request nội bộ tới backend và nhận mã 200.
  2. Kiểm tra qua Nginx: Gửi request qua domain chính thức và nhận mã 200.
  3. Theo dõi error log trực tiếp: Chạy lệnh sudo tail -f /var/log/nginx/error.log trong 2-3 phút trong khi duyệt web để đảm bảo không còn dòng lỗi 502 nào mới phát sinh.
  4. Kiểm tra luồng người dùng thực tế: Thực hiện một thao tác gọi tới đúng endpoint từng bị lỗi để chắc chắn nghiệp vụ đã thông suốt.

Checklist khoanh vùng nhanh lỗi 502 Bad Gateway

  • ☐ Đã xác nhận trang tĩnh còn hoạt động và lỗi chỉ xảy ra trên trang động của backend.
  • ☐ Đã đọc chính xác thông báo lỗi trong /var/log/nginx/error.log (Connection refused, Permission denied hay Connection reset).
  • ☐ Đã thử nghiệm kết nối trực tiếp vào IP/port hoặc socket của backend bằng curl.
  • ☐ Đã kiểm tra trạng thái tiến trình backend (systemctl status hoặc docker compose ps), xác nhận app không bị crash loop.
  • ☐ Nếu dùng Unix socket: đã kiểm tra quyền sở hữu tệp socket thuộc về www-data với mode 0660.
  • ☐ Nếu dùng Docker: đã xác nhận Nginx và backend nằm chung một Docker network và có thể ping thông qua service name.
  • ☐ Đã kiểm tra log kernel dmesg để loại trừ trường hợp backend bị OOM Killer khai tử do hết RAM.
  • ☐ Đã kiểm tra dung lượng ổ đĩa (df -h) và số lượng inode (df -i).
  • ☐ Chỉ khởi động lại đúng tiến trình backend bị kẹt sau khi đã lưu trữ log phục vụ điều tra.

Lỗi 502 sẽ không còn là một ẩn số phức tạp nếu bạn tiếp cận nó như một bài toán định tuyến mạng: tìm đúng điểm đứt gãy giữa proxy và ứng dụng, đọc đúng thông điệp lỗi trong nhật ký hệ thống, và giải quyết triệt để nguyên nhân thay vì chỉ restart để chữa triệu chứng bề mặt.

Nguồn tham khảo

Chia sẻ bài viết hữu ích

Gửi bài viết này cho đồng nghiệp

Đọc tiếp

Bài nên đọc tiếp

Xem tất cả bài về Xử lý sự cố