Skip to content
RU

"Has Been Blocked by CORS Policy": Every Variant and Its Fix

What it means: The API answered, but its headers did not allow your page's origin to read the response.

  1. Read the reason after "CORS policy:"
  2. Return Access-Control-Allow-Origin for your origin
  3. Answer OPTIONS preflight with 2xx
  4. With credentials, never use *
  5. Add Vary: Origin to cached responses

Test your URL now

Sends a cross-origin request and an OPTIONS preflight to your URL and shows the Access-Control-* headers it returns.

Check your site's CORS →

What "blocked by CORS policy" means

Chrome and Edge print one Console line per blocked request, built in Blink's cors_error_string.cc:

Access to fetch at 'https://api.example.com/v1/items' from origin 'https://app.example.com'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present
on the requested resource.

«fetch» becomes «XMLHttpRequest» or «font» depending on what made the request, and the text after CORS policy: is the actual reason. Firefox words the same check as Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at ... (Reason: CORS header 'Access-Control-Allow-Origin' missing). Status code: 200. (security.properties).

CORS is enforced by the browser, not the server. The request usually reached the API and got an answer; the browser then refused to hand that answer to a script from another origin (scheme, host and port must all match to count as the same origin). The script only receives TypeError: Failed to fetch. That is also why the same call works in curl or Postman: they do not enforce CORS at all.

Each variant of the message and what to change

Text after "CORS policy:"CauseFix on the API
No 'Access-Control-Allow-Origin' header is present on the requested resource.The header is missing, often only on error responses or on a path behind another proxySend it on every response, including 4xx/5xx (nginx: add_header ... always)
The 'Access-Control-Allow-Origin' header has a value '...' that is not equal to the supplied origin.A hard-coded origin, or a cached response for another originEcho an allow-listed Origin; add Vary: Origin
The 'Access-Control-Allow-Origin' header contains multiple values '...', but only one is allowed.Both the app and the proxy add the headerSet it in one layer only
The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.credentials: 'include' or withCredentials with *Return the exact origin and Access-Control-Allow-Credentials: true
Response to preflight request doesn't pass access control check: It does not have HTTP ok status.OPTIONS gets 401, 404, 405 or a redirect targetAnswer OPTIONS with 200 or 204 before auth
Redirect is not allowed for a preflight request.http to https or trailing-slash redirect on OPTIONSCall the final URL directly
Request header field authorization is not allowed by Access-Control-Allow-Headers in preflight response.Header missing from the allow listList it in Access-Control-Allow-Headers
Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.Method missing from the allow listAdd it to Access-Control-Allow-Methods

A preflight is an automatic OPTIONS request the browser sends before a cross-origin call that uses a method other than GET, HEAD or POST, a Content-Type such as application/json, or custom headers like Authorization. Note that Access-Control-Allow-Headers: * does not cover Authorization: Firefox already warns that it must be listed explicitly. More on preflights: CORS preflight.

Behind nginx, Cloudflare or an API gateway

Most real-world cases are a layer problem rather than a missing line of code. The application sets the headers, but nginx answers OPTIONS itself with 405, an authentication layer returns 401 to the preflight because browsers never send credentials on it, or an error page from the proxy (502, 504, 413) goes out without any CORS headers, so a backend failure surfaces as a CORS error. In nginx, add_header only applies to 2xx and 3xx responses unless you add always:

location /v1/ {
    if ($request_method = OPTIONS) {
        add_header Access-Control-Allow-Origin  $http_origin always;
        add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE' always;
        add_header Access-Control-Allow-Headers 'Content-Type, Authorization' always;
        add_header Access-Control-Max-Age 600 always;
        return 204;
    }
    add_header Access-Control-Allow-Origin $http_origin always;
    add_header Vary Origin always;
    proxy_pass http://app;
}

Echoing $http_origin as shown allows every origin; in production, map it against an allow list first. A CDN that caches the response must vary on Origin, or one site's allowed origin is served to another.

How to fix it on the API

  1. Copy the exact reason from the Console. The table above maps each wording to one change.
  2. See what the server returns for your origin:
    curl -sS -D - -o /dev/null https://api.example.com/v1/items \
      -H 'Origin: https://app.example.com'
  3. Replay the preflight with the method and headers your code sends:
    curl -sS -D - -o /dev/null -X OPTIONS https://api.example.com/v1/items \
      -H 'Origin: https://app.example.com' \
      -H 'Access-Control-Request-Method: POST' \
      -H 'Access-Control-Request-Headers: content-type,authorization'
    Expect a 2xx status, your origin in access-control-allow-origin, and the method and headers in the allow lists.
  4. Make errors carry the headers too. Trigger a 404 or 401 with the same Origin header and check that the allow header is still there.
  5. For cookies or HTTP auth, return the exact origin (never *), add Access-Control-Allow-Credentials: true, and set credentials: 'include' on the client.
  6. If you do not control the API, call it from your own backend and return the data from your origin. mode: 'no-cors' does not fix it: it returns an opaque response your script cannot read (MDN).

Background on the header model: CORS explained and how to configure CORS.

Mistakes that keep the error alive

  • Adding CORS headers in front-end code. Access-Control-Allow-Origin is a response header; setting it on the request does nothing except trigger a preflight.
  • Fixing http://localhost:3000 but deploying from https://app.example.com. The origin is compared exactly, including scheme and port.
  • A trailing slash in the allowed origin (https://app.example.com/). An origin has no path.
  • Disabling web security in the browser to test. It hides the error on one machine and proves nothing about production.
  • Allowing * on an API that relies on cookies. Browsers reject that combination, and it is unsafe on private APIs.

How to confirm the fix

curl -sS -D - -o /dev/null -X OPTIONS https://api.example.com/v1/items \
  -H 'Origin: https://app.example.com' -H 'Access-Control-Request-Method: POST' \
  | grep -i '^access-control'
# access-control-allow-origin: https://app.example.com
# access-control-allow-methods: GET, POST, PUT, DELETE

Then reload the page: the Network tab should show the OPTIONS request with a 2xx status followed by the real request, and the Console should stay clean. The CORS checker sends the simple request and the preflight for any URL and grades the answer.

Related errors

Learn more

Sources

Frequently Asked Questions

Can I fix a CORS error from the front-end?

No. Only the server that answers the request can allow your origin. From the front-end you can only route the call through a backend you control.

Why does the API work in Postman but not in the browser?

Postman does not enforce CORS. The browser does, so a missing or wrong Access-Control-Allow-Origin header only breaks the browser call.

Is Access-Control-Allow-Origin: * safe?

Only for public data that needs no cookies or auth. Browsers refuse * with credentials, and private APIs should return an allow-listed origin instead.

Why do I get a CORS error only when the server returns 500?

The error response was generated without CORS headers, often by a proxy. Add the headers to error responses too, for example with nginx add_header ... always.

Does mode no-cors fix the CORS error?

It removes the error but returns an opaque response with no readable body or headers. It is only useful for fire-and-forget requests.

Try the live tool that powered this guide

Free plan — 10 monitors, checks every 5 min, no card required. Upgrade for 1-minute interval and multi-region monitoring.