Mô tả lỗi
Log lỗi Nginx sẽ có nội dung tương tự như sau:
2024/01/15 10:23:41 [error] 12345#12345: *1 upstream sent invalid header while reading response header from upstream, client: 192.168.1.10, server: example.com, request: "GET /index.php HTTP/1.1", upstream: "fastcgi://unix:/run/php/php8.1-fpm.sock", host: "example.com"
Trình duyệt nhận được lỗi 502. Đây không phải lỗi 502 thông thường (PHP-FPM không chạy) hay 504 (timeout). Ở đây, Nginx đã kết nối thành công tới PHP-FPM — nhưng dữ liệu trả về lại không hợp lệ. PHP-FPM trả về nội dung mà Nginx không thể phân tích được dưới dạng header phản hồi FastCGI hợp lệ.
Nguyên nhân
Có nhiều nguyên nhân gây ra lỗi này. Các nguyên nhân phổ biến nhất:
- PHP script xuất text thô hoặc HTML trước header — một warning, notice lạc, hoặc BOM trước bất kỳ lệnh
header()nào - Cấu hình
fastcgi_passsai — trỏ đến URL HTTP proxy thay vì FastCGI socket hoặc cổng - Đường dẫn socket không khớp giữa cấu hình Nginx và cổng PHP-FPM đang thực sự lắng nghe
- Cấu hình pool PHP-FPM sai khiến các worker bị crash giữa chừng khi đang phản hồi
- Thiếu hoặc sai các directive
fastcgi_params/fastcgi_index
Bước 1: Kiểm tra log lỗi PHP-FPM trước
Chưa cần động vào Nginx. Bắt đầu bằng cách xem PHP-FPM đang thực sự làm gì:
# Ubuntu/Debian
tail -n 50 /var/log/php8.1-fpm.log
# Hoặc theo pool (www là tên pool mặc định)
tail -n 50 /var/log/php/8.1/fpm/www-error.log
Nếu thấy lỗi fatal PHP, cảnh báo về output trước header, hoặc segfault — vấn đề nằm ở PHP, không phải Nginx. Hãy sửa PHP trước.
Cũng kiểm tra log lỗi riêng của pool. Trong /etc/php/8.1/fpm/pool.d/www.conf, đảm bảo có:
catch_workers_output = yes
php_flag[display_errors] = off
php_admin_value[error_log] = /var/log/php8.1-fpm-www.log
Bước 2: Kiểm tra target của fastcgi_pass
Chín trong mười trường hợp, đây là thủ phạm. Mở server block Nginx của bạn:
grep -n 'fastcgi_pass' /etc/nginx/sites-enabled/your-site.conf
Các cấu hình sai phổ biến:
# SAI — trỏ đến HTTP proxy thay vì FastCGI
fastcgi_pass http://127.0.0.1:9000;
# SAI — đường dẫn socket không tồn tại
fastcgi_pass unix:/var/run/php-fpm.sock;
# ĐÚNG — không có http://, chỉ host:port
fastcgi_pass 127.0.0.1:9000;
# ĐÚNG — socket (kiểm tra đường dẫn tồn tại)
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
Kiểm tra socket có tồn tại và PHP-FPM đang lắng nghe trên đó không:
# Kiểm tra file socket
ls -la /run/php/php8.1-fpm.sock
# Hoặc kiểm tra cổng TCP
ss -tlnp | grep php
# Kết quả phải hiển thị: LISTEN ... 127.0.0.1:9000
Sau đó đối chiếu với cấu hình PHP-FPM:
grep 'listen =' /etc/php/8.1/fpm/pool.d/www.conf
# listen = /run/php/php8.1-fpm.sock
# hoặc
# listen = 127.0.0.1:9000
Giá trị trong fastcgi_pass phải khớp chính xác — từng ký tự một.
Bước 3: Kiểm tra các directive FastCGI bắt buộc
Một location block PHP-FPM tối giản nhưng hoạt động đúng trông như sau:
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
Hai directive dễ bị bỏ sót. Không có SCRIPT_FILENAME, PHP-FPM không biết file nào cần thực thi và trả về phản hồi rỗng hoặc sai định dạng. Không có include fastcgi_params, hàng loạt biến môi trường bắt buộc sẽ không được thiết lập.
Đảm bảo file đó tồn tại:
ls -la /etc/nginx/fastcgi_params
cat /etc/nginx/fastcgi_params
Bước 4: Kiểm tra PHP xuất output trước header
Bất kỳ output nào trước lệnh header() — dù chỉ là một khoảng trắng, UTF-8 BOM, hay một PHP notice — đều khiến PHP-FPM gửi nội dung thô trước. Nginx nhận nội dung đó như header phản hồi, không phân tích được, và ghi lại đúng lỗi này.
Bật ghi log lỗi chi tiết trong php.ini để bắt các trường hợp này:
# /etc/php/8.1/fpm/php.ini
log_errors = On
error_reporting = E_ALL
display_errors = Off # Không bao giờ bật trong môi trường production
error_log = /var/log/php8.1-fpm-errors.log
Reload FPM và tái tạo lỗi:
systemctl reload php8.1-fpm
curl -v http://your-domain/problematic-page.php
tail -f /var/log/php8.1-fpm-errors.log
Bước 5: Kiểm tra cài đặt pool PHP-FPM
Khi tải cao — ví dụ 50+ request PHP đồng thời trên máy chủ 2GB RAM — các worker có thể hết bộ nhớ và crash giữa chừng. Crash đó gây ra chính xác lỗi này. Kiểm tra giới hạn pool của bạn:
# /etc/php/8.1/fpm/pool.d/www.conf
pm = dynamic
pm.max_children = 20
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 10
pm.max_requests = 500 # Tái tạo worker sau N request (giúp tránh rò rỉ bộ nhớ)
Muốn theo dõi tình trạng pool theo thời gian thực? Bật status page:
# Bật status page trong cấu hình pool
pm.status_path = /fpm-status
# Sau đó trong Nginx:
location /fpm-status {
allow 127.0.0.1;
deny all;
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
# Truy vấn:
curl http://127.0.0.1/fpm-status
Áp dụng bản sửa lỗi
Không bao giờ reload Nginx mà chưa kiểm tra. Kiểm tra cấu hình trước:
nginx -t
# nginx: configuration file /etc/nginx/nginx.conf test is successful
systemctl reload nginx
Tương tự với PHP-FPM:
php-fpm8.1 -t
# [15-Jan-2024 10:30:00] NOTICE: configuration file /etc/php/8.1/fpm/php-fpm.conf test is successful
systemctl reload php8.1-fpm
Xác nhận kết quả
Theo dõi log lỗi Nginx theo thời gian thực trong khi gửi request kiểm thử:
tail -f /var/log/nginx/error.log &
curl -I https://your-domain/index.php
Phản hồi sạch trông như sau:
HTTP/2 200
content-type: text/html; charset=UTF-8
x-powered-by: PHP/8.1.x
Nếu dòng upstream sent invalid header không còn xuất hiện trong log lỗi, bạn đã hoàn thành.
Phòng ngừa
- Đặt
output_buffering = Ontrong php.ini — nó hấp thụ output sớm không mong muốn trước khi PHP xử lý header, ngăn chặn toàn bộ loại lỗi này - Giữ
display_errors = Offtrong môi trường production. PHP warning ghi ra stdout gây ra đúng lỗi này - Đặt đường dẫn socket ở một nơi và tham chiếu từ cả cấu hình pool PHP-FPM lẫn Nginx — thêm comment trỏ đến vị trí kia
- Chạy
nginx -t && php-fpm8.1 -ttrong pipeline CI/CD trước khi push bất kỳ thay đổi cấu hình nào - Đặt
pm.max_requests = 500trong cấu hình pool — worker được tái tạo sau 500 request, ngăn chặn worker bị rò rỉ bộ nhớ gửi output rác

