CORS Preflight Failing โ OPTIONS Returns 404 or 403
Updated April 2026
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.
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:
- Methods: PUT, DELETE, PATCH
- Headers: Authorization, Content-Type: application/json, or any custom header
- Credentials: any request with credentials: 'include'
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.