Logo
Back to Blog
Web Development August 8, 2026 9 min read

Fix "No Access-Control-Allow-Origin Header Is Present"

O

OmniWebKit Team

Backend Engineering

Share:
Article Cover Image

You write a fetch call, hit refresh, and the console fills with red. The request clearly reached the server, the data is right there in the network tab, and your code still cannot touch it. This error stops more frontend work than any other, and the fix is almost always one line.

Why the No Access-Control-Allow-Origin Header Error Happens

Your server did not tell the browser it was allowed to share the response.

Browsers block a page from reading responses fetched from a different origin unless the server explicitly permits it. Permission arrives as one response header:

Access-Control-Allow-Origin: https://your-app.com

No header, no permission. The request still reached the server and the server still replied — the browser simply refuses to hand the result to your JavaScript.

An origin is scheme plus host plus port. That makes http://localhost:3000 and http://localhost:5173 different origins, which surprises people on their own machine.

Check what your endpoint actually returns with our CORS tester before changing anything.

Confirm What the Server Is Really Sending

Guessing wastes more time than checking.

Open the network tab, click the failing request, and read the response headers. If Access-Control-Allow-Origin is absent, you have confirmed the diagnosis.

One caveat that catches everyone: browsers hide the underlying failure. If your route throws a 500, the error handler often skips the CORS middleware, so the response arrives bare and the browser reports CORS rather than the crash.

In our experience, roughly one CORS report in four is actually a 500 wearing a disguise. Read the server logs before touching the config.

Fixing Blocked by CORS Policy in Express

Install the cors package and configure it explicitly.

const cors = require('cors');

app.use(cors({
  origin: ['https://your-app.com', 'http://localhost:3000'],
  credentials: true,
}));

Two details matter. Register it before your routes, or requests are handled before the headers get added. And list origins explicitly rather than reflecting whatever arrives.

Reflecting the request origin back is a common shortcut that quietly allows every site on the internet. With credentials enabled that is a genuine security hole.

The other trap is error handling. Your CORS middleware runs on the happy path; make sure your error handler adds the header too, or every 500 looks like a CORS bug.

Adding the Access Control Allow Origin Header in Nginx

Three lines, plus one for the preflight.

location /api/ {
  add_header 'Access-Control-Allow-Origin' 'https://your-app.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;
  }
}

That always flag is the part people miss. Without it, Nginx skips the header on error responses, which recreates the exact problem described above.

Vary: Origin matters if anything caches. Without it a CDN can store the response for one origin and serve it to another, producing failures that appear at random.

Why the CORS Error Only Shows Up on localhost

Because your dev server is a different origin from your API.

In production the frontend and API often share a domain, so no cross-origin request happens and CORS never applies. Locally your app runs on one port and the API on another, which makes them different origins.

Two clean fixes. Add your localhost origin to the allowed list, which is what most teams do. Or proxy through the dev server so the browser only ever sees one origin:

// vite.config.js
server: {
  proxy: { '/api': 'http://localhost:8080' }
}

We prefer the proxy for local work. It keeps development origins out of your production configuration, which is one fewer thing to forget to remove.

When You Do Not Control the API

You cannot fix it from the browser. Something server-side has to sit in between.

Three legitimate options:

  • Your own backend proxy. Your server calls the API and returns the result. Server-to-server requests are not subject to CORS.
  • A dev-server proxy. Fine locally, useless in production.
  • Ask the provider. Many public APIs will add your origin if you ask.

What does not work: mode: 'no-cors'. It silences the error and returns an opaque response with status 0 and an empty body. You get quiet failure instead of a loud one.

Public CORS proxies are a fourth option we would think hard about. They see every request you send, including auth tokens, and they go down without warning.

Wrapping Up

The no Access-Control-Allow-Origin header error means the server never granted permission. Add the header, add it on error responses too, and name your origins explicitly.

Check the server logs before the config — a surprising share of these are crashes in disguise.

If the preflight itself is failing rather than the main request, see fixing a failing CORS preflight. For the background, what CORS is and why it exists explains what the browser is protecting.

Frequently Asked Questions

I added the header and it still fails. What am I missing?

+
Check what happens on an error response. Many frameworks add CORS headers in middleware that never runs when a route throws, so a 500 arrives with no headers at all and the browser reports a CORS failure instead of your real error.

Why does the browser hide the real error message?

+
Because it blocks the response before your JavaScript can read it. All your code sees is a network failure, which is why the console message mentions CORS rather than the 500 underneath. Check the server logs, not the browser.

Can I fix this from the frontend?

+
Not really, and every trick that claims otherwise is either a proxy or a lie. CORS is enforced by the browser based on what the server sends, so the header has to come from the server. A dev-server proxy is the only legitimate frontend workaround.

Does mode: no-cors solve it?

+
It stops the error and gives you a response you cannot read. The status is always 0 and the body is always empty, which is almost never what you wanted. We have watched teams ship that thinking it worked.

Why does it break in production but not staging?

+
The allowed origin list usually names the staging domain and nothing else. An origin is scheme, host and port together, so https://app.example.com and https://www.app.example.com are separate entries. Check for a missing www as well.

Is a 301 redirect causing this?

+
It can be, and the message gives no hint. Browsers do not follow redirects during a preflight at all, so a redirect from http to https aborts the whole thing. Point your request at the final URL.

My CDN is stripping the header. How do I tell?

+
Request the endpoint directly, bypassing the CDN, and compare the headers. Some CDN configurations drop unknown response headers or cache one origin response and serve it to everyone. Sending Vary: Origin prevents the second problem.

Should I just allow every origin with a star?

+
Only for genuinely public data. A wildcard means any website can read the response in a visitor browser, which is fine for a public price feed and wrong for anything user-specific. It also disables cookies entirely.

Why do some libraries need the header on the OPTIONS response too?

+
Because a preflight is a separate request with its own response. Adding the header only to your GET handler leaves the OPTIONS reply bare, so the browser rejects the request before the GET ever runs.

Does this error mean my API is insecure?

+
No, it means the opposite — the browser is doing its job. CORS restricts what a page can read, not what your server exposes. Your API still needs real authentication regardless of what CORS allows.

Tags

#CORS#Debugging#HTTP#API