CORS Explained: Why It's a Server-Side Fix for a Browser-Side Error, and How to Debug It
A CORS error appears in the browser console as if it's a client-side problem β but it's actually caused by missing response headers from the server, which is why "it works in curl/Postman but fails in the browser" is the classic CORS symptom. Here's same-origin policy, the key CORS headers, simple vs preflighted requests, and a systematic debugging approach.
By sadiqbd Β· June 16, 2026
CORS errors are one of the most common sources of confusion for developers β not because the underlying concept is complicated, but because the error message appears in a place that makes the actual cause hard to see
"Access to fetch at 'https://api.example.com/data' from origin 'https://app.example.com' has been blocked by CORS policy" β this error appears in the browser console when a cross-origin request is blocked, and it's one of the most-searched error messages in web development. The error is genuinely caused by missing response headers from the server β but it appears as a client-side error in the browser, which is why developers often first look for a fix on the wrong side of the request.
What "cross-origin" actually means
An "origin" in web terms is the combination of scheme (http/https), domain, and port. Two URLs have the same origin only if all three match exactly:
https://app.example.comandhttps://app.example.com/page2β same origin (path doesn't matter)https://app.example.comandhttps://api.example.comβ different origins (different subdomain)https://app.example.comandhttp://app.example.comβ different origins (different scheme)https://app.example.com:443andhttps://app.example.com:8443β different origins (different port)
The Same-Origin Policy is a browser security mechanism that, by default, prevents JavaScript running on one origin from making certain types of requests to a different origin and reading the response. This is a browser security feature β it doesn't prevent the request from being sent in many cases, but it prevents the JavaScript code from reading the response unless the server explicitly permits it.
Why CORS exists: the security problem it solves
Without the Same-Origin Policy, a malicious website could include JavaScript that makes requests to, say, your bank's website (using your browser, which may have active login cookies for your bank) and read the responses β potentially accessing your account information, simply because you visited the malicious site while logged into your bank in another tab.
CORS (Cross-Origin Resource Sharing) is the mechanism by which a server can explicitly opt in to allowing specific cross-origin requests β essentially, the server says "yes, I'm okay with JavaScript running on [these origins] reading my responses," via specific HTTP response headers.
The key CORS response headers
Access-Control-Allow-Origin: the most fundamental CORS header β specifies which origin(s) are permitted to read the response. Can be a specific origin (https://app.example.com), or * (any origin β though * has limitations, particularly when credentials are involved, as discussed below).
Access-Control-Allow-Methods: for "preflighted" requests (discussed below), specifies which HTTP methods (GET, POST, PUT, DELETE, etc.) are allowed for cross-origin requests to this resource.
Access-Control-Allow-Headers: specifies which request headers (beyond a small set of "safe" headers that don't trigger preflight) are allowed to be sent in cross-origin requests.
Access-Control-Allow-Credentials: if set to true, allows the request to include credentials (cookies, HTTP authentication) β but when this is true, Access-Control-Allow-Origin cannot be *; it must specify the exact origin, for security reasons (allowing credentialed requests from any origin would defeat much of the purpose of credential-based authentication).
Simple requests vs preflighted requests
Not all cross-origin requests trigger the same browser behaviour:
"Simple" requests (meeting specific criteria β certain methods like GET/POST/HEAD, certain content types, no custom headers beyond a small allowed set) are sent directly, and the browser checks the Access-Control-Allow-Origin header on the actual response to decide whether to allow the JavaScript code to read it.
"Preflighted" requests (most requests with custom headers, methods like PUT/DELETE/PATCH, or certain content types like application/json in some configurations) trigger the browser to first send an OPTIONS request to the same URL β this "preflight" request asks the server "would you allow a request with these characteristics from this origin?" β and the server responds with the relevant Access-Control-Allow-* headers on the OPTIONS response. Only if the preflight response indicates the actual request would be allowed does the browser proceed to send the actual request.
This is a common source of confusion: a developer might correctly add CORS headers to their main API endpoint's response, but if their server doesn't have a handler that responds appropriately to the OPTIONS preflight request for that same path (returning the right Access-Control-Allow-* headers on the OPTIONS response specifically), the preflight fails, and the browser never even sends the actual request β the error in the console relates to the failed preflight, which can look similar to a failure on the main request if you're not specifically looking at the network tab to distinguish the OPTIONS request from the actual GET/POST/etc.
Debugging CORS errors systematically
Step 1: identify if this is actually a CORS issue, or a different error that's being misread as CORS. Sometimes a server error (500 Internal Server Error, or the server being unreachable entirely) can result in a response that also happens to lack CORS headers β and the browser reports "CORS error" because that's the symptom it can observe (no Access-Control-Allow-Origin header on whatever response it got), even though the root cause is that the server errored before it got to the point of setting any headers at all. Checking the actual HTTP status code of the failed request (in the Network tab) β is it a CORS-specific block (often shown with status, but the response itself might be inaccessible to JavaScript due to CORS) or is the underlying request actually returning a 500/502/503 β helps distinguish "server is broken" from "server works but doesn't send CORS headers."
Step 2: check whether the request is "simple" or "preflighted." Look in the Network tab for an OPTIONS request to the same URL, sent just before the actual request. If present, check the OPTIONS response headers specifically β these need the Access-Control-Allow-* headers, separate from whatever the actual GET/POST endpoint returns.
Step 3: check Access-Control-Allow-Origin on the actual response (not just the preflight, if there was one) β this needs to match (or be *, if credentials aren't involved) the origin making the request.
Step 4: if credentials are involved (fetch with credentials: 'include', or cookies needed for the cross-origin request to work) β check that Access-Control-Allow-Credentials: true is present, and that Access-Control-Allow-Origin is the specific origin, not *.
Common server-side CORS configuration patterns
Express.js (Node.js):
const cors = require('cors');
app.use(cors({
origin: 'https://app.example.com',
credentials: true
}));
The cors middleware handles both the preflight OPTIONS responses and adding the appropriate headers to actual responses β a common source of CORS issues in custom implementations is handling only one of these (e.g., manually adding headers to GET/POST responses, but not having any handler for OPTIONS requests at all, resulting in a 404 or 405 for the preflight).
Reverse proxies and CDNs: in some architectures, CORS headers are added at a reverse proxy or CDN layer rather than by the application server directly β if there's a mismatch between what the application sets and what the proxy/CDN passes through or overrides, this can cause CORS headers to be missing or duplicated (duplicated Access-Control-Allow-Origin headers, for instance, can themselves cause the browser to reject the response, since a single value is expected).
How to use the HTTP Headers Checker on sadiqbd.com
- Check the response headers of an API endpoint β verify whether
Access-Control-Allow-Originand related headers are present - Compare OPTIONS vs GET/POST responses β if you can send different request methods to the same URL, comparing the headers returned for OPTIONS (preflight) vs the actual method reveals whether preflight handling is correctly configured separately
- Debug "it works from Postman but not from the browser" issues β tools like Postman don't enforce CORS (CORS is a browser mechanism, not a server-side restriction that affects all clients) β so a request that works in Postman/curl but fails in the browser with a CORS error is consistent with the server working but not sending the CORS headers the browser specifically requires
Frequently Asked Questions
If I disable CORS in my browser, does that mean the server has a security problem? No β browser extensions or flags that "disable CORS" only change your own browser's enforcement of the Same-Origin Policy for your own browsing session; they don't change anything about the server, and don't help any other user. This is sometimes used for local development convenience, but it's not a "fix" in any general sense β and importantly, doesn't reflect how the request would behave for actual users with standard browser configurations.
Why does my request work fine when I curl it, but fail with a CORS error in the browser?
Because CORS is enforced by the browser, not by the server or the network β curl (and similar command-line tools) simply make the HTTP request and show you the response, with no Same-Origin Policy enforcement at all. The server response might be identical in both cases β but the browser additionally checks for CORS headers before allowing your JavaScript to read that response, which curl has no equivalent restriction for.
Is the HTTP Headers Checker free? Yes β completely free, no sign-up required.
Try the HTTP Headers Checker free at sadiqbd.com β inspect response headers for any URL, including CORS-related headers.