CORS localhost errors happen when a browser page loaded from one origin asks another origin for data, and the server response does not grant browser access.
The request may reach the server. The server may even return a valid response. The browser still blocks JavaScript from reading it if the CORS headers do not match the requesting origin.
CORS localhost: what is failing?
CORS stands for Cross-Origin Resource Sharing. MDN defines it as an HTTP-header mechanism that lets a server indicate which origins, other than its own, a browser may permit when loading resources.
An origin is the combination of scheme, host, and port. These are different origins:
http://localhost:3000
http://localhost:5173
https://localhost:3000
https://example.ngrok-free.app
During local development, your frontend might run on http://localhost:3000 while your API runs on http://localhost:8080. Same machine, different port. To the browser, that is cross-origin.
Fix the server, not the browser
The right fix usually lives on the API server. The server must return CORS headers that match the frontend origin.
For a simple local Express API, you might configure one allowed origin:
import cors from "cors";
import express from "express";
const app = express();
app.use(cors({
origin: "http://localhost:3000",
credentials: true,
}));
For multiple local ports, use an allowlist:
const allowedOrigins = new Set([
"http://localhost:3000",
"http://localhost:5173",
]);
app.use(cors({
origin(origin, callback) {
if (!origin || allowedOrigins.has(origin)) {
callback(null, true);
return;
}
callback(new Error("Origin not allowed"));
},
credentials: true,
}));
Avoid disabling browser security flags for normal work. That hides the problem on your machine and leaves your teammates, reviewers, and production users with the same failure.
Understand preflight requests
Some requests trigger a CORS preflight. The browser sends an OPTIONS request before the real request. That preflight asks whether the actual method and headers are allowed.
MDN describes preflight behavior for requests with methods or headers outside the simple request set. Common local triggers include:
Authorizationheaders.Content-Type: application/jsonon some requests.- Custom headers such as
X-Client-Version. - Methods like
PUT,PATCH, orDELETE.
The API must answer the preflight with headers such as:
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
If the preflight fails, the browser blocks the real request. Your server logs may only show an OPTIONS request, which can make the actual API route look unused.
Credentials need exact origins
Cookies and authentication add one common trap. If a request includes credentials, the server cannot use Access-Control-Allow-Origin: *. It must return a specific origin and include Access-Control-Allow-Credentials: true.
That means this is wrong for cookie-based local auth:
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Use the exact frontend origin instead:
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Credentials: true
Vary: Origin
Also check cookie attributes. SameSite, Secure, and domain settings can block cookies even after CORS headers look correct.
When a tunnel helps with CORS localhost issues
A tunnel does not fix a misconfigured API. The API still needs correct CORS behavior.
A tunnel helps when the problem comes from local-only origins, HTTPS requirements, or reviewer access. A public HTTPS review URL can make your local app behave closer to the deployed origin. It also lets phones, clients, and external services reach a server that would otherwise live only on your machine.
When you test through a tunnel, add the tunnel origin to your API allowlist for that session. That keeps the fix honest: the browser sees a real origin, and the server grants that origin on purpose.
With wiremaven:
npx wiremaven-cli 3000 --expires 30m
wiremaven creates a temporary encrypted public link for your local app. The local machine connects to the relay through an outbound-only WebSocket. Reviewers open a browser URL, and you see live viewer, request, and failure signals while they test.
Use the docs for setup and how wiremaven works for the request path.
Local CORS debugging checklist
Work through the browser console and Network panel:
- Confirm the frontend origin: scheme, host, and port.
- Confirm the API response includes
Access-Control-Allow-Origin. - Confirm credentialed requests do not use
*. - Check whether an
OPTIONSpreflight fails before the real request. - Add
Vary: Originwhen responses vary by origin. - Make sure redirects do not change the preflight target.
- Test the same flow through the URL your reviewer will use.
For local mobile checks, read mobile browser testing. For tunnel basics, read what is a localhost tunnel.
FAQ
Why does CORS fail on localhost?
Different localhost ports are different origins. If your frontend and API use different ports, the API must return CORS headers that allow the frontend origin.
Can I use Access-Control-Allow-Origin star locally?
Only for non-credentialed requests. If cookies or credentials are involved, return the exact origin instead of *.
Does a tunnel fix CORS?
It can help by giving your app a stable HTTPS origin for review, but it does not replace correct API headers.
Should I disable CORS in my browser?
No. Fix the server response. Browser flags create false local success and hide problems from the actual review path.
Related: Mobile Browser Testing During Local Development - What Is a Localhost Tunnel?