Common causes
- The API server never sends Access-Control-Allow-Origin for your front-end's origin
- The OPTIONS preflight request returns 404, 405 or a redirect instead of a 2xx with CORS headers
- The server crashes or returns a 500/401 error page that lacks the CORS headers, so the real error is masked as CORS
- Origin mismatch such as www vs non-www, http vs https, or a different port in local development
- Credentials (cookies) are sent but the server replies with * instead of the exact origin plus Access-Control-Allow-Credentials: true
- Fonts or assets on a CDN or subdomain served without CORS headers
How to fix it
- Inspect the failing request. In DevTools > Network, find the request (and any OPTIONS preflight before it) and check its status and response headers. A 4xx or 5xx status means fix that error first.
- Add the header on the API server. Return Access-Control-Allow-Origin with the exact calling origin, for example https://example.com. Use an allow-list of origins rather than reflecting any origin back.
- Handle preflight requests. For requests with JSON bodies, custom headers or methods like PUT/DELETE, the server must answer OPTIONS with 204 and Access-Control-Allow-Methods and Access-Control-Allow-Headers.
- Configure your framework. In Laravel edit config/cors.php (paths, allowed_origins, supports_credentials). In Express use the cors middleware. In WordPress REST API, use the rest_pre_serve_request hook or a vetted plugin.
- Fix credentials mode. If you use credentials: 'include' or withCredentials, the server must send the exact origin (not *) and Access-Control-Allow-Credentials: true, plus Vary: Origin.
- Use a same-origin proxy if you do not control the API. Call the third-party API from your own backend and return the result to the browser, keeping any API keys server-side.
Nginx (CORS for one trusted origin)
location /api/ {
add_header Access-Control-Allow-Origin "https://example.com" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
add_header Vary Origin always;
if ($request_method = OPTIONS) { return 204; }
try_files $uri /index.php?$query_string;
} How to stop it happening again
- Keep an explicit allow-list of front-end origins in one config file
- Send CORS headers on error responses too (the always flag in Nginx)
- Test preflight requests with curl -X OPTIONS during deployment