You’ve made a fetch request. The network tab shows it fired. But in the console there’s a wall of red: Access to fetch at ‘https://api.example.com’ from origin ‘https://yourapp.com’ has been blocked by CORS policy.
Before diving into explanations — here’s the fastest diagnostic path. Open DevTools → Network tab → find the failed request → look at the Response Headers. If you don’t see Access-Control-Allow-Origin, your server is not sending CORS headers. That’s the fix. The rest of this article explains exactly what to send and why.
What CORS Actually Is
CORS — Cross-Origin Resource Sharing — is enforced by the browser, not your server. Your API doesn’t inherently block cross-origin requests. Your browser does, on behalf of users, to prevent scripts on evil.com from silently reading your banking data.
The browser checks: “Is the response from that API telling me it’s okay for this origin to read it?” If not, it blocks the response — even though the server already processed the request and returned a 200. The server never knows why the client discarded it.
This matters when you’re debugging: the error is always on the client side. The server needs to tell the browser “yes, this origin is allowed.” That’s what the CORS response headers do.
Simple Requests vs Preflight Requests
Not all cross-origin requests behave the same way. The browser distinguishes two types.
Simple requests are GET or POST requests with plain text or form-encoded bodies and a small set of allowed headers. The browser sends them directly and checks the response for Access-Control-Allow-Origin.
Preflight requests happen first when your request doesn’t meet those conditions — for example, when you send a PUT 或 DELETE, include a custom header like Authorization 或 Content-Type: application/json, or send credentials. The browser fires an automatic OPTIONS request to the same URL before your actual request. If the server doesn’t respond to that OPTIONS call with the right CORS headers, your actual request never goes out.
If you’re seeing OPTIONS requests in your network tab that return 404 or 405, that’s why your requests are failing. Your server needs to handle OPTIONS for every route that receives cross-origin traffic.
The Headers That Matter
Getting the cors headers fix right means understanding what each response header actually controls:
Access-Control-Allow-Origin— which origins can read the response. Either a specific origin (https://yourapp.com) or*for any origin.Access-Control-Allow-Methods— which HTTP methods are permitted (e.g.,GET, POST, PUT, DELETE, OPTIONS).Access-Control-Allow-Headers— which request headers the browser is allowed to send (e.g.,Authorization, Content-Type).Access-Control-Allow-Credentials— whether cookies and auth headers can be sent with the request. Must betrueexplicitly.Access-Control-Max-Age— how long in seconds the browser should cache the preflight response.
The Wildcard Trap
Using Access-Control-Allow-Origin: * is the fastest way to open up your API — but it breaks the moment you add credentials. When Access-Control-Allow-Credentials: true is required, the wildcard is rejected by the browser. You must specify the exact origin:
# This will fail with credentials:
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
# This works:
Access-Control-Allow-Origin: https://yourapp.com
Access-Control-Allow-Credentials: true
If you have multiple allowed origins, read the request’s Origin header and echo it back conditionally — don’t concatenate them.
Common CORS Errors and What They Mean
The browser error message usually tells you exactly what’s missing. Here’s a quick reference:
| Error message | What it means | How to fix |
|---|---|---|
| No ‘Access-Control-Allow-Origin’ header is present | Server sent no CORS headers at all | 添加 Access-Control-Allow-Origin to the response |
| The value of the ‘Access-Control-Allow-Origin’ header … does not match the supplied origin | Origin mismatch — server returned wrong or wildcard origin with credentials | Echo the request’s Origin header conditionally; remove wildcard when using credentials |
| Method PUT is not allowed by Access-Control-Allow-Methods | The HTTP method isn’t listed in the allowed methods header | Add the missing method to Access-Control-Allow-Methods |
| Request header ‘Authorization’ is not allowed by Access-Control-Allow-Headers | Custom header is missing from the allowed list | Add the header to Access-Control-Allow-Headers |
| Response to preflight request doesn’t pass access control check | OPTIONS request returned wrong status or missing headers | Handle OPTIONS explicitly; return 200/204 with correct headers |
| Credential is not supported if the CORS header ‘Access-Control-Allow-Origin’ is ‘*’ | Wildcard used with credentials mode | Replace * with explicit origin; add Access-Control-Allow-Credentials: true |
How to Configure CORS in Express, FastAPI, and Nginx
Express (Node.js)
const cors = require('cors');
app.use(cors({
origin: 'https://yourapp.com',
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400
}));
// Handle preflight for all routes
app.options('*', cors());
FastAPI (Python)
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://yourapp.com"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
Nginx
location /api/ {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://yourapp.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Max-Age' 86400;
add_header 'Content-Length' 0;
return 204;
}
add_header 'Access-Control-Allow-Origin' 'https://yourapp.com';
add_header 'Access-Control-Allow-Credentials' 'true';
proxy_pass http://backend;
}
Note the Nginx pattern: you need add_header in both the OPTIONS block and the main block. Headers set inside if blocks don’t carry over to the outer block.
Your Debugging Checklist
When you hit a CORS error, work through this in order:
- Read the full error message — it tells you exactly which header or value is wrong.
- Check the network tab — look at the actual response headers, not what you think you configured.
- Check for an OPTIONS request — if it’s failing or missing, your server isn’t handling preflight.
- Verify the origin matches exactly — trailing slashes, HTTP vs HTTPS, and port numbers all matter.
- Remove the wildcard if you’re using credentials — they’re mutually exclusive.
- Confirm headers aren’t stripped by a proxy — Nginx and CDNs sometimes strip or override CORS headers.
One trap easy to miss: if your API sits behind a reverse proxy or CDN, that layer may add its own Access-Control-Allow-Origin header that conflicts with what your application server returns. When two of those headers appear in a single response, the browser rejects the whole thing. Always check the raw response headers at the network level — not just what your app code emits.
Another edge case: some frameworks only attach CORS headers to routes that match a registered handler. If you’re hitting a 404 or an unregistered route, the CORS middleware may never run, and you’ll see the “no header present” error even though your config looks correct. Test with a valid endpoint first.
If you need to quickly generate the exact CORS headers your server should return, the IO Tools CORS Headers Builder lets you configure origin, methods, and credentials and outputs the correct header block ready to paste into your server config.
A Note on Security
CORS is not an API security mechanism. Setting Access-Control-Allow-Origin: * does not make your API public in any meaningful attack-surface sense — curl, Postman, and server-to-server calls are never subject to CORS restrictions. Only browser-based JavaScript is. If your API needs authentication, enforce it at the API level with tokens or sessions. CORS headers only tell browsers which origins are allowed to read responses from JavaScript. They are orthogonal to actual access control.
With that context, you can make sensible decisions about your CORS configuration rather than either locking everything down out of misplaced security concern or opening everything up without understanding the tradeoff.
