CORS

CORS Preflight Failing — OPTIONS Returns 404 or 403

Postman works. curl works. The browser fails. That is almost always a missing preflight handler. Browsers send an OPTIONS request before POST, PUT, or DELETE — and your server is not responding to it correctly.

Browser Console Error
Access to fetch at 'https://api.example.com/submit' from origin 'https://app.example.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

What the browser actually sends first

OPTIONS /api/submit HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization

Your server must respond to this OPTIONS request with a 200 or 204 and the correct CORS headers. If it returns 404, the browser stops and shows a CORS error — even though your actual POST endpoint works fine.

What triggers a preflight

Not all requests get a preflight. Simple requests (GET, HEAD, POST with only simple headers like Content-Type: application/x-www-form-urlencoded) skip it. You get a preflight when you use:

Fix by framework

Express

app.options('*', cors()); // Handle all OPTIONS preflights
app.use(cors({ origin: 'https://app.example.com' }));

Nginx

if ($request_method = OPTIONS) {
    add_header Access-Control-Allow-Origin "https://app.example.com";
    add_header Access-Control-Allow-Methods "POST, GET, PUT, DELETE, OPTIONS";
    add_header Access-Control-Allow-Headers "Content-Type, Authorization";
    add_header Access-Control-Max-Age 86400;
    add_header Content-Length 0;
    return 204;
}

FastAPI

# CORSMiddleware handles OPTIONS automatically — no extra config needed
app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://app.example.com"],
    allow_methods=["*"],
    allow_headers=["*"],
)

Django

# Install django-cors-headers
pip install django-cors-headers

# settings.py
INSTALLED_APPS = [..., 'corsheaders']
MIDDLEWARE = ['corsheaders.middleware.CorsMiddleware', ...]
CORS_ALLOWED_ORIGINS = ['https://app.example.com']
CORS_ALLOW_CREDENTIALS = True

Cache the preflight

Access-Control-Max-Age: 86400 tells the browser to cache the preflight result for 24 hours. Without it, the browser sends OPTIONS before every single request — visible as extra network calls in DevTools.

Send a live OPTIONS preflight to your API →