Logo
Back to Blog
Web Development August 8, 2026 8 min read

CORS Preflight Failing? Fix the OPTIONS Request

O

OmniWebKit Team

Backend Engineering

Share:
Article Cover Image

Your GET works. Your POST does not. The console mentions a preflight, and the network tab shows an OPTIONS request you never wrote. Nothing you change to the actual request makes any difference, because the actual request is never being sent.

Why a CORS Preflight Request Failed Before Your Code Ran

The browser asked permission first, and your server said no.

Before certain cross-origin requests, browsers send an OPTIONS request asking whether the real one is allowed. If that answer is unsatisfactory, the real request never happens.

Three things make a request non-simple and trigger this check:

  • A method other than GET, HEAD or POST.
  • Any header outside the small CORS safelist, including Authorization.
  • A content type of application/json.

That last one explains why almost every modern API call gets preflighted. Sending JSON is enough on its own.

Our CORS tester sends the OPTIONS request separately, so you can see which step is failing.

Cause 1: The OPTIONS Request 404s

Your route only handles POST, so OPTIONS hits nothing.

Frameworks vary. Express with the cors package handles it for you; a hand-rolled router usually does not.

// Handle preflight explicitly
app.options('/api/*', cors());

Check the network tab for the status on the OPTIONS row. A 404 there is unambiguous and takes ten seconds to confirm.

Cause 2: An OPTIONS 401 Unauthorized

Your auth middleware is rejecting the preflight.

This one is genuinely counter-intuitive. A preflight deliberately carries no credentials — no cookies, no Authorization header, nothing.

So any middleware checking for a token sees an unauthenticated request and returns 401. The browser reads that as refusal and stops.

app.use((req, res, next) => {
  if (req.method === 'OPTIONS') return next();
  return requireAuth(req, res, next);
});

Skipping auth on OPTIONS is safe. A preflight cannot read data — it only asks a question.

Cause 3: The Access-Control-Allow-Methods Error

The method you want is not on the list.

The preflight response has to name every method your API accepts:

Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS

We see this most on DELETE. It gets forgotten because nobody tests it until late, and framework defaults often cover only GET and POST.

Same applies to headers. Anything custom must appear in Access-Control-Allow-Headers, or the preflight refuses it.

Cause 4: A Redirect During Preflight

Browsers do not follow redirects on a preflight. At all.

A 301 or 302 aborts the whole thing, and the error message gives you no hint that a redirect was involved.

Two configurations cause it constantly: forcing https, and adding or removing a trailing slash. Both are invisible in your code.

The fix is to request the final URL directly. Inspect what your endpoint returns with our HTTP headers checker if you are not sure whether a redirect is in play.

Cause 5: Headers Present on One Response but Not the Other

The preflight and the real request are two separate responses.

Adding CORS headers to your OPTIONS handler only gets you past step one. The actual POST reply needs them too.

The reverse happens as well — headers on the route but nothing on the preflight. Either way you get a failure that looks intermittent because it depends which request you are watching.

In our experience this is the hardest of the five to spot, because the network tab shows a green OPTIONS row directly above a red one.

A Diagnosis Order That Saves Time

  1. Find the OPTIONS row in the network tab and read its status.
  2. 404? No route handler for OPTIONS.
  3. 401 or 403? Auth middleware is running on it.
  4. 301 or 302? A redirect is aborting it.
  5. 200 or 204 but still blocked? Read the response headers — something is missing from the allow lists.

That order resolves nearly every case we are asked about, usually in under five minutes.

Wrapping Up

When a CORS preflight request failed, the real request never left the browser. Start with the OPTIONS status code, because it points straight at the cause.

Exclude OPTIONS from authentication, list every method and header you accept, and make sure both responses carry the headers.

For the underlying mechanics, see how preflight requests actually work. If the origin header is the missing piece, the Access-Control-Allow-Origin fix covers that instead.

Frequently Asked Questions

Why does my OPTIONS request return 401?

+
Your auth middleware is running on it. A preflight never carries credentials by design, so any check for a token rejects it before your route is reached. Exclude OPTIONS from authentication entirely.

The preflight returns 200 but the browser still blocks it. Why?

+
A successful status is not enough — the response also has to carry the right Access-Control headers. A 200 with no Access-Control-Allow-Methods fails just as hard as a 404.

Can I stop the preflight happening at all?

+
Only by keeping the request simple: GET, HEAD or POST, no custom headers, and a content type of text/plain, multipart/form-data or application/x-www-form-urlencoded. Sending JSON always triggers one, because application/json is not on that list.

Why does adding an Authorization header cause a preflight?

+
Because it is not on the CORS safelist. Any header outside the small allowed set makes the request non-simple, and the browser checks permission first. It catches teams who added a token to a previously working GET.

Do preflights slow my app down noticeably?

+
Only on first use for a given path, then Access-Control-Max-Age caches the answer. Chrome caps that cache at two hours regardless of the value you send, so a very large number is silently reduced.

Why does the preflight fail only for PUT and DELETE?

+
Your Access-Control-Allow-Methods list is probably missing them. GET and POST often work by accident because a framework default covers them. List every method your API actually accepts.

Does a redirect break the preflight?

+
Completely — browsers do not follow redirects during preflight. A trailing-slash rule or an http to https rule will abort the request with a generic message. Send the request to the final URL.

My preflight passes but the real request fails. What changed?

+
They are separate responses with separate headers. A common cause is CORS headers added only to the OPTIONS handler, leaving the actual GET or POST reply bare. Both need them.

Why does it work in one browser and not another?

+
Header caching differs. Safari and Chrome cache preflight results for different durations, so one may still be using a stale success while the other re-asks and fails. Hard-reload both before comparing.

Should OPTIONS return 200 or 204?

+
Either works, and 204 is tidier since there is no body to send. What matters is the headers, not the status code. Some older proxies mishandle 204, so switch to 200 if you see odd behaviour in the middle.

Tags

#CORS#Preflight#Debugging#HTTP