Lỗi gặp phải
Endpoint upload file của bạn đang trả về lỗi 500. Mở /var/log/nginx/error.log ra và bạn sẽ thấy nội dung tương tự như sau:
open() "/var/lib/nginx/tmp/client_body/0000000001" failed (13: Permission denied) while reading client request body, client: 203.0.113.45, server: example.com, request: "POST /upload HTTP/1.1"
Điều kỳ lạ là: các file nhỏ vẫn upload bình thường. Chỉ khi file đủ lớn — thường là vài kilobyte trở lên — thì lỗi 500 mới xuất hiện. Đây chính là manh mối quan trọng.
Nguyên nhân
Nginx không stream trực tiếp các POST body lớn lên ứng dụng của bạn. Thay vào đó, nó buffer chúng trước — ghi vào file tạm trong thư mục client_body_temp_path (mặc định: /var/lib/nginx/tmp/client_body/) trước khi chuyển lên upstream. Nếu tiến trình worker không có quyền sở hữu thư mục đó, việc ghi sẽ thất bại và bạn nhận được lỗi permission denied.
Nguyên nhân hầu như luôn xuất phát từ một trong bốn tình huống sau:
- Bạn đổi nguồn package — ví dụ, chuyển từ repo Nginx của distro sang repo Nginx chính thức. User chạy mặc định đã thay đổi từ
www-datasangnginx(hoặc ngược lại), nhưng quyền sở hữu thư mục tạm chưa được cập nhật theo - Thư mục
/var/lib/nginx/bị tạo lại hoặc restore từ backup với owner sai - Bạn chỉnh thủ công directive
usertrongnginx.confmà không cập nhật quyền sở hữu thư mục - Khi migrate server, thư mục tmp chưa được khởi tạo
Bước 1 — Xác định nguyên nhân gốc rễ
Đầu tiên, kiểm tra xem các worker của Nginx đang chạy với user nào:
ps aux | grep nginx | grep worker
Kết quả sẽ trông giống một trong hai dạng sau:
www-data 1234 0.0 0.1 nginx: worker process
# hoặc
nginx 1234 0.0 0.1 nginx: worker process
Tiếp theo, kiểm tra chủ sở hữu của thư mục tạm:
ls -la /var/lib/nginx/tmp/
Nếu hai thông tin này không khớp, đó chính là thủ phạm. Ví dụ:
drwx------ 2 nginx nginx 40 Apr 21 02:14 client_body
# Worker đang chạy bằng www-data — đây là sự không khớp
Bước 2 — Sửa phân quyền
Thay đổi quyền sở hữu để khớp với user mà worker đang chạy. Trên Ubuntu/Debian thường là www-data; trên CentOS/RHEL thường là nginx:
# Ubuntu/Debian
chown -R www-data:www-data /var/lib/nginx/
# CentOS/RHEL
chown -R nginx:nginx /var/lib/nginx/
Nếu thư mục tmp chưa tồn tại, hãy tạo trước:
mkdir -p /var/lib/nginx/tmp/client_body
chown -R www-data:www-data /var/lib/nginx/tmp/
chmod 700 /var/lib/nginx/tmp/client_body
Bước 3 — Kiểm tra directive user trong nginx.conf
Chỉ sửa phân quyền thôi chưa đủ nếu file config vẫn trỏ đến user sai:
grep -E '^user' /etc/nginx/nginx.conf
Dòng đó phải khớp với thực tế. Trên Ubuntu, nếu ghi user nginx; nhưng Nginx lại được cài để chạy bằng www-data, hãy sửa lại:
# /etc/nginx/nginx.conf
user www-data; # phải khớp với user thực tế trên hệ thống
Nhân tiện, nên thiết lập rõ ràng đường dẫn temp — giúp config tự mô tả và dễ debug về sau:
http {
client_body_temp_path /var/lib/nginx/tmp/client_body 1 2;
client_max_body_size 100m;
# ... phần còn lại của config
}
Bước 4 — Reload Nginx
# Kiểm tra config trước
nginx -t
# Reload không gián đoạn — zero downtime
systemctl reload nginx
Bước 5 — Xác nhận đã sửa xong
Mở một terminal theo dõi error log:
tail -f /var/log/nginx/error.log
Ở terminal khác, thử upload một file có kích thước thực:
# Tạo file test 50MB rồi upload
dd if=/dev/urandom of=/tmp/testfile bs=1M count=50
curl -X POST https://example.com/upload \
-F "file=@/tmp/testfile" \
-w "\nHTTP Status: %{http_code}\n"
Nếu nhận được response 200 (hoặc mã thành công mà app bạn trả về) và error log không có gì mới, tức là bạn đã xử lý xong.
Cách khắc phục thay thế — Chuyển hướng đường dẫn temp
Không thể thay đổi quyền sở hữu dễ dàng? Tình huống phổ biến trong container. Chỉ cần trỏ Nginx đến nơi nó có thể ghi được:
http {
client_body_temp_path /tmp/nginx_client_body 1 2;
# ...
}
Sau đó thiết lập thư mục:
mkdir -p /tmp/nginx_client_body
chown www-data:www-data /tmp/nginx_client_body
nginx -t && systemctl reload nginx
Lưu ý một điểm: /tmp sẽ bị xóa sau mỗi lần reboot. Với môi trường production, hãy chọn đường dẫn tồn tại qua các lần khởi động lại.
Mẹo thêm
Sau khi nâng cấp package Nginx
Chuyển đổi giữa repo của distro và repo Nginx chính thức sẽ âm thầm thay đổi user chạy mặc định. Hãy biến thao tác này thành thói quen sau mỗi lần nâng cấp Nginx:
NGINX_USER=$(ps aux | grep 'nginx: worker' | grep -v grep | awk '{print $1}' | head -1)
echo "Nginx worker user: $NGINX_USER"
chown -R $NGINX_USER:$NGINX_USER /var/lib/nginx/
Trong Docker Container
Các Docker image tùy chỉnh thường bỏ qua việc tạo thư mục /var/lib/nginx/tmp/. Thêm hai dòng này vào Dockerfile để tránh hoàn toàn vấn đề:
RUN mkdir -p /var/lib/nginx/tmp/client_body \
&& chown -R nginx:nginx /var/lib/nginx/
Không chắc dùng giá trị chmod nào?
Unix Permissions Calculator trên ToolCraft cho phép bạn giải mã các giá trị octal một cách trực quan — dán 700 vào và nó hiển thị chính xác quyền được cấp cho owner, group và others. Chạy hoàn toàn trên trình duyệt.
Cố định bằng Ansible/Terraform
Nếu bạn provision server tự động, hãy làm cho thao tác sửa quyền sở hữu trở thành idempotent để nó chạy mỗi lần deploy:
# Ansible task
- name: Ensure nginx tmp directory has correct ownership
file:
path: /var/lib/nginx/tmp/client_body
state: directory
owner: "{{ nginx_user }}"
group: "{{ nginx_user }}"
mode: '0700'
recurse: yes

