Why localhost and 127.0.0.1 Are Different Origins
A request from http://localhost:3000 to an API on http://127.0.0.1:8080 is blocked by CORS, even though both point to the same machine. This trips up nearly every developer at some point, and the cause is not a bug — it is the exact definition of an origin. localhost and 127.0.0.1 are different host strings, and CORS compares hosts as strings, never as resolved addresses.
This page is part of Origin Matching Rules & Validation, which covers how browsers evaluate the origin tuple in full.
The Symptom This Resolves
Access to fetch at 'http://127.0.0.1:8080/api/data' from origin
'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin'
header is present on the requested resource.
The same fetch works when both the page and the API use localhost, and breaks the moment one side switches to 127.0.0.1 — or to a different port.
Root Cause
The WHATWG Fetch Standard and the URL Standard define an origin as the tuple (scheme, host, port). Two origins are the same only when all three components are identical, and the host is compared as an exact ASCII string after parsing — the browser does not perform DNS resolution or consult the hosts file when comparing origins. So localhost and 127.0.0.1 are distinct hosts, http and https are distinct schemes, and :3000 and :8080 are distinct ports. Any difference makes the request cross-origin and subjects it to CORS. This is the same exact-string comparison described in How Browsers Evaluate Same-Origin Policy.
Prerequisite State
- A local front-end and a local API running on the same machine.
- You can read the exact
Originheader of the failing request (DevTools → Network → the request → Headers). - You can edit the API’s CORS allowlist.
Step-by-Step
Step 1 — Read the exact Origin
In the failing request’s headers, note the exact Origin, including the port — for example http://localhost:3000. That exact string is what the API must allow.
Step 2 — Pick one canonical dev origin
Standardize your tooling on a single host and port. If the front-end runs on http://localhost:3000, configure it to call the API at http://localhost:8080, not http://127.0.0.1:8080. Consistency alone resolves most of these failures.
Step 3 — Allow the exact dev origins (development only)
If you genuinely need both, list the exact origins in a development-only allowlist. This pattern, and the danger of shipping it, is covered in Safely Allowing localhost Origins in Development:
const DEV_ORIGINS = new Set([
'http://localhost:3000',
'http://127.0.0.1:3000',
]);
app.use((req, res, next) => {
const origin = req.headers.origin;
if (process.env.NODE_ENV !== 'production' && DEV_ORIGINS.has(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
}
next();
});
Verification
curl -si http://127.0.0.1:8080/api/data \
-H "Origin: http://localhost:3000" | grep -i access-control-allow-origin
# → access-control-allow-origin: http://localhost:3000
Security Boundary Note
Do not “solve” the mismatch with Access-Control-Allow-Origin: * or by reflecting any origin — a wildcard also breaks the moment you add credentials, and blind reflection lets any site call your API. Keep the localhost entries in a development-only branch and ensure they are stripped from production builds; a stray http://localhost:3000 left in a production allowlist is an opening for a local malware page to reach your API. See Wildcard vs Dynamic Origin Reflection for the general rule.
Common Mistakes
| Issue | Technical impact | Mitigation |
|---|---|---|
Assuming localhost == 127.0.0.1 for CORS |
Requests break unpredictably between the two | Treat them as distinct origins; standardize on one |
| Forgetting the port is part of the origin | :3000 → :8080 requests are cross-origin and blocked |
List the exact origin with its port in the allowlist |
| Leaving localhost origins in production | Local pages can reach the production API | Gate dev origins behind a NODE_ENV check |
FAQ
If localhost and 127.0.0.1 point to the same machine, why does CORS treat them differently?
CORS never resolves hostnames to IP addresses. An origin is the tuple (scheme, host, port), and the host is compared as an exact ASCII string. localhost and 127.0.0.1 are different host strings, so http://localhost:3000 and http://127.0.0.1:3000 are different origins regardless of DNS or the hosts file mapping them to the same address.
Does the port count as part of the origin in development?
Yes. The port is part of the origin tuple, so http://localhost:3000 and http://localhost:5173 are different origins, and a request from a dev server on 5173 to an API on 3000 is cross-origin and needs CORS headers. This is one of the most common local development surprises.
Should I add both localhost and 127.0.0.1 to my dev allowlist?
Pick one canonical dev origin and use it consistently. If your team genuinely uses both, add both exact origins (with ports) to a development-only allowlist. Never ship that allowlist to production and never replace it with a wildcard to sidestep the problem.