CORS errors are common, noisy, and often misleading. The browser reports them as a security problem, but the real issue may be a missing response header, a failed preflight request, a redirect, a proxy mismatch, or a credential configuration mistake. This guide gives you a repeatable way to debug CORS errors quickly in modern web apps, with a practical checklist you can reuse across local development, staging, and production.
Overview
If you have ever seen a browser message like “blocked by CORS policy,” you already know the frustrating part: the console error is usually shorter than the actual problem. CORS, or Cross-Origin Resource Sharing, is a browser-enforced rule set that controls whether frontend code from one origin can access resources from another origin.
An origin is defined by scheme, host, and port. That means these are all different origins:
http://localhost:3000http://localhost:5173https://app.example.comhttps://api.example.com
When your frontend calls an API on a different origin, the browser checks whether the server allows that request. If the server response does not satisfy the browser’s rules, the browser blocks access to the response even if the server handled the request.
The key idea is simple: CORS is enforced by the browser, but fixed on the server or edge layer. You do not usually solve CORS by changing application logic in the client. You solve it by making the server return the right headers for the right request path, method, and origin.
Before changing anything, keep these principles in mind:
- CORS errors are about browser access, not whether the backend is alive.
- Tools like curl or server-to-server requests may work even while the browser fails.
- The visible error may hide the real problem, such as a 401, 404, 500, redirect, or network failure.
- Preflight requests can fail even when the main request would otherwise succeed.
If your team works across multiple services, this is also a good place to standardize your debugging workflow. Articles like API Testing Tools Compared: Postman Alternatives for Different Team Sizes and Environment Variables Best Practices for Local, Staging, and Production are useful companions because tooling and environment mismatches often amplify CORS issues.
Template structure
Use the following structure every time you need to debug CORS errors. It is designed to reduce guesswork and help you isolate the exact failure point.
1. Confirm the exact origins involved
Write down the frontend origin and the API origin exactly as the browser sees them.
- Frontend:
http://localhost:3000 - API:
http://localhost:8000
Do not assume two URLs are “basically the same.” Different ports mean different origins. HTTP and HTTPS also count as different origins.
2. Identify whether the browser sent a preflight request
A preflight is an OPTIONS request the browser sends before the real request in some cases, such as when:
- The method is not a simple method like GET or POST under narrow conditions
- You send custom headers such as
Authorization - You send JSON with
Content-Type: application/json - You include credentials and the browser requires extra checks
Open the browser DevTools Network tab and look for an OPTIONS request before the failing request. If the preflight fails, fix that first. The main request will not proceed until the preflight succeeds.
3. Inspect the response headers, not just the console error
In DevTools, click the failing request and inspect:
- Request URL
- Request Method
- Status Code
- Request Headers
- Response Headers
The most important response headers commonly involved are:
Access-Control-Allow-OriginAccess-Control-Allow-MethodsAccess-Control-Allow-HeadersAccess-Control-Allow-Credentials
If the browser sent a preflight request, make sure the OPTIONS response includes the expected allow headers and a successful status.
4. Check whether the response is actually an error page or redirect
Many “CORS errors” are secondary symptoms. For example:
- Your API redirects HTTP to HTTPS
- An auth layer redirects to a login page
- A reverse proxy returns a 404 or 502
- A CDN or gateway strips headers on some routes
If the final response is a redirect or error page without the right CORS headers, the browser reports CORS even though the underlying problem is routing or infrastructure.
5. Verify credentials behavior
If your frontend sends cookies or authenticated requests, the rules get stricter.
Common pitfalls:
- You use
credentials: 'include'in fetch, but the server does not sendAccess-Control-Allow-Credentials: true - The server uses
Access-Control-Allow-Origin: *together with credentials, which browsers do not accept - Cookies are not configured correctly for cross-site usage
When credentials are involved, the server usually needs to return the specific allowed origin rather than a wildcard.
6. Confirm the route, method, and headers match the server’s CORS policy
Some backends only apply CORS middleware to certain routes. Others allow GET but forget PUT, PATCH, or DELETE. Others allow the origin but not the request headers.
Compare what the browser sends with what the server explicitly allows:
- Does the allowed origin match exactly?
- Is the method included?
- Are custom headers, such as
AuthorizationorX-Requested-With, included? - Is the middleware running before auth and routing logic?
7. Reproduce with a minimal request
Strip the request down to the simplest possible version:
- Try GET before POST
- Remove custom headers temporarily
- Remove credentials temporarily if possible
- Test one endpoint rather than the full application flow
This helps you determine whether the failure is global or specific to one request shape.
8. Compare local, staging, and production separately
CORS often changes by environment. A local proxy may hide the issue, while staging exposes it. Different hostnames, load balancers, or env vars can produce different results. Treat each environment as its own configuration.
How to customize
The debugging template above is reusable, but you should adapt it to your stack and deployment model.
Frontend development servers
Modern frontend tooling often includes a dev proxy, which can make cross-origin requests look same-origin during local development. That is convenient, but it can also mask production issues.
When debugging:
- Check whether your dev server proxies API calls
- Test both with and without the proxy if possible
- Make sure environment variables point to the intended backend
If you rely on multiple local environments, keep your origin values documented. This fits well with a disciplined environment setup, as discussed in Environment Variables Best Practices for Local, Staging, and Production.
Backend frameworks
Most backend frameworks provide CORS middleware, but the details vary. Instead of memorizing framework-specific syntax, focus on these questions:
- Where is CORS configured?
- Is it global or route-specific?
- Does it handle preflight automatically?
- Does middleware order matter?
- Are reverse proxies or gateways overriding the headers?
A common failure is placing CORS after authentication or route handlers, which means rejected requests never receive the required headers.
API gateways, CDNs, and serverless platforms
In distributed systems, the code that serves the response may not be the layer adding headers. The browser only sees the final response path.
Check whether headers are being:
- Added in the application server
- Overridden by a reverse proxy
- Dropped on redirects
- Configured differently for error responses
- Handled inconsistently across regions or deployments
This matters especially when comparing API styles or infrastructure approaches. If your team is evaluating API architecture more broadly, see REST vs GraphQL vs gRPC: How to Choose the Right API Style.
Authenticated apps
For apps using cookies, sessions, or bearer tokens, include auth behavior in your CORS checklist from the start. Requests with auth headers often trigger preflight, and cookie-based auth introduces stricter browser rules.
Ask:
- Am I sending cookies or tokens?
- Does the request include
Authorization? - Does the response vary by auth state?
- Are unauthenticated responses missing CORS headers?
One subtle bug is when successful authenticated responses include CORS headers, but 401 or 403 responses do not. From the browser’s perspective, that still looks like a CORS failure.
A practical header checklist
When the browser blocks a cross-origin request, verify these conditions in plain language:
- The server recognizes the frontend’s origin
- The allowed origin is returned in the response
- The allowed methods include the actual method used
- The allowed headers include the actual request headers sent
- The preflight response succeeds for
OPTIONS - Credentials settings are consistent on both client and server
If you want a compact debugging ritual, use this sequence:
- Read the origin pair
- Check Network tab
- Find preflight
- Read response headers
- Confirm status code and redirects
- Check credentials mode
- Test a minimal request
- Inspect proxy or gateway config
Examples
These examples are generalized on purpose. They reflect patterns that recur across frameworks and hosting platforms.
Example 1: Local frontend to local API
Your frontend runs on http://localhost:5173 and your API runs on http://localhost:8080. A fetch request with JSON fails.
What often happened:
- The browser sent a preflight
OPTIONSrequest - The API had no handler or middleware for
OPTIONS - The real POST request never ran
Fix approach:
- Enable CORS middleware for the API
- Make sure
OPTIONSis handled - Allow the frontend origin explicitly
- Allow
Content-Typeif JSON is being sent
Example 2: Production app behind a reverse proxy
Your app works locally but fails in production. The backend sends CORS headers in development, yet the browser still blocks the request in production.
What often happened:
- A reverse proxy or CDN was terminating requests
- Error responses or redirects were generated at the edge layer
- The CORS headers from the application never reached the browser
Fix approach:
- Inspect the exact production response in DevTools
- Check whether the status is 301, 302, 401, 403, 404, or 502
- Verify whether headers are present on both success and error responses
- Review gateway, edge, or hosting rules
Example 3: Credentials with wildcard origin
Your frontend sends cookies with credentials: 'include'. The server responds with Access-Control-Allow-Origin: * and Access-Control-Allow-Credentials: true. The browser still blocks it.
What often happened:
- The wildcard origin conflicts with credentialed requests
Fix approach:
- Return the specific requesting origin instead of
* - Keep credentials settings aligned on client and server
- Retest the full auth flow
Example 4: Authorization header triggers preflight
Your simple GET request worked until you added an Authorization header for bearer tokens.
What often happened:
- The custom header caused the browser to preflight the request
- The server allowed the origin but did not allow the header
Fix approach:
- Confirm that preflight is now happening
- Add
Authorizationto allowed headers where appropriate - Make sure
OPTIONSreturns the expected CORS headers
Example 5: The issue is not CORS at all
The console says CORS, but the underlying problem is a bad URL path or wrong environment variable.
What often happened:
- The frontend called the wrong API host
- The backend returned 404 or an HTML error page
- The browser surfaced the result as a CORS problem because the response was not accessible
Fix approach:
- Verify the request URL carefully
- Check environment values
- Confirm the route exists and is reachable
- Use API tools to compare expected and actual responses
This is also where general-purpose developer tools help. URL and encoding mistakes can complicate debugging, so references like URL Encoder and Decoder Guide for Web Developers and Base64 Encode and Decode Tools: When to Use Them and What to Avoid can save time when requests contain query params, tokens, or transformed payloads.
When to update
This guide is meant to be reused, not read once and forgotten. Revisit your CORS debugging checklist whenever your app architecture or publishing workflow changes.
Update your internal playbook when:
- You add a new frontend origin, such as a staging app or admin panel
- You move APIs behind a gateway, CDN, or serverless edge layer
- You switch authentication methods
- You introduce new custom headers
- You change local dev proxies or monorepo tooling
- You split one app into multiple services or domains
- You notice success responses include CORS headers but error responses do not
A good maintenance habit is to keep a short CORS runbook in your repository with:
- Known frontend and API origins by environment
- Expected allowed methods and headers
- Whether credentials are used
- Where CORS is configured in the stack
- How to test preflight requests
If your team uses a monorepo or multiple packages, document where environment configuration and local proxy setup live. That makes onboarding easier and reduces “works on my machine” debugging loops. For broader repo organization questions, see Monorepo Tools Compared: Turborepo vs Nx vs Plain Workspaces.
To make this article practical, here is a final action checklist you can copy into your notes:
- Write down frontend origin and API origin exactly
- Open DevTools Network tab and reproduce the request
- Find out whether an
OPTIONSpreflight occurred - Inspect status code, redirects, and response headers
- Compare actual method and headers against server CORS settings
- Check whether credentials are being sent
- Test the simplest possible request shape
- Verify proxy, gateway, and edge behavior
- Repeat in each environment separately
- Document the fix in a shared runbook
The fastest way to debug CORS errors is to stop treating them as a vague frontend problem. Treat them as a request-and-response tracing exercise. Once you identify the exact origin pair, preflight behavior, headers, and infrastructure path, most CORS issues become routine to fix.
