HTTP

Debug Guide

How to Debug a Failed CORS Preflight

A preflight failure means the browser did not receive permission to send the actual cross-origin request. Inspect the OPTIONS exchange directly, then trace every server and proxy that handles it.

1. Find the OPTIONS request

Open DevTools, select Network, and filter for OPTIONS. Inspect the preflight request separately from the intended GET, POST, PUT, PATCH, or DELETE request. The important request headers are:

A JSON Content-Type, an Authorization header, or methods such as PUT and DELETE commonly trigger a preflight. The preflight itself is expected browser behavior, not the error.

2. Reproduce the browser's preflight

Use curl with the same origin, method, and header names shown in DevTools:

curl -i -X OPTIONS 'https://api.example.com/widgets' \
  -H 'Origin: https://app.example.com' \
  -H 'Access-Control-Request-Method: POST' \
  -H 'Access-Control-Request-Headers: content-type,authorization'

Curl does not enforce CORS, which makes it useful for viewing the server's raw response. It does not prove the browser will accept that response; compare each returned header with the browser's request.

3. Validate every allow header

A successful response can use any successful status; 204 No Content is a common choice:

HTTP/2 204
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true
Vary: Origin
CheckFailure to look for
HTTP statusThe route returns a redirect, 401, 403, 404, or 500 instead of a successful OPTIONS response.
Allowed originThe value is missing, has a trailing-slash mismatch, or uses * with credentials.
Allowed methodThe requested method is absent from Access-Control-Allow-Methods.
Allowed headersA requested header such as Authorization is absent from Access-Control-Allow-Headers.
CredentialsThe request uses credentials but Access-Control-Allow-Credentials: true is absent.

Header names are case-insensitive, but values still need to describe what the browser requested. When origin handling is dynamic, return the validated origin and add Vary: Origin so shared caches do not reuse a response for the wrong site.

4. Check routers, authentication, redirects, and proxies

OPTIONS must reach the CORS handler. A common failure is middleware that demands authentication before the CORS layer runs. Preflights normally do not contain the credentials for the actual request, so let OPTIONS reach the policy handler and return its permission response.

5. Verify the actual response after preflight succeeds

A successful preflight only permits the browser to send the next request. The actual response must still return a matching Access-Control-Allow-Origin header and, when credentials are used,Access-Control-Allow-Credentials: true. Inspect both Network entries before declaring the issue fixed.

Paste the request details into the CORS Debugger for a policy check. For broader issues, including simple requests and framework examples, continue with thecomplete CORS debugging guide.

Fast diagnosis checklist

  1. Find the OPTIONS request: Use the browser Network panel to inspect the preflight separately from the actual request.
  2. Reproduce the preflight: Send an OPTIONS request with the same Origin, requested method, and requested headers.
  3. Validate the allow response: Check that the response explicitly permits the origin, method, headers, and credentials in use.
  4. Trace the full request path: Verify that authentication, redirects, routers, proxies, and CDNs do not intercept OPTIONS.
  5. Verify the actual response: After preflight succeeds, confirm the real response also contains the required CORS headers.

Frequently Asked Questions

Why does the browser send OPTIONS before POST?

The browser preflights requests that are not CORS-safelisted, such as many JSON requests, non-safelisted headers, and methods such as PUT, PATCH, or DELETE. OPTIONS asks the server whether the real request is allowed.

Should a CORS preflight require authentication?

Usually no. A preflight does not include the credentials for the actual request, so an authentication layer that rejects OPTIONS before CORS handling commonly causes the failure.

Why does the actual request fail after OPTIONS returns 204?

Preflight and actual responses are checked independently. The actual response can still have a missing or incorrect Access-Control-Allow-Origin header, or fail a credentials rule.

References