Cùng một mã nguồn PHP và môi trường Nginx, cùng mở một trang quản trị (admin tool) có lượng dữ liệu render lớn F5 lúc được, lúc bị ngắt cụt mã HTML giữa chừng (trang web mất chân trang, giao diện vỡ, thiếu dữ liệu).

 

1. Hiện tượng (Symptom)#

  • Triệu chứng: Cùng một mã nguồn PHP và môi trường Nginx, cùng mở một trang quản trị (admin tool) có lượng dữ liệu render lớn:

  • Một server chạy bình thường.

  • Một server gặp tình trạng tải trang chập chờn: F5 lúc được, lúc bị ngắt cụt mã HTML giữa chừng (trang web mất chân trang, giao diện vỡ, thiếu dữ liệu).

  • Đặc điểm gây nhiễu: Các thông số tài nguyên cốt lõi giữa 2 bên hoàn toàn giống nhau (memory_limit = 4096M, max_execution_time = 0, output_buffering = 0, cấu hình pool PHP-FPM đồng nhất).


2. Nguyên nhân gốc rễ (Root Cause)#

Cơ chế đệm response của Nginx kết hợp FastCGI:

  1. Khi PHP trả dữ liệu về Nginx, nếu payload nhỏ hơn bộ đệm trong RAM (fastcgi_buffer_size / fastcgi_buffers, thường là 4KB–64KB tùy OS/arch), Nginx stream trực tiếp về client mượt mà.
  2. Khi dữ liệu HTML vượt quá kích thước buffer RAM, Nginx bắt buộc phải băm (hash) và ghi phần dữ liệu vượt ngưỡng vào thư mục tạm trên ổ cứng (fastcgi_temp/).
  3. Lỗi phát sinh:
  • Thư mục cài đặt Nginx hoặc thư mục fastcgi_temp/ bị mất cờ thực thi (x - execute/traverse) hoặc bị phân quyền sai thành root:root.
  • Worker process của Nginx (chạy bằng user thường, ví dụ appuser) không có quyền tạo thư mục con (các bucket 0/, 1/,... 9/) và không ghi được file tạm.
  • Ngay khi đụng lỗi ghi file, Nginx ngắt ngang kết nối với backend (upstream prematurely closed connection), khiến trình duyệt chỉ nhận được một nửa trang HTML.
  1. Tại sao F5 lúc được lúc không?
  • Các request trả về payload nhỏ hoặc lúc mạng truyền tải kịp xả buffer RAM thì trang tải bình thường.
  • Các request có dữ liệu lớn hơn hoặc mạng nghẽn buộc Nginx phải ghi đĩa thì lập tức bị ngắt.

3. Cách điều tra & Dấu hiệu nhận diện (Investigation)#

Dấu hiệu 1: Soi quyền thư mục đệm FastCGI

ls -la /opt/webstack/nginx/fastcgi_temp/
  • Bình thường: Nginx tự sinh ra các thư mục con từ 0 đến 9 thuộc quyền sở hữu của user chạy worker:
drwx------ 102 appuser appuser 4096 ... 0
drwx------ 102 appuser appuser 4096 ... 1
...
  • Bị lỗi: Thư mục trống rỗng, không tạo được thư mục con, hoặc thư mục cha thiếu cờ x:
drw------- 2 appuser root 4096 ... .
drw------- 11 root    root 4096 ... ..

Dấu hiệu 2: Nginx error log Xem file /opt/webstack/nginx/logs/error.log thường xuất hiện một trong hai lỗi:

[crit] ... open() "/opt/webstack/nginx/fastcgi_temp/..." failed (13: Permission denied)
[error] ... upstream prematurely closed connection while reading response header from upstream

4. Quy trình khắc phục chuẩn (Resolution)#

Cấp lại quyền thực thi (traverse) cho thư mục cha và phân quyền đầy đủ cho user dịch vụ đối với tất cả thư mục đệm:

# Giả sử Nginx chạy độc lập tại /opt/webstack/nginx và worker chạy dưới user:group là appuser:appuser

# 1. Mở cờ thực thi thư mục gốc Nginx để worker truy cập được cây thư mục con
chmod 755 /opt/webstack/nginx

# 2. Gán quyền sở hữu và cấp toàn quyền cho user dịch vụ tại fastcgi_temp
chown -R appuser:appuser /opt/webstack/nginx/fastcgi_temp
chmod 700 /opt/webstack/nginx/fastcgi_temp

# 3. Phân quyền tương tự cho các thư mục đệm khác để tránh lỗi tương tự khi upload file/proxy
chown -R appuser:appuser /opt/webstack/nginx/*_temp
chmod 700 /opt/webstack/nginx/*_temp

# 4. Reload lại Nginx
/opt/webstack/nginx/sbin/nginx -s reload

5. Bài học rút ra (Post-Mortem Takeaways)#

  • Tránh chạy Nginx trực tiếp bằng root khi deploy/debug: Nếu lỡ gõ sudo nginx trực tiếp bằng binary thay vì dùng service wrapper, Nginx master process có thể tạo các file tạm/thư mục tạm dưới quyền root:root, khiến worker process (chạy user thường) sau đó không còn quyền ghi.
  • Cờ x trên thư mục: Một thư mục dù có quyền đọc r, nhưng nếu thiếu quyền x (execute) thì user không thể truy cập vào bên trong (cd) hoặc mở file bên trong thư mục đó.
  • Kinh nghiệm chẩn đoán lỗi đứt response: Khi một trang HTML lớn bị cắt cụt, ưu tiên kiểm tra theo thứ tự:
  1. memory_limit của PHP (xem có Fatal Error out-of-memory).
  2. Phân quyền thư mục fastcgi_temp của Nginx (lỗi ghi file đệm).
  3. Timeout (fastcgi_read_timeout / max_execution_time).
711 words · ~3 min read

Bài đăng phổ biến từ blog này

Gọi hàm nhiều cấp trong MIPS

Đệ quy trong MIPS