CORS preflight failed: 405, “does not have HTTP ok status” and friends

Before some requests the browser asks the server for permission with an OPTIONS request. If that question gets a 405, a login page or a redirect, the real request is never sent.

What a preflight is

For a simple request (GET or POST with plain form or text bodies and no custom headers) the browser just sends it. Anything else, such as PUT/DELETE, a JSON Content-Type, or an Authorization header, triggers a preflight: an OPTIONS request asking whether the real one is allowed.

OPTIONS /api/items HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: authorization, content-type

The server must reply with a 2xx and matching headers:

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

If anything is wrong the browser shows a CORS error and your fetch rejects with TypeError: Failed to fetch. The start of the story is in the CORS errors guide; this page is about the preflight step.

The messages, decoded

Browser saysUsual causeFix
It does not have HTTP ok statusOPTIONS got 404, 405, 401, 403 or 500Add an OPTIONS handler that returns 204 before routing and auth run
Redirect is not allowed for a preflight requesthttp to https, a trailing slash (/api/items to /api/items/) or a login redirectCall the final URL exactly; don’t redirect OPTIONS
Method PUT is not allowed by Access-Control-Allow-MethodsThe method list is missing itAdd the method
Request header field authorization is not allowed by Access-Control-Allow-HeadersHeader not listedList every custom header the page sends
No ‘Access-Control-Allow-Origin’ header is present (on the OPTIONS)CORS headers only added to GET/POST responses, or only on successAdd them to OPTIONS and error responses too
The value of … must not be the wildcard ‘*’ when credentials mode is ‘include’* with cookiesEcho the exact allowed origin and send Access-Control-Allow-Credentials: true

Need a reminder of what each status means? Look up any HTTP status code.

The 405 and 401 traps

Test the preflight with curl

curl -si -X OPTIONS https://api.example.com/api/items \
  -H "Origin: https://app.example.com" \
  -H "Access-Control-Request-Method: PUT" \
  -H "Access-Control-Request-Headers: authorization, content-type"

You want a 204 (or 200) and the four Access-Control-Allow-* lines. A 301/302 with a Location means a redirect, so fix the URL (see redirects explained).

Answering preflights by stack

# nginx (inside the location that proxies the API)
if ($request_method = OPTIONS) {
    add_header Access-Control-Allow-Origin "https://app.example.com" always;
    add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE" always;
    add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
    add_header Access-Control-Max-Age 7200 always;
    return 204;
}
// Express: before routes and auth
const cors = require('cors');
app.use(cors({ origin: 'https://app.example.com', credentials: true }));
app.options('*', cors());   // Express 4; Express 5 answers preflights via app.use
# Flask
from flask_cors import CORS
CORS(app, origins=["https://app.example.com"], supports_credentials=True)

# FastAPI
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(CORSMiddleware, allow_origins=["https://app.example.com"],
                   allow_methods=["*"], allow_headers=["*"], allow_credentials=True)

Serverless functions: handle event.httpMethod === "OPTIONS" (or the platform’s equivalent) first and return 204 with the headers; also enable CORS on the gateway itself if it has a setting.

Or avoid the preflight

Preflight passes but cookies still go missing? See CORS with credentials.

Questions

What is a CORS preflight request?

An OPTIONS request the browser sends before a cross-origin request that uses methods like PUT or DELETE, a JSON Content-Type or custom headers like Authorization. The server must answer with a 2xx status and Access-Control-Allow-* headers, or the real request is not sent.

Why does my preflight return 405?

The server or framework has no handler for the OPTIONS method on that URL, so it answers 405 Method Not Allowed. Add CORS middleware or an OPTIONS route that returns 204 with the CORS headers.

What does 'It does not have HTTP ok status' mean?

The preflight OPTIONS response was not 2xx: commonly 401 because auth ran on a request that never carries credentials, 404 or 405 because no route matched, or 500. Let OPTIONS through before authentication and return 204.

Why is a redirect not allowed for a preflight request?

Browsers refuse to follow redirects on preflights. Call the exact final URL (https, correct trailing slash) and make sure no login or HTTPS redirect applies to OPTIONS.

Published .

Try it

Related guides

Newest guides