Home / Tutorials / HTTP

HTTP · Network · Degraded

Fix: No 'Access-Control-Allow-Origin' header is present (CORS)

Error has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource

By the Erzon engineering team · A step-by-step fix, not a forum thread

When the browser console says a request has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource, the request usually reached the server and the server usually answered. The browser then enforced the same-origin policy: JavaScript on origin A asked for a resource on origin B, the response did not carry a header authorizing origin A to read it, so the browser withheld the response. The fix always lands on the server that owns the resource. Nothing you do in frontend code can grant the permission the server did not send.

Access to fetch 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.

What this error means

Browsers isolate origins (scheme plus host plus port) from each other. A page on https://app.example.com may send a request to https://api.example.com, but it may only read the response if the API says so by including Access-Control-Allow-Origin with either the requesting origin or *. For requests that are not “simple” (any method beyond GET, HEAD, and simple POST, or any custom header such as Authorization or Content-Type: application/json), the browser first sends a preflight OPTIONS request, and the actual request is only made if the preflight response approves the method and headers.

This error means that opt-in never arrived: the server omitted the header, the preflight failed, or something between the app and the browser (proxy, CDN, error page) stripped or skipped it.

Common causes

  • The server simply never sets CORS headers because the API was built for same-origin use.
  • Preflight failures: the OPTIONS request hits an auth middleware and gets 401, falls through routing to a 404 or 405, or is rejected before any CORS logic runs. The browser reports the same missing-header error.
  • Credentials plus wildcard: the frontend sends cookies with credentials: "include" and the server answers Access-Control-Allow-Origin: *, which browsers reject for credentialed requests.
  • Headers only on the success path: error responses, redirects, and proxy-generated error pages (a 502 from Nginx, for example) carry no CORS headers, so failures show up as CORS errors.
  • An origin allowlist that does not match exactly: http versus https, a missing port, www versus apex, or a trailing slash in the configured value (the Origin header never has one).
  • A cache or CDN serving a response stored for a different origin because Vary: Origin was missing.

How to fix it

STABILIZE first. Confirm what the server actually returns, because the browser collapses many distinct failures into this one message. Send the preflight by hand:

curl -i -X OPTIONS https://api.example.com/data \
  -H "Origin: https://app.example.com" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: content-type,authorization"

Then the real request:

curl -i https://api.example.com/data -H "Origin: https://app.example.com"

DIAGNOSE from what comes back. A 200 with no Access-Control-Allow-Origin means the server has no CORS handling for this route. A 401, 404, or 405 on the OPTIONS request means preflight is being eaten by auth or routing before CORS runs. Headers present here but the browser still failing usually means the failing response is a different one, an error page or a cached copy, so check the exact status and response headers in the browser’s Network tab.

FIX it on the server. In Express, put the CORS middleware before auth and routes so it also covers preflight and errors:

const cors = require("cors");

const allowlist = ["https://app.example.com", "https://staging.example.com"];

app.use(cors({
  origin: (origin, cb) => cb(null, !origin || allowlist.includes(origin)),
  credentials: true,
  methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
  allowedHeaders: ["Content-Type", "Authorization"],
  maxAge: 86400,
}));

The equivalent raw headers, for any stack, on both preflight and actual responses:

Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
Vary: Origin

Echo the validated origin, never reflect it blindly, and never combine * with Allow-Credentials: true. If the API sits behind Nginx and you handle CORS there, answer preflight directly and mark headers always so they are attached to error responses too:

location /api/ {
    if ($request_method = OPTIONS) {
        add_header Access-Control-Allow-Origin  $http_origin 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 Access-Control-Max-Age 86400 always;
        return 204;
    }
    add_header Access-Control-Allow-Origin $http_origin always;
    add_header Vary Origin always;
    proxy_pass http://app;
}

Do not “fix” this in the browser. Disabling web security flags, browser extensions that inject the header, and mode: "no-cors" (which silently gives you an unreadable response) only hide the problem on one developer’s machine. Every real user still fails. A same-origin proxy in front of the API is a legitimate architecture choice, but it works because it removes the cross-origin request, not because it tricks the check.

How to prevent it

  • Handle CORS in exactly one place per service, as the first middleware, so preflight, errors, and every route get consistent headers.
  • Keep the origin allowlist in config per environment, and match origins exactly: scheme, host, and port, no trailing slash.
  • Always send Vary: Origin when the allowed origin is dynamic, so CDNs and caches never cross-serve headers.
  • Test preflight in CI with the curl requests above against staging, so a middleware reorder cannot silently break it.
  • Watch for CORS errors that are really 5xx errors underneath: fix the outage, not the headers.

When to call a senior engineer

Call for help when the headers look right but browsers still block requests intermittently, when a CDN or multi-proxy chain is mangling preflight and nobody owns the full path, or when credentials, subdomains, and third party embeds interact in ways that turn every deploy into a CORS regression. Erzon engineers can trace the exact request the browser is rejecting, find the hop that drops the header, and leave you with one correct CORS layer instead of five conflicting ones.

Related errors we fix

Still down after this?

Want a senior engineer to just fix it?

Paste this exact error into the form. A senior engineer replies within one business hour with what is likely wrong and a flat quote. Triage is free.

Book a fix

← Back to all error fixes

Still down?

Get a senior engineer on it now

Leave your email and a line about what is happening. A senior engineer replies within one business hour with what is likely wrong and a flat quote. Triage is free.

Or email [email protected]. Same engineers, same one-hour clock.