DevBolt
By The DevBolt Team··12 min read

How to Fix CORS Errors: A Complete Guide for Every Server Framework

CORSSecurityBackendHowTo

CORS errors are fixed on the server, not the client. The browser blocks cross-origin requests unless the server responds with the correct Access-Control-Allow-Origin header. No amount of client-side code will bypass this — you need to configure the server that hosts the API.

What Is CORS and Why Does It Exist?

CORS stands for Cross-Origin Resource Sharing. It's a security mechanism built into every modern browser that restricts how a web page on one origin (say, https://app.example.com) can request resources from a different origin (say, https://api.example.com).

The underlying rule is the Same-Origin Policy. Two URLs share the same origin only when they have the same protocol, host, and port. Without this policy, any malicious site you visit could silently make authenticated requests to your bank, your email, or any other service where you're logged in — stealing data or performing actions on your behalf.

CORS relaxes that restriction in a controlled way. Before sending certain cross-origin requests, the browser fires a preflight request — an OPTIONS request that asks the server: “Will you accept a request from this origin, with this method and these headers?” Only if the server responds with the right CORS headers does the browser allow the actual request to proceed.

The 7 Most Common CORS Errors

1. “No 'Access-Control-Allow-Origin' header is present”

The most common error. The server doesn't include the Access-Control-Allow-Origin header in its response at all. This means the server has no CORS configuration — you need to add one.

Fix: Add the header to the response
Access-Control-Allow-Origin: https://app.example.com

2. “Origin not allowed”

The header exists, but its value doesn't match the requesting origin. If the header says https://app.example.com but your frontend is running on http://localhost:3000, the browser will block it. Double-check the origin value includes the correct protocol and port.

3. “Preflight request failed”

The browser sent an OPTIONS preflight request but the server returned a non-2xx status code, or didn't include the required CORS headers in the preflight response. Many servers and frameworks don't handle OPTIONS requests by default — you need to add explicit handling for them.

4. “Method not allowed”

The preflight response is missing the HTTP method in the Access-Control-Allow-Methods header. If your frontend sends a PUT or DELETE but the server only allows GET, POST, the request is blocked.

Fix: Include all required methods
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS

5. “Header not allowed”

The request includes a custom header (commonly Authorization or Content-Type with a value other than the simple types) that the server hasn't explicitly allowed via Access-Control-Allow-Headers.

Fix: Allow the headers your client sends
Access-Control-Allow-Headers: Content-Type, Authorization, X-Request-Id

6. “Credentials not supported with wildcard origin”

You set Access-Control-Allow-Credentials: true (needed for cookies or auth headers) but also used Access-Control-Allow-Origin: *. The spec forbids this combination. When credentials are involved, the origin must be an explicit value — not a wildcard.

Fix: Use an explicit origin
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true

7. “Redirect during preflight”

The OPTIONS preflight request hit a 301 or 302 redirect (commonly from HTTP to HTTPS, or from a trailing-slash normalization). Preflight requests must not be redirected. Make sure the exact URL your frontend calls doesn't trigger a redirect on the server.

How to Fix CORS Errors: Step-by-Step

Step 1: Check the Browser Console

Open DevTools (F12) and look at the Console tab. The error message tells you exactly which header is missing or mismatched. It will say something like: “has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.”

Step 2: Identify the Origin, Method, and Headers

In the Network tab, find the failed request. Note three things:

  • Origin — the URL of the page making the request (e.g., http://localhost:3000)
  • Method — the HTTP verb (GET, POST, PUT, DELETE, PATCH)
  • Headers — any custom headers the request sends (Authorization, Content-Type, etc.)

Step 3: Add the Correct Headers on the Server

Using the information from Step 2, configure your server to return:

Required CORS response headers
Access-Control-Allow-Origin: https://your-frontend.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400

The Access-Control-Max-Age header tells the browser to cache the preflight response for the specified number of seconds (86400 = 24 hours), reducing the number of OPTIONS requests.

Step 4: Handle Preflight (OPTIONS) Requests

The server must respond to OPTIONS requests with a 204 No Content (or 200 OK) status and include all the CORS headers. If your framework doesn't handle OPTIONS automatically, add an explicit route for it.

Step 5: Test with cURL

Simulate a preflight request from the terminal to verify your headers are correct:

Simulate a preflight request
curl -X OPTIONS https://api.example.com/endpoint \
  -H "Origin: https://your-frontend.com" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: Content-Type, Authorization" \
  -v

The response should include the Access-Control-Allow-Origin, Access-Control-Allow-Methods, and Access-Control-Allow-Headers headers. Use our cURL to Code Converter to translate this command into fetch, axios, or any other HTTP client.

Server Configuration Examples

Express.js (Node.js)

Express with cors middleware
import cors from "cors";
import express from "express";

const app = express();

app.use(cors({
  origin: "https://your-frontend.com",
  methods: ["GET", "POST", "PUT", "DELETE", "PATCH"],
  allowedHeaders: ["Content-Type", "Authorization"],
  credentials: true,
  maxAge: 86400,
}));

// Or allow multiple origins
app.use(cors({
  origin: [
    "https://your-frontend.com",
    "http://localhost:3000",
  ],
}));

Next.js API Routes

next.config.js
// next.config.js — global CORS via headers
module.exports = {
  async headers() {
    return [
      {
        source: "/api/:path*",
        headers: [
          { key: "Access-Control-Allow-Origin", value: "https://your-frontend.com" },
          { key: "Access-Control-Allow-Methods", value: "GET,POST,PUT,DELETE,OPTIONS" },
          { key: "Access-Control-Allow-Headers", value: "Content-Type, Authorization" },
        ],
      },
    ];
  },
};
Route handler (App Router)
// app/api/data/route.ts
export async function OPTIONS() {
  return new Response(null, {
    status: 204,
    headers: {
      "Access-Control-Allow-Origin": "https://your-frontend.com",
      "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
      "Access-Control-Allow-Headers": "Content-Type, Authorization",
    },
  });
}

export async function GET() {
  return Response.json({ data: "hello" }, {
    headers: {
      "Access-Control-Allow-Origin": "https://your-frontend.com",
    },
  });
}

Nginx

nginx.conf
location /api/ {
    if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' 'https://your-frontend.com';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
        add_header 'Access-Control-Max-Age' 86400;
        return 204;
    }

    add_header 'Access-Control-Allow-Origin' 'https://your-frontend.com' always;
    proxy_pass http://backend;
}

Apache (.htaccess)

.htaccess
<IfModule mod_headers.c>
    Header set Access-Control-Allow-Origin "https://your-frontend.com"
    Header set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
    Header set Access-Control-Allow-Headers "Content-Type, Authorization"
</IfModule>

# Handle preflight
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=204,L]

Go (net/http)

Go CORS middleware
func corsMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "https://your-frontend.com")
        w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
        w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")

        if r.Method == http.MethodOptions {
            w.WriteHeader(http.StatusNoContent)
            return
        }

        next.ServeHTTP(w, r)
    })
}

Python Flask

Flask with flask-cors
from flask import Flask
from flask_cors import CORS

app = Flask(__name__)

CORS(app, resources={
    r"/api/*": {
        "origins": "https://your-frontend.com",
        "methods": ["GET", "POST", "PUT", "DELETE"],
        "allow_headers": ["Content-Type", "Authorization"],
        "supports_credentials": True,
        "max_age": 86400,
    }
})

Common CORS Mistakes

  • Using Access-Control-Allow-Origin: * with credentials. The wildcard origin and credentials: true are mutually exclusive. The browser will reject the response. Always use an explicit origin when sending cookies or auth headers.
  • Not handling OPTIONS separately. Many frameworks route OPTIONS to the same handler as GET or POST, which may return a response body or wrong status code. Ensure OPTIONS returns 204 with the CORS headers and no body.
  • Using a proxy in development but not in production. Tools like Vite and webpack-dev-server can proxy API requests during development, hiding CORS issues. Everything works locally, then breaks the moment you deploy. Always test with actual cross-origin requests before shipping.
  • Forgetting Content-Type in allowed headers. If your client sends Content-Type: application/json, that triggers a preflight. The server must include Content-Type in Access-Control-Allow-Headers, or the preflight fails.

When NOT to Use CORS

CORS adds complexity. In many cases, you can avoid it entirely:

  • Same-origin architecture. Serve your API and frontend from the same domain. If both live at https://example.com, there's no cross-origin request and no CORS needed.
  • Reverse proxy. Put Nginx, Caddy, or a cloud load balancer in front of your API so it's accessible under the same domain as your frontend (e.g., example.com/api/* proxies to your backend).
  • Backend-for-Frontend (BFF) pattern. Your frontend talks to its own server-side layer (e.g., Next.js API routes or a lightweight Express server), which then calls the external API server-to-server. Server-to-server requests are not subject to CORS.

Need a server to configure?

DigitalOcean App Platform deploys your API directly from a Git repo with built-in CORS configuration, free SSL, and auto-scaling. Set your allowed origins in the dashboard, push to deploy, and stop fighting CORS headers by hand.

Secure Your Headers Beyond CORS

CORS is just one piece of browser security. Once your API is accepting cross-origin requests correctly, make sure the rest of your security headers are in order. Use our CSP Header Builder to generate a Content Security Policy that prevents XSS attacks, and the Security Headers Generator to configure Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, and other headers that harden your application.

If you need to check what status code your server is returning for preflight requests, the HTTP Status Code Reference has the full list with explanations. And once you've verified your CORS setup with cURL, paste the command into the cURL to Code Converter to generate production-ready code in your language of choice.