CORS Error "No Access-Control-Allow-Origin": The Fix

A CORS error means your API didn't return an Access-Control-Allow-Origin header that matches your frontend. Fix it on the server, not in the browser.
Every web developer meets this red console line sooner or later: Access to fetch at 'https://api.example.com/...' from origin 'https://www.example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. It looks like a frontend bug. It isn't. The browser is enforcing a rule, and only the server you're calling can change the answer. I run this exact split on my own site: a Next.js frontend on www.rakibulinux.com talking to a NestJS API on api.rakibulinux.com, so this guide is the checklist I use when the two stop talking.
Key takeaways
- CORS is enforced by the browser, configured on the server. Changing your
fetch()call or installing a browser extension doesn't fix it for your users. - The origin must match exactly: scheme, host and port.
https://example.comandhttps://www.example.comare different origins. - Cookies or auth with credentials can't use
*. You must echo the exact origin and sendAccess-Control-Allow-Credentials: true. - Most "CORS errors" on POST/PATCH are failed preflights: the
OPTIONSrequest gets a 404, a redirect or a 401 before your route ever runs. - A 500 or 502 can look like CORS. Error pages from your app or Nginx often lack the CORS headers, so the browser reports CORS instead of the real failure.
Why does the browser block the request?
Browsers follow the same-origin policy: JavaScript on one origin can't read responses from another origin unless that server says it's allowed. CORS (Cross-Origin Resource Sharing) is how the server says so, through response headers. The request often reaches your server and runs; the browser just refuses to hand the response to your code. That's why the same URL works in curl, Postman or a server-side fetch and fails in the browser. Those tools don't enforce CORS.
For "simple" requests (GET, HEAD or POST with a plain content type and no custom headers) the browser sends the request and checks the response headers. For anything else, such as Content-Type: application/json, an Authorization header, or PUT, PATCH and DELETE, it first sends a preflight OPTIONS request and only sends the real request if the preflight answer allows it. The MDN CORS guide lists the exact rules.
Step 1: Read the exact error in DevTools
Open DevTools, go to the Network tab, and look for two rows for the failing call: an OPTIONS (the preflight) and the real request. The console message tells you which rule failed:
- "No 'Access-Control-Allow-Origin' header is present": the server didn't send the header at all, or the response came from an error page or proxy that doesn't add it.
- "The 'Access-Control-Allow-Origin' header has a value ... that is not equal to the supplied origin": the server allows a different origin, often the apex vs www, or http vs https.
- "Response to preflight request doesn't pass access control check: It does not have HTTP ok status": the
OPTIONSrequest got a 401, 404 or 500. - "Redirect is not allowed for a preflight request": the API URL redirects (http to https, adding a trailing slash, apex to www). Call the final URL directly.
- "...must not be the wildcard '*' when the request's credentials mode is 'include'": you send cookies but the server answers
*.
Step 2: Reproduce it with curl
Replay the preflight from your terminal, sending the same Origin your site uses, and look only at the headers:
curl -si -X OPTIONS https://api.example.com/api/v1/orders \ -H 'Origin: https://www.example.com' \ -H 'Access-Control-Request-Method: POST' \ -H 'Access-Control-Request-Headers: content-type,authorization' \ | grep -i -E '^HTTP|^access-control|^location'
A working answer is a 200 or 204 with access-control-allow-origin: https://www.example.com and the methods and headers you asked for. A 301, 404 or 401 here is your bug, and you now know which layer to fix.
Step 3: Allow the right origin on the server
NestJS
NestJS has CORS built in. NestFactory.create(AppModule, { cors: true }) allows any origin, which is fine for a public, read-only API that uses bearer tokens instead of cookies. For anything with cookies or private data, list your origins:
app.enableCors({
origin: ['https://www.example.com', 'http://localhost:3000'],
credentials: true, // only if you send cookies
methods: ['GET', 'POST', 'PATCH', 'DELETE'],
maxAge: 86400, // cache the preflight for a day
});
On the Fastify adapter, the NestJS docs note that only GET, HEAD and POST are allowed by default, so a PATCH or DELETE fails the preflight until you list it in methods.
Express
import cors from 'cors';
app.use(cors({ origin: ['https://www.example.com'], credentials: true }));
Register it before your routes and before any auth middleware, so the OPTIONS request is answered without needing a token.
Nginx in front of the API
Prefer one layer. If both Nginx and the app add Access-Control-Allow-Origin, the browser sees two values and rejects it. If you must do it in Nginx, add always so the headers are also sent on 4xx and 5xx responses:
location /api/ {
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin "https://www.example.com" always;
add_header Access-Control-Allow-Methods "GET, POST, PATCH, DELETE" 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 "https://www.example.com" always;
add_header Vary Origin always;
proxy_pass http://127.0.0.1:5000;
}
Then sudo nginx -t && sudo systemctl reload nginx and run the curl test again.
Step 4: Fix credentials and cookies
If your frontend uses fetch(url, { credentials: 'include' }) or axios withCredentials: true, three things must all be true: the server sends the exact origin (not *), it sends Access-Control-Allow-Credentials: true, and the cookie itself is set with SameSite=None; Secure if the API is on a different site. If you allow several origins dynamically, also send Vary: Origin so a CDN doesn't cache one origin's answer for another.
Step 5: Or skip CORS with a same-origin proxy
With Next.js you can avoid cross-origin calls from the browser entirely. Fetch from server components or route handlers (server-to-server requests have no CORS), or add a rewrite so the browser calls /api/... on your own domain and Next.js forwards it. Nginx can do the same with a location /api/ block on the main site. This is my usual fix when a third-party API doesn't support CORS at all. My Next.js 16 on a VPS guide shows the Nginx setup.
Common mistakes that keep the error coming back
- Origin typos: a trailing slash (
https://www.example.com/) never matches; origins have no path. - Forgetting localhost: development runs on
http://localhost:3000, a separate origin you must allow (ideally only outside production). - The real error is a crash. If the API returns a 502 because the app is down, the Nginx error page has no CORS headers. Fix the crash first; see my Nginx 502 guide.
- Turning CORS off in the browser with flags or extensions "works" for you only, and hides the real fix.
- Reflecting any origin with credentials. Echoing back whatever
Originarrives while allowing cookies lets any website make logged-in requests as your users. Use an allowlist.
Frequently asked questions
Can I fix a CORS error from the frontend?
No. The browser enforces CORS based on headers the server sends, so the fix must be on the API or a proxy you control. From the frontend you can only avoid the cross-origin call, for example by routing it through your own server.
Why does my API work in Postman but not in the browser?
Postman, curl and server-side code don't enforce the same-origin policy, only browsers do. The request works everywhere; the browser just refuses to give your JavaScript the response without the right CORS headers.
Is Access-Control-Allow-Origin: * safe?
It's fine for public data that doesn't depend on cookies, such as a public read-only API or fonts. Browsers reject a wildcard answer to a request sent with credentials anyway. For anything tied to a logged-in user, list your exact origins instead.
Why do I get a CORS error only on POST, not GET?
A JSON POST triggers a preflight OPTIONS request and a simple GET doesn't. Your server or proxy is probably rejecting the OPTIONS request with a 404, 401 or redirect. Test it with the curl command above.
Why did CORS break after I moved to a new domain?
The origin changed, so your allowlist no longer matches. Add the new origin (with and without www if both serve the app, though a single canonical host is better) and make sure the API URL doesn't redirect.
Still blocked by CORS?
I build and fix Next.js, NestJS and Node.js apps, including the API, Nginx and auth setup that CORS problems usually hide in. See my web development services, or contact me with the console error and your API URL and I'll tell you what's wrong.
Written by
MD Rakibul Islam Rakib
Full-stack developer, DevOps engineer and Linux system administrator with 5+ years of production experience. I deploy, harden and fix servers and web apps for clients worldwide, and everything in this article runs on real servers I manage, including this site.
- CORS error
- No Access-Control-Allow-Origin header
- CORS preflight
- NestJS CORS
- Express CORS
- Nginx CORS
- Next.js


