Handle OPTIONS Preflight Requests in Your Server

Ensure your server correctly responds to OPTIONS preflight requests. Covers common pitfalls with 405 errors, missing headers, and framework-specific handling.

Preflight

Detailed Explanation

Responding to OPTIONS Requests

If your server returns 405 Method Not Allowed or a 200 with an HTML body for OPTIONS requests, the browser will fail the preflight and block the actual request. Your server must respond with a 204 No Content (or 200) and the correct CORS headers.

What the Browser Sends

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

What the Server Must Return

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 3600
Vary: Origin

Common Pitfalls

  1. Framework routing ignores OPTIONS — Many frameworks route only GET/POST by default. You need to explicitly handle OPTIONS or use CORS middleware.
  2. Authentication middleware runs first — If your auth layer rejects unauthenticated requests before CORS headers are set, the preflight fails with a 401. CORS middleware must run before authentication.
  3. Reverse proxy strips headers — A misconfigured Nginx or AWS ALB might strip the Access-Control-* headers. Add always in Nginx to ensure headers are sent even on non-2xx responses.
  4. Missing Vary header — Without Vary: Origin, cached preflight responses can leak across origins.

Express.js: CORS Before Auth

// CORS must come BEFORE auth middleware
app.use(cors(corsOptions));
app.use(authMiddleware);

Nginx: Explicit OPTIONS Block

if ($request_method = 'OPTIONS') {
    add_header Access-Control-Allow-Origin $cors_origin always;
    add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
    add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
    add_header Access-Control-Max-Age 3600 always;
    return 204;
}

Use Case

A backend developer deploying a new API endpoint finds that PUT and DELETE requests from the frontend fail with CORS errors, even though GET requests work. The issue is that non-simple methods trigger preflights, and the server was not configured to handle OPTIONS.

Try It — CORS Header Builder

Open full tool