Nginx add_header Inheritance: Giải mã bí ẩn thiếu hụt các Security Header

intermediate Nginx2026-07-01| Bất kỳ bản phân phối Linux nào (Ubuntu, CentOS, Debian) đang chạy Nginx (tất cả các phiên bản).

Error Message

X-Frame-Options/Content-Security-Policy header không xuất hiện trong response dù đã khai báo ở server block
#nginx#web-security#devops#sysadmin

Bí ẩn về sự biến mất của các Header

Bạn vừa hoàn tất một đợt kiểm tra bảo mật và kết quả thật đáng thất vọng. Bản báo cáo cho rằng trang web production của bạn đang thiếu các header X-Frame-OptionsContent-Security-Policy (CSP). Bạn nhớ rõ là đã thêm chúng vào server block trong nginx.conf. Khi kiểm tra trang chủ, mọi thứ đều hoàn hảo. Tuy nhiên, ngay khi bạn truy cập vào một API endpoint như /api/v1/status, những security header quan trọng đó bỗng dưng biến mất.

Lỗi cụ thể này—X-Frame-Options/Content-Security-Policy header không xuất hiện trong response dù đã khai báo ở server block—là một cái bẫy phổ biến đối với người dùng Nginx.

Quá trình Debug

Tránh phụ thuộc vào công cụ phát triển của trình duyệt (developer tools), vì bộ nhớ đệm (cache) có thể đánh lừa bạn. Thay vào đó, hãy sử dụng curl để xem chính xác những gì máy chủ đang gửi. Chạy thử nghiệm với domain gốc và sau đó so sánh với một đường dẫn con cụ thể.

# Các header có khả năng xuất hiện ở đây
curl -I https://example.com/

# Các header thường biến mất ở đây
curl -I https://example.com/api/data

Nếu lệnh đầu tiên hiển thị các header nhưng lệnh thứ hai thì không, cấu hình của bạn có thể trông giống như đoạn mã này:

server {
    listen 443 ssl;
    add_header X-Frame-Options "SAMEORIGIN";
    add_header Content-Security-Policy "default-src 'self';";

    location /api/ {
        # Dòng duy nhất này phá vỡ mọi thứ bên trên
        add_header X-API-Version "1.0";
        proxy_pass http://backend;
    }
}

Trong kịch bản này, response của /api/ sẽ hiển thị X-API-Version. Thật không may, Nginx sẽ âm thầm loại bỏ các header X-Frame-OptionsCSP cho đường dẫn cụ thể đó.

Tại sao Nginx lại hoạt động như vậy

Hầu hết các chỉ thị (directives) của Nginx được truyền từ cha xuống con một cách liền mạch. Nhưng add_header lại hoạt động như một "người anh em đố kỵ". Nếu một location block định nghĩa dù chỉ một add_header, nó sẽ bỏ qua tất cả các add_header đã được định nghĩa trong các server hoặc http block phía trên nó.

Nginx không hợp nhất (merge) các danh sách header này. Nó ghi đè hoàn toàn lên chúng. Lựa chọn thiết kế này khiến ngay cả những kỹ sư kỳ cựu cũng phải bất ngờ trong các đợt triển khai định kỳ.

Cách khắc phục: Hai phương pháp hiệu quả

Phương pháp 1: Chiến lược "Include" (Best Practice)

Đừng copy-paste các header vào mọi block. Thay vào đó, hãy giữ cho cấu hình của bạn tuân thủ nguyên tắc DRY (Don't Repeat Yourself) bằng cách sử dụng một tệp cấu hình riêng lẻ. Điều này đảm bảo tính nhất quán trên toàn bộ trang web của bạn.

  1. Tạo một tệp tại /etc/nginx/conf.d/security_headers.conf:
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Content-Security-Policy "default-src 'self';" always;
  1. Tham chiếu tệp này trong server block và bất kỳ location block nào cần thêm các header bổ sung:
server {
    include conf.d/security_headers.conf;

    location /api/ {
        include conf.d/security_headers.conf;
        add_header X-API-Version "1.0" always;
        proxy_pass http://backend;
    }
}

Phương pháp 2: Sử dụng Module headers_more

Nếu bạn sử dụng OpenResty hoặc có thể cài đặt các module tùy chỉnh, headers_more là một "cứu cánh". Chỉ thị more_set_headers của nó thông minh hơn nhiều so với mặc định. Nó kế thừa từ các block cha ngay cả khi block con thêm các header riêng.

server {
    more_set_headers "X-Frame-Options: SAMEORIGIN";

    location /api/ {
        more_set_headers "X-API-Version: 1.0";
        proxy_pass http://backend;
    }
}

Với module này, cả hai header sẽ xuất hiện trong response của bạn mà không cần thêm bất kỳ dòng include nào.

Xác minh: Kiểm tra kết quả

Trước khi kết thúc, hãy luôn kiểm tra cú pháp của bạn. Chỉ một dấu chấm phẩy bị thiếu cũng có thể làm sập trang web của bạn.

nginx -t

Nếu bài kiểm tra vượt qua, hãy reload service để áp dụng các thay đổi:

systemctl reload nginx

Finally, verify the specific API path again. You should now see the full stack of headers in your terminal output.

Mẹo chuyên nghiệp cho Header Nginx

- **Cờ "always" là bắt buộc:** Theo mặc định, Nginx chỉ gửi các header cho các phản hồi thành công như 200 hoặc 302. Thêm `always` đảm bảo các security header của bạn vẫn hoạt động trong các lỗi 404 hoặc 500.
- **Kế thừa là tất cả hoặc không có gì:** Hãy nhớ rằng việc thêm một header trong block con sẽ xóa sạch các thiết lập trước đó.
- **Tập trung hóa bảo mật:** Sử dụng các tệp `include` để quản lý các thiết lập bảo mật. Điều này ngăn ngừa lỗi do con người khi bạn thêm các endpoint mới sau này.

Related Error Notes