Lỗi Gặp Phải
Access to XMLHttpRequest at 'https://api.example.com/data' from origin 'https://app.example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Lỗi này xuất hiện trong console của trình duyệt khi frontend (chạy trên một domain) cố gọi đến backend trên domain, subdomain hoặc port khác — mà Nginx không trả về các header CORS cần thiết. Trình duyệt chặn response trước khi JavaScript của bạn kịp nhận được.
Nguyên Nhân
Trình duyệt thực thi chính sách Same-Origin Policy. Nếu origin của frontend khác với origin của backend (khác domain, subdomain hoặc port), trình duyệt sẽ kiểm tra header Access-Control-Allow-Origin trong response. Nginx mặc định không tự thêm header này — bạn phải cấu hình thủ công.
Cách Khắc Phục Từng Bước
1. Tìm File Cấu Hình Nginx
ls /etc/nginx/sites-enabled/
cat /etc/nginx/sites-enabled/your-api.conf
2. Thêm CORS Headers Vào Server Block
Một origin được phép (trường hợp phổ biến nhất)
server {
listen 80;
server_name api.example.com;
location / {
# Xử lý preflight OPTIONS request trước
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://app.example.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept' always;
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
add_header 'Access-Control-Allow-Origin' 'https://app.example.com' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept' always;
proxy_pass http://localhost:8080;
}
}
Cờ always ở mỗi add_header là bắt buộc, không thể bỏ qua. Nếu thiếu, Nginx chỉ đính kèm header vào các response 2xx — các response lỗi (4xx, 5xx) sẽ không có header, khiến trình duyệt chỉ hiện lỗi CORS thay vì lỗi thực sự. Rất khó debug.
Nhiều origin được phép (dev + staging + prod)
Khi cần whitelist nhiều origin cụ thể, dùng block map — đặt bên ngoài block server, thường ở đầu file config hoặc trong /etc/nginx/conf.d/cors.conf:
map $http_origin $cors_origin {
default "";
"https://app.example.com" $http_origin;
"https://staging.example.com" $http_origin;
"http://localhost:3000" $http_origin;
}
server {
listen 80;
server_name api.example.com;
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' $cors_origin always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept' always;
add_header 'Access-Control-Max-Age' 1728000;
return 204;
}
add_header 'Access-Control-Allow-Origin' $cors_origin always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept' always;
add_header 'Vary' 'Origin' always;
proxy_pass http://localhost:8080;
}
}
Header Vary: Origin quan trọng trong trường hợp này — nếu thiếu, CDN hoặc proxy có thể cache response với header của một origin rồi phục vụ nhầm cho origin khác.
Public API — cho phép mọi origin
add_header 'Access-Control-Allow-Origin' '*' always;
Chỉ dùng wildcard cho các API thực sự public. Trình duyệt sẽ từ chối * khi có credentials (cookie, Authorization header) trong request.
Request có cookie hoặc Authorization header
Bạn phải chỉ định đúng origin (không dùng wildcard) và thêm credentials header:
add_header 'Access-Control-Allow-Origin' 'https://app.example.com' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
3. Kiểm Tra Cấu Hình và Reload
# Kiểm tra cú pháp trước
sudo nginx -t
# Reload mà không ngắt kết nối đang có
sudo nginx -s reload
Xác Nhận Đã Sửa Thành Công
curl — cách nhanh nhất để kiểm tra
# Kiểm tra GET request thông thường
curl -I \
-H "Origin: https://app.example.com" \
https://api.example.com/data
# Kiểm tra preflight OPTIONS request
curl -X OPTIONS \
-H "Origin: https://app.example.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Authorization" \
-I https://api.example.com/data
Tìm dòng Access-Control-Allow-Origin: https://app.example.com trong kết quả. Nếu curl hiển thị đúng nhưng trình duyệt vẫn báo lỗi, hãy mở cửa sổ ẩn danh — trình duyệt đôi khi cache lại các lần preflight thất bại.
Kiểm tra bằng DevTools của trình duyệt
Tab Network → click vào request bị lỗi → tab Headers → phần Response Headers. Header phải xuất hiện ở đây. Nếu curl thấy nhưng DevTools không thấy, kiểm tra xem request có đi qua route khác không, hoặc có service worker nào đang chặn nó không.
Những Lỗi Hay Gặp
- Location block con ghi đè header của block cha: Trong Nginx, nếu một block
locationlồng bên trong có bất kỳ directiveadd_headernào, toàn bộadd_headertừ block cha sẽ bị xóa sạch — chúng không được gộp lại. Đây là hành vi gây bất ngờ nhất. Hãy thêm tất cả CORS headers trực tiếp vào từng blocklocationcần dùng. - Backend cũng tự thêm CORS headers: Nếu ứng dụng (Node, Flask, v.v.) đã set CORS headers mà Nginx cũng thêm vào, response sẽ bị trùng lặp —
Access-Control-Allow-Origin: *, https://app.example.com. Trình duyệt sẽ từ chối. Chỉ xử lý CORS ở đúng một nơi. - HTTP và HTTPS là hai origin khác nhau:
http://app.example.comvàhttps://app.example.comlà hai origin hoàn toàn khác nhau. Đảm bảo scheme trong config khớp chính xác với những gì trình duyệt gửi lên. - Wildcard subdomain không được hỗ trợ sẵn: Nginx không thể xử lý
*.example.comtheo cách tự nhiên. Dùng cách tiếp cậnmapở trên và liệt kê từng subdomain cụ thể. - Thiếu handler cho preflight: Trình duyệt gửi OPTIONS request trước khi thực hiện POST/PUT/DELETE có custom headers. Nếu Nginx chuyển OPTIONS thẳng về backend mà backend không xử lý được, preflight sẽ thất bại và request thật sự không bao giờ được gửi đi.

