technology

How to Resolve Preflight Request Failures: Troubleshooting Cross-Origin Resource Sharing in 2024

Master CORS troubleshooting in 2024 by understanding and resolving preflight request failures with ease. Enhance web security and functional communication.

October 9, 2025
CORS API webdevelopment security troubleshooting preflightrequest websecurity
14 min read

Mastering CORS troubleshooting in 2024 starts with understanding what your browser is trying to protect you from. When a site running at one origin (like https://app.example.com) tries to call an API at another origin (like https://api.example.com), the browser enforces Cross-Origin Resource Sharing (CORS) rules. If the request is potentially risky, the browser first sends a “preflight” request (an HTTP OPTIONS call) to check whether the server permits the actual request.

When that preflight fails, you’ll see opaque, infuriating errors like “has been blocked by CORS policy” or “Response to preflight request doesn’t pass access control check.” This guide walks you through how preflight works, why failures happen, and exactly how to fix them across popular stacks and deployment setups in 2024.

Quick primer: how CORS and preflights work

  • Simple vs. non-simple requests:

    • A simple request does not trigger a preflight and is limited to:
      • Methods: GET, HEAD, POST
      • Headers: a small safelisted set (e.g., Accept, Accept-Language, Content-Language, Content-Type)
      • Content-Type restricted to: application/x-www-form-urlencoded, multipart/form-data, or text/plain
    • A non-simple request triggers a preflight OPTIONS call first if you:
      • Use other methods (PUT, PATCH, DELETE)
      • Send non-safelisted headers (Authorization, X-Requested-With, custom headers, etc.)
      • Use a non-safelisted Content-Type like application/json
  • The preflight:

    • Browser sends OPTIONS with headers:
      • Origin: https://app.example.com
      • Access-Control-Request-Method: PATCH
      • Access-Control-Request-Headers: Authorization, X-Custom-Header
    • Server must respond with 2xx plus CORS headers:
      • Access-Control-Allow-Origin: https://app.example.com (or *)
      • Access-Control-Allow-Methods: PATCH, GET, POST, OPTIONS
      • Access-Control-Allow-Headers: Authorization, X-Custom-Header
      • Optionally: Access-Control-Allow-Credentials: true, Access-Control-Max-Age: 600, Vary: Origin
    • If headers or status are incorrect, the browser refuses to send the actual request.
  • Important notes:

    • Preflight responses should not be redirected.
    • Preflight should usually be unauthenticated and fast.
    • If you serve different ACAO values per origin, add Vary: Origin for caching correctness.

How to diagnose a preflight failure quickly

  1. Check the browser console and Network panel:

    • Look for the OPTIONS request to your API URL.
    • Check its response status code (should be 200/204).
    • Inspect response headers for:
      • Access-Control-Allow-Origin
      • Access-Control-Allow-Methods
      • Access-Control-Allow-Headers
      • Access-Control-Allow-Credentials (if sending credentials)
      • Access-Control-Allow-Private-Network (for private network requests)
    • Confirm the values match exactly what the preflight requested.
  2. Reproduce with curl:

    • Emulate a preflight OPTIONS request:
      curl -i -X OPTIONS https://api.example.com/resource \
        -H "Origin: https://app.example.com" \
        -H "Access-Control-Request-Method: PATCH" \
        -H "Access-Control-Request-Headers: Authorization, X-Custom-Header"
      
    • Your response must include:
      • Access-Control-Allow-Origin: https://app.example.com
      • Access-Control-Allow-Methods: PATCH
      • Access-Control-Allow-Headers: Authorization, X-Custom-Header
    • If you see a redirect, a 401/403, a 404/405, or missing headers—there’s your problem.
  3. Check server and proxy logs:

    • Confirm OPTIONS hits your application or is handled upstream by a proxy/CDN.
    • Look for middleware rejecting OPTIONS or stripping headers.

The most common preflight failure scenarios (and fixes)

1) The server does not handle OPTIONS (404/405)

Symptoms:

  • OPTIONS request returns 404 or 405 Method Not Allowed.

Fix:

  • Ensure the route accepts OPTIONS on the path and doesn’t require auth or CSRF.
  • Respond with the correct CORS headers and a 204 or 200 status.

Example Node/Express:

app.options('/resource', (req, res) => {
  res.set({
    'Access-Control-Allow-Origin': 'https://app.example.com',
    'Access-Control-Allow-Methods': 'GET,POST,PATCH,DELETE,OPTIONS',
    'Access-Control-Allow-Headers': 'Authorization, Content-Type, X-Custom-Header',
    'Access-Control-Max-Age': '600',
    'Vary': 'Origin'
  });
  res.sendStatus(204);
});

2) Missing or incorrect Access-Control-Allow-Origin

Symptoms:

  • Error: “No ‘Access-Control-Allow-Origin’ header is present on the requested resource.”
  • ACAO is set to * but you’re using credentials.
  • ACAO value doesn’t match the Origin.

Fix:

  • If no credentials are used, you can use Access-Control-Allow-Origin: *.
  • If credentials (cookies, Authorization, or fetch with credentials: 'include') are used:
    • Set Access-Control-Allow-Origin to the exact origin (not *).
    • Add Access-Control-Allow-Credentials: true.
    • Ensure client sets credentials: 'include' (fetch) or withCredentials = true (Axios).
    • Add Vary: Origin if your server echoes specific origins.

3) Missing Access-Control-Allow-Methods or Access-Control-Allow-Headers

Symptoms:

  • Error: “Request header field X-Custom-Header is not allowed by Access-Control-Allow-Headers in preflight response.”
  • Browser complains about requested method PATCH not allowed.

Fix:

  • Make sure both headers include exactly what the browser requests.
    • For headers: list custom headers like Authorization, X-Custom-Header, Content-Type if using application/json.
    • For methods: include PATCH/PUT/DELETE, etc. as relevant.

Note: Matching is case-insensitive for header names, but list actual names as sent by the client.

4) Redirected preflight

Symptoms:

  • Preflight returns 301/302/307/308. Browser logs a CORS error.
  • Often happens when redirecting http to https, or naked domain to www.

Fix:

  • Never redirect preflight. Handle OPTIONS on the final destination without redirects.
  • If you must redirect GET/POST, configure a special case for OPTIONS to return 200/204 directly.

5) Preflight challenged by authentication or CSRF

Symptoms:

  • OPTIONS returns 401/403 because middleware demands auth or CSRF tokens.

Fix:

  • Permit OPTIONS unauthenticated on CORS endpoints.
  • Disable CSRF for OPTIONS (and typically for non-browser clients) where appropriate.
  • Keep auth on the actual request (GET/POST/etc.), not the preflight.

6) Proxy/CDN stripping or caching headers

Symptoms:

  • CORS works locally but fails in production.
  • Reverse proxy removes Access-Control-Request-Headers or CORS response headers.
  • Cached response with wrong ACAO served to another origin.

Fix:

  • Ensure “hop-by-hop” header filtering doesn’t remove CORS headers.
  • Always include Vary: Origin when ACAO depends on Origin.
  • At CDNs, whitelist and forward CORS headers, and avoid “header normalization” that drops them.
  • Serve CORS headers even on 304 Not Modified (or ensure the cached metadata includes them).

7) Using credentials with wildcard ACAO

Symptoms:

  • You set Access-Control-Allow-Origin: * and Access-Control-Allow-Credentials: true.
  • Browser blocks the response.

Fix:

  • Replace * with the exact Origin and include Access-Control-Allow-Credentials: true.

8) application/json request body causing preflight

Symptoms:

  • POST with JSON triggers preflight even when you don’t need custom headers.

Fix:

  • This is expected: application/json is not safelisted.
  • If preflight overhead is problematic, consider:
    • Using application/x-www-form-urlencoded or multipart/form-data where acceptable, or
    • Relying on preflight caching via Access-Control-Max-Age.

9) Private Network Access (PNA) preflight in 2024

Symptoms:

  • Requests from a secure origin (https) to private network targets (e.g., http://192.168.1.10) fail.
  • Chrome logs mention “Private Network Access” or requests for “Access-Control-Allow-Private-Network”.

Fix:

  • Respond to the preflight with:
    • Access-Control-Allow-Private-Network: true
    • Usual CORS headers (ACAO, ACAM, ACAH)
  • Ensure TLS and appropriate security posture if exposing private network devices.

10) Incorrect or excessive caching behavior

Symptoms:

  • Intermittent CORS errors; seems to depend on prior traffic from other origins.
  • Preflights repeat too often, affecting latency.

Fix:

  • For dynamic ACAO, set Vary: Origin.
  • Use a reasonable Access-Control-Max-Age (e.g., 600–7200 seconds).
  • Be aware that browsers cap max age; don’t rely on massive values.

Practical client-side patterns

fetch examples

  • Simple GET with credentials:
const resp = await fetch('https://api.example.com/data', {
  credentials: 'include' // requires ACAO specific origin + ACAC: true on server
});
  • JSON POST (will trigger preflight):
const resp = await fetch('https://api.example.com/items', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer xyz' },
  body: JSON.stringify({ name: 'Widget' }),
  credentials: 'include'
});
  • Avoid using mode: 'no-cors':
    • That makes the response opaque, not a fix. You won’t be able to read the response body.

Axios examples

import axios from 'axios';

// Credentials
axios.defaults.withCredentials = true;

await axios.post('https://api.example.com/items', { name: 'Widget' }, {
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer xyz'
  }
});

Server-side recipes

Node.js/Express

  • Use the cors middleware for simplicity:
import express from 'express';
import cors from 'cors';

const app = express();
const allowlist = ['https://app.example.com', 'https://admin.example.com'];

const corsOptions = {
  origin: (origin, cb) => {
    if (!origin) return cb(null, false); // optional: block non-browser tools
    if (allowlist.includes(origin)) return cb(null, true);
    return cb(new Error('Not allowed by CORS'));
  },
  methods: ['GET', 'POST', 'PATCH', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Authorization', 'Content-Type', 'X-Custom-Header'],
  credentials: true,
  maxAge: 600
};

app.use(cors(corsOptions));
// Optional: handle preflight across the board
app.options('*', cors(corsOptions));

app.get('/data', (req, res) => res.json({ ok: true }));
app.listen(3000);
  • Manual handling (if you don’t want the middleware):
app.use((req, res, next) => {
  const origin = req.headers.origin;
  const allowlist = new Set(['https://app.example.com']);
  if (origin && allowlist.has(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
    res.setHeader('Vary', 'Origin');
    res.setHeader('Access-Control-Allow-Credentials', 'true');
    res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PATCH,DELETE,OPTIONS');
    res.setHeader('Access-Control-Allow-Headers', 'Authorization, Content-Type, X-Custom-Header');
    res.setHeader('Access-Control-Max-Age', '600');
  }
  if (req.method === 'OPTIONS') return res.sendStatus(204);
  next();
});

NGINX reverse proxy

Ensure OPTIONS is handled and CORS headers are set before proxying (or allow upstream to handle). Example:

server {
  listen 443 ssl;
  server_name api.example.com;

  location / {
    # Handle preflight
    if ($request_method = OPTIONS) {
      add_header 'Access-Control-Allow-Origin' 'https://app.example.com' always;
      add_header 'Access-Control-Allow-Methods' 'GET,POST,PATCH,DELETE,OPTIONS' always;
      add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,X-Custom-Header' always;
      add_header 'Access-Control-Allow-Credentials' 'true' always;
      add_header 'Access-Control-Max-Age' '600' always;
      add_header 'Vary' 'Origin' always;
      return 204;
    }

    # Forward actual requests
    proxy_pass http://upstream_api;
    add_header 'Access-Control-Allow-Origin' 'https://app.example.com' always;
    add_header 'Access-Control-Allow-Credentials' 'true' always;
    add_header 'Vary' 'Origin' always;
  }
}

Note: If your upstream dynamically sets ACAO, avoid duplicating at NGINX or use variables to echo the request Origin after validation.

Spring Boot (Java)

Using Spring MVC config:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;

@Configuration
public class WebConfig implements WebMvcConfigurer {
  @Override
  public void addCorsMappings(CorsRegistry registry) {
    registry.addMapping("/**")
        .allowedOrigins("https://app.example.com")
        .allowedMethods("GET", "POST", "PATCH", "DELETE", "OPTIONS")
        .allowedHeaders("Authorization", "Content-Type", "X-Custom-Header")
        .allowCredentials(true)
        .maxAge(600);
  }
}

For Spring Security, ensure CORS is enabled before security filters:

http.cors().and().csrf().disable();

And define a CorsConfigurationSource bean if needed.

ASP.NET Core

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("CorsPolicy", policy =>
    {
        policy.WithOrigins("https://app.example.com")
              .WithMethods("GET","POST","PATCH","DELETE","OPTIONS")
              .WithHeaders("Authorization","Content-Type","X-Custom-Header")
              .AllowCredentials()
              .SetPreflightMaxAge(TimeSpan.FromMinutes(10));
    });
});

var app = builder.Build();
app.UseCors("CorsPolicy");
// Place before authentication if needed for OPTIONS
app.MapGet("/data", () => new { ok = true });
app.Run();

Ensure your server doesn’t require auth for OPTIONS; if it does, exempt it.

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
CORS_ALLOW_HEADERS = [
    "authorization",
    "content-type",
    "x-custom-header",
]
CORS_ALLOW_METHODS = [
    "GET", "POST", "PATCH", "DELETE", "OPTIONS"
]
CORS_PREFLIGHT_MAX_AGE = 600

AWS API Gateway (HTTP APIs)

  • In the console, enable CORS:
  • Ensure your integration returns CORS headers on OPTIONS and actual responses.
  • For CloudFront or ALB in front, forward the Origin header and cache by Origin.

S3/Static hosting example

S3 bucket CORS configuration (XML):

<CORSConfiguration>
  <CORSRule>
    <AllowedOrigin>https://app.example.com</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>HEAD</AllowedMethod>
    <AllowedHeader>*</AllowedHeader>
    <ExposeHeader>ETag</ExposeHeader>
    <MaxAgeSeconds>600</MaxAgeSeconds>
  </CORSRule>
</CORSConfiguration>

Security best practices in 2024

  • Prefer an allowlist of trusted origins. Don’t mirror the Origin header blindly. Validate against a list or a well-tested pattern.
  • Don’t use Access-Control-Allow-Origin: * with credentials. It’s blocked by browsers and is unsafe.
  • Limit allowed methods and headers to what’s necessary. Don’t say “*” in ACAM/ACAH just to “make it work.”
  • Add Vary: Origin when echoing ACAO so caches don’t leak CORS state across origins.
  • Keep OPTIONS unauthenticated, but keep actual requests protected by your normal auth/CSRF strategy.
  • For Private Network Access, understand that enabling ACA-PN expands the attack surface; restrict origins tightly and consider IP allowlists.

Performance tips: reduce preflight overhead

  • Cache preflight responses:
    • Set Access-Control-Max-Age to 600–7200 seconds for stable endpoints.
    • Be aware of browser caps and that some contexts (like private network) may have different limits.
  • Batch operations to reduce request count.
  • Prefer simple requests if practical:
    • Using application/x-www-form-urlencoded can avoid preflight for POSTs with no custom headers.
    • Only do this when it doesn’t complicate your API contract or security posture.
  • Co-locate front-end and API on the same origin when you can (e.g., reverse proxy under one domain), eliminating CORS entirely.

A step-by-step troubleshooting playbook

  1. Identify the failing request in DevTools:

    • Find the OPTIONS preflight and read the exact Access-Control-Request-Method and Access-Control-Request-Headers.
  2. Confirm server response:

    • Status must be 2xx, not 3xx/4xx/5xx.
    • Headers must include ACAO, ACAM, ACAH—and ACAC if you use credentials.
  3. Verify origin settings:

    • If credentials are used, ACAO must equal the request Origin (not *) and ACAC must be true.
    • Add Vary: Origin if ACAO is dynamic.
  4. Check for redirects:

    • Remove or bypass redirects for OPTIONS. Serve 200/204 directly.
  5. Bypass auth/CSRF for OPTIONS:

    • Ensure middleware allows OPTIONS to pass quickly and safely.
  6. Inspect proxies/CDNs:

    • Ensure headers are forwarded and not stripped.
    • Ensure caching is keyed by Origin (Vary: Origin).
  7. Reproduce with curl:

    • Send a synthetic preflight to isolate front-end issues from server config.
  8. Consider Private Network Access:

    • If calling local/private IPs from a secure origin, include Access-Control-Allow-Private-Network: true in preflight response.
  9. Lock down and document:

    • Once fixed, document what origins, methods, and headers are allowed and why.

Common error messages mapped to fixes

  • “No ‘Access-Control-Allow-Origin’ header...”

    • Add ACAO to preflight and actual responses. Use exact origin if credentials are involved.
  • “Response to preflight request doesn’t pass access control check: It does not have HTTP ok status.”

    • Return 200/204 for OPTIONS. Remove redirects, 401, 403.
  • “Request header field Authorization is not allowed by Access-Control-Allow-Headers.”

    • Add Authorization to Access-Control-Allow-Headers.
  • “The value of the ‘Access-Control-Allow-Origin’ header in the response must not be the wildcard ‘*’ when the request’s credentials mode is ‘include’.”

    • Replace * with the exact Origin and add Access-Control-Allow-Credentials: true.
  • “Method PATCH is not allowed by Access-Control-Allow-Methods in preflight response.”

    • Add PATCH to Access-Control-Allow-Methods.
  • “TypeError: Failed to fetch” (with CORS context)

    • Often indicates network error or blocked by CORS; inspect the preflight in Network panel to see the root cause.

Advanced tips and edge cases

  • Exposing response headers:
    • If your client needs to read custom response headers (e.g., X-RateLimit-Remaining), add:
      • Access-Control-Expose-Headers: X-RateLimit-Remaining
  • 204 vs 200 for preflight:
    • 204 is perfect for preflight. Include Content-Length: 0 to be explicit if your framework complains.
  • CORS with websockets:
    • CORS doesn’t apply the same way to WebSocket handshake, but proxies may need header adjustments. Still, many CDNs enforce Origin checks for WS.
  • Service Workers:
    • Respect CORS rules; they don’t bypass browser CORS. But they can help cache preflighted endpoints locally.
  • SameSite cookies:
    • If you rely on cookies cross-site, ensure SameSite=None; Secure. Combine with ACAC true and explicit ACAO.

Actionable checklist for production

  • Server:

    • OPTIONS returns 204/200 with ACAO/ACAM/ACAH/ACAC (if needed)
    • Vary: Origin present when ACAO is dynamic
    • No redirects on OPTIONS
    • OPTIONS bypasses auth and CSRF
    • Access-Control-Max-Age set (e.g., 600s)
    • Private Network Access header set if needed
  • Client:

    • credentials: 'include' only when necessary
    • Avoid non-safelisted headers if possible
    • Prefer stable headers/methods to leverage preflight caching
  • Infrastructure:

    • Proxies/CDNs forward Origin and preserve CORS headers
    • Caches respect Vary: Origin
    • Monitoring set up to detect CORS-related 4xx/5xx for OPTIONS

Final thoughts

Preflight failures are noisy because the browser is doing exactly what it should—protect users. The good news is that once you understand how preflights are constructed and how servers must answer them, fixes are straightforward and repeatable. In 2024, keep an eye on Private Network Access, cache behavior (Vary: Origin), and the credentials wildcard rule. Use allowlists and minimal, explicit headers for security, and rely on Access-Control-Max-Age and thoughtful API design to control performance overhead.

With the strategies and snippets above, you can resolve preflight request failures efficiently, harden your security posture, and keep cross-origin API communication running smoothly.

Share this article
Last updated: October 9, 2025

Related technology Posts

Discover more startup know-how and business insights

How to Resolve Specific Safari Bugs: A Detailed Troubleshoot...

Discover effective solutions for resolving specific Safari bugs in 2024 with our...

Effective Memory Management Solutions: Addressing Out-of-Mem...

Discover modern strategies to tackle out-of-memory errors and enhance your syste...

CDN Configuration Errors: Troubleshooting Guide with Cloudfl...

Master the art of troubleshooting CDN configuration errors with Cloudflare and A...

Monitor and Aggregate Logs Effectively: Using ELK and Sentry...

Learn to master proactive system management with ELK and Sentry, honing your log...

Need Expert Help?

Get professional consulting for startup and business growth.
We help you build scalable solutions that lead to business results.