Back to blog

Frontend API Requests Blocked by CORS? Here's How to Check CDN Response Headers and Preflight Requests

CdnChart Technical TeamPublished on 2026-10-0212 min read
Frontend API Requests Blocked by CORS? Here's How to Check CDN Response Headers and Preflight Requests

A frontend page calls the API directly, and suddenly the browser console fills with red errors:

Access to fetch at 'https://api.example.com/user'
from origin 'https://www.example.com'
has been blocked by CORS policy

Many people see the word 'blocked' and immediately assume that a CDN or firewall blocked the request.

In fact, a CORS error does not necessarily mean the request never reached the server.

Sometimes the API executes successfully and the server returns 200, but the response is missing the cross-origin headers the browser requires, so JavaScript cannot read the result. In other cases, the browser first sends an OPTIONS preflight request, but the preflight fails, and the actual GET or POST request is never sent.

To troubleshoot CORS issues, don't just check whether the API 'opens'; inspect each of the following separately:

  • What request the browser sent

  • Whether the OPTIONS preflight succeeded

  • Whether the actual API response includes cross-origin headers

  • Whether the response headers are added by the origin or the CDN

  • Whether the CDN cached a response for a different Origin

  • Whether error responses also include CORS headers

First, get this straight: CORS is not ordinary network blocking

CORS stands for Cross-Origin Resource Sharing.

The browser determines whether a request is cross-origin based on the page origin and the API address. An Origin consists of three parts:

协议 + 域名 + 端口

Each of the following addresses may be a different Origin:

https://www.example.com
https://api.example.com
http://www.example.com
https://www.example.com:8443

Even if two domain names belong to the same company,www.example.comandapi.example.comthey are still different Origins as far as the browser is concerned.

CORS mainly restricts JavaScript in the browser from reading cross-origin responses. It does not mean the server rejected the request, nor does it mean the CDN necessarily dropped the connection.

As a result, this situation is common:

  • The browser call fails

  • Opening the API in the address bar works

  • curlThe request works

  • Postman requests also work

  • The server access log shows 200

This is not a contradiction.curlAnd Postman usually does not perform CORS checks the way browsers do, so a successful request only proves the API is reachable—it does not prove the CORS configuration is correct.

First, check in the browser whether the failed request is the OPTIONS request or the actual request

Open Chrome or Edge DevTools:

F12 → Network → 重新发起请求

Find the API address in the request list and check whether two requests appear:

OPTIONS /api/user
POST /api/user

If you only see the OPTIONS request and no subsequent POST, the preflight request usually did not pass.

If the OPTIONS request succeeds and the POST is sent, but the browser still reports a CORS error, the actual response may be missing the correct cross-origin headers.

Use the following cases to quickly diagnose the issue:

Browser symptom

Check first

Only OPTIONS, no actual request

Preflight status code, allowed methods, allowed headers

OPTIONS returns 403

The CDN, WAF, or origin blocks OPTIONS

OPTIONS returns 404

The API route does not handle OPTIONS

OPTIONS returns 301 or 302

HTTP-to-HTTPS redirect, domain redirect, or path redirect

OPTIONS returns 200 but an error still appears

Whether the response headers are complete

The actual request returns 200, but the frontend cannot read it

The actual response is missing CORS headers

The actual request returns 401 or 403 and reports CORS

The error response does not include CORS headers

Intermittent success and failure

CDN cache key,Vary: Originor multiple origins are inconsistent

Which requests trigger an OPTIONS preflight?

Not all cross-origin requests send an OPTIONS request first.

A simple GET request may be sent directly if it has no special headers, but the following cases are more likely to trigger a preflight:

  • Using methods such as PUT, PATCH, or DELETE

  • POST usingapplication/json

  • The request includesAuthorization

  • The request includes custom headers

  • The request includes headers outside the browser safelistContent-Type

  • The frontend explicitly adds a special header

For example:

fetch("https://api.example.com/user", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer token"
  },
  body: JSON.stringify({
    name: "test"
  })
});

The browser usually does not send the POST immediately; it first asks the server:

OPTIONS /user HTTP/1.1
Origin: https://www.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization, content-type

Only if the preflight response allows that Origin, method, and headers will the browser send the actual POST request.

MDN's CORS documentation also notes that non-simple cross-origin requests usually need to pass a preflight; only after the preflight succeeds does the browser send the actual request.

Simulate the browser's OPTIONS preflight with curl

Suppose the frontend page is:

https://www.example.com

The API address is:

https://api.example.com/v1/user

The request method is POST and includesAuthorizationandContent-Type.

You can simulate the preflight like this:

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

A normal preflight response might look like:

HTTP/2 204
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 600
Vary: Origin

Check each item one by one.

Access-Control-Allow-Origin

It determines which Origin can read the response:

Access-Control-Allow-Origin: https://www.example.com

If the response returns:

Access-Control-Allow-Origin: https://admin.example.com

but the actual page comes from:

https://www.example.com

If the two do not match, the browser will still reject it.

The Origin must match exactly, including protocol and port:

http://www.example.com
https://www.example.com
https://www.example.com:8443

These are three different Origins.

Access-Control-Allow-Methods

It should include the methods used by the actual request:

Access-Control-Allow-Methods: GET, POST, OPTIONS

If the frontend sends DELETE but the response only allows GET and POST, the preflight will not pass.

Access-Control-Allow-Headers

It should include the non-simple headers the frontend actually intends to send:

Access-Control-Allow-Headers: Authorization, Content-Type

If the frontend sends:

X-Request-ID
X-App-Version
Authorization

but the preflight response does not allow these headers, the browser will block the subsequent request.

Access-Control-Allow-Credentials

If the frontend needs to include cookies or HTTP authentication information cross-origin, you need:

Access-Control-Allow-Credentials: true

The frontend request must also explicitly enable credentials:

fetch("https://api.example.com/user", {
  credentials: "include"
});

Note that credentialed cross-origin requests cannot use:

Access-Control-Allow-Origin: *

In this case, you must return a specific Origin, for example:

Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Credentials: true

If a credentialed request and the wildcard*are used together, the browser will reject the response.

Vary: Origin

If the server dynamically returns differentAccess-Control-Allow-Originbased on the Origin in the request, it should also return:

Vary: Origin

It tells browsers, CDNs, and other caching systems that requests from different Origins may receive different responses.

MDN recommends that when returning a specific Origin rather than*you also useVary: Originto prevent different Origins from incorrectly reusing cached responses.

A successful OPTIONS request does not guarantee the actual request will succeed

After the preflight response is correct, you also need to check the actual API response.

For example, test GET:

curl -i \
  'https://api.example.com/v1/user' \
  -H 'Origin: https://www.example.com'

Test a POST with JSON:

curl -i \
  'https://api.example.com/v1/user' \
  -X POST \
  -H 'Origin: https://www.example.com' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer test-token' \
  --data '{"name":"test"}'

The actual response must also include the corresponding CORS headers:

Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Credentials: true
Vary: Origin

A very common configuration mistake is:

OPTIONS响应带有CORS头
POST响应没有CORS头

Although the browser passes the preflight and sends the POST, it still does not allow frontend JavaScript to read the response.

The reverse is also true: if the actual response is configured correctly but OPTIONS is not handled properly, the actual request will still not be sent.

Focus on whether the CDN rewrites or removes response headers

If cross-origin requests work when the API is not behind the CDN but errors begin after it is added, test the CDN and the origin separately.

Suppose the origin IP is:

203.0.113.10

Test the response through the CDN:

curl -i \
  'https://api.example.com/v1/user' \
  -H 'Origin: https://www.example.com'

Bypass the CDN to access the origin while preserving the correct Host and HTTPS SNI:

curl -i \
  --resolve api.example.com:443:203.0.113.10 \
  'https://api.example.com/v1/user' \
  -H 'Origin: https://www.example.com'

Test the preflight separately as well:

curl -i -X OPTIONS \
  --resolve api.example.com:443:203.0.113.10 \
  'https://api.example.com/v1/user' \
  -H 'Origin: https://www.example.com' \
  -H 'Access-Control-Request-Method: POST' \
  -H 'Access-Control-Request-Headers: authorization,content-type'

Compare the two sets of results:

CDN response

Origin response

More likely issue

The CDN is missing CORS headers

The origin has correct CORS headers

The CDN removed, overwrote, or cached the response headers

Both the CDN and origin are missing them

Origin application or web server configuration

Duplicate headers at the CDN

Both the origin and CDN are adding them

The CDN returns 403, while the origin returns 204

The WAF or edge rules block OPTIONS

The CDN returns an old Origin

The CDN cache does not distinguish between Origins

The headers are the same on both sides, but the browser still fails

Check credentials, redirects, and the actual frontend Origin

You can also use CdnChart'sCDN detection toolto confirm whether the API domain goes through a CDN and to see the CNAME and node IP it currently resolves to.

If the problem occurs only in certain regions, you can usewebsite speed test toolto observe status codes and node results across regions, then inspect the specific response headers further from the browser or command line.

The most easily overlooked issue: CORS responses cached incorrectly by the CDN

Suppose the API allows access from two backend domains:

https://admin-a.example.com
https://admin-b.example.com

The origin dynamically returns based on the Origin in the request:

Access-Control-Allow-Origin: https://admin-a.example.com

or:

Access-Control-Allow-Origin: https://admin-b.example.com

If the CDN cache key does not include Origin and it is not handled correctly:

Vary: Origin

this can happen:

  1. Backend A accesses the API first.

  2. The CDN caches the response containing A's domain.

  3. Backend B then requests the same URL.

  4. The CDN returns A's response directly to B.

  5. B's browser sees that the allowed Origin does not match and reports a CORS error.

This type of problem is characterized by:

  • It temporarily recovers after purging the cache

  • The first domain to access it works, while the other fails

  • The problem does not occur every time

  • Results differ across CDN nodes

  • In the response,Access-Control-Allow-Originchanges

When fixing it, confirm:

  • The origin returnsVary: Origin

  • The CDN supports and correctly honorsVary

  • Whether the CDN cache key needs to include Origin

  • Whether the API response is actually suitable for caching

  • Whether OPTIONS responses are incorrectly cached for a long time

  • Whether multiple allowed domains are configured consistently

Do not simply reflect the Origin from the request into the response header, especially when cookies or authentication information are allowed. First compare the Origin against an explicit allowlist, and only return the corresponding Origin if it passes.

Duplicate Access-Control-Allow-Origin headers also cause failures

Some teams have already configured this at the origin:

Access-Control-Allow-Origin: https://www.example.com

Then they add the same response header again in the CDN console.

The browser may end up receiving:

Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Origin: *

or:

Access-Control-Allow-Origin: https://www.example.com, *

This does not mean a broader allowed range; it is an invalid configuration.

Access-Control-Allow-OriginOnly one valid value should be returned. MDN also notes that if a response contains multiple instances of this field, an Origin mismatch error can occur.

Use the following command to directly check for duplicate headers:

curl -sS -D - -o /dev/null \
  'https://api.example.com/v1/user' \
  -H 'Origin: https://www.example.com'

If both the origin and the CDN are adding CORS headers, it is best to keep only one clear configuration layer. Otherwise, future changes can easily make the rules on the two sides inconsistent.

Error responses also need CORS headers

There is another frustrating situation:

The API actually returns 401, 403, 429, or 500, but because the error response has no CORS headers, the browser only shows a CORS error, and the frontend cannot see the original error content.

For example, the API returns:

HTTP/2 401 Unauthorized
Content-Type: application/json

but is missing:

Access-Control-Allow-Origin: https://www.example.com

Frontend JavaScript may be unable to read the 401 response body and can only see a vague CORS message.

Therefore, check whether CORS headers cover:

  • 2xx success responses

  • 4xx authentication and rate-limiting responses

  • 5xx server errors

  • WAF block pages

  • CDN custom error pages

  • Errors returned directly by Nginx

  • Errors returned by application exception handlers

If you use Nginx to add response headers, check based on your actual needs whether you need to use thealwaysparameter:

add_header Access-Control-Allow-Origin "https://www.example.com" always;
add_header Access-Control-Allow-Credentials "true" always;
add_header Vary "Origin" always;

But this is only an example for a single allowed domain. In multi-domain scenarios, use a strict allowlist, and do not unconditionally allow any Origin on APIs that use credentials.

A WAF may allow GET but block OPTIONS

Some security rules allow only the common GET and POST methods and treat OPTIONS as an abnormal method.

The symptoms are usually:

GET接口直接访问正常
OPTIONS返回403
浏览器不发送后续POST
Postman调用却成功

In this case, check:

  • Whether the CDN allows the OPTIONS method

  • Whether the WAF blocks OPTIONS

  • Whether the origin route handles OPTIONS

  • Whether the load balancer forwards OPTIONS

  • Whether the API gateway has a route configured for OPTIONS

  • Whether authentication middleware incorrectly requires the preflight request to be logged in

  • Whether hotlink protection rules check the wrong Referer or Origin

A preflight request itself usually does not carry the authentication credentials used in the actual business request. You must not require OPTIONS to pass user login authentication first; otherwise, the browser may never be able to send the actual request.

Redirects can also cause preflight failures

When checking the OPTIONS response, also watch for:

301 Moved Permanently
302 Found
307 Temporary Redirect
308 Permanent Redirect

Common redirects include:

http://api.example.com → https://api.example.com
api.example.com/v1 → api.example.com/v1/
旧域名 → 新域名
无www → 有www

Browsers handle cross-origin preflight requests and redirects more strictly than ordinary page requests. Even if the final address has the correct CORS headers, an intermediate redirect can still cause the request to fail.

It is best to use the final URL directly as the frontend API address rather than relying on multiple redirects.

You can use:

curl -i -X OPTIONS \
  'https://api.example.com/v1/user' \
  -H 'Origin: https://www.example.com' \
  -H 'Access-Control-Request-Method: POST'

If you seeLocationresponse header, continue checking whether the URL used by the frontend needs to be changed.

Why does the browser still report errors after I change the CORS configuration?

The browser itself may cache preflight results, and the CDN may also cache OPTIONS or API responses.

A common cache control in preflight responses is:

Access-Control-Max-Age: 600

It indicates that the browser can reuse the preflight result for a period of time. The Fetch standard also defines the browser's CORS preflight cache.

After changing the configuration, you can try:

  • Clear the browser cache

  • Retest in an incognito window

  • Disable caching in DevTools

  • Purge the CDN cache for the corresponding URL

  • Check whether OPTIONS is cached separately

  • Test from a different node or network

  • Confirm that the actual request is not hitting an old response

But do not treat 'clearing the cache' as the final solution. If the cache key andVary: Originare themselves misconfigured, the problem will recur.

What should you keep from a complete CORS investigation?

So that frontend, backend, and CDN engineers can see the same evidence, it is recommended to save:

前端页面完整Origin
API完整URL
实际请求方法
实际请求头
OPTIONS状态码
OPTIONS响应头
实际请求状态码
实际响应头
CDN节点IP
源站IP
请求发生时间
CDN请求ID
浏览器控制台完整报错

In particular, do not just say 'it's a cross-origin issue.'

'It's a cross-origin issue' only means the browser refused to read the response; it does not indicate which field is wrong. Post the complete response headers for the OPTIONS and the actual request, and the problem can usually be narrowed down quickly to a specific configuration.

FAQ

The API works in Postman, so why does the browser still fail?

Because Postman usually does not enforce the browser's same-origin policy and CORS checks. A successful Postman request only proves the API is reachable; it does not prove the API returns the correct cross-origin response headers.

Is setting Access-Control-Allow-Origin to*the easiest approach?

It is only suitable for public resources without credentials. If the request carries cookies or HTTP authentication, or the frontend usescredentials: "include", you cannot use*, you must return a specific allowed Origin.

Is it normal for OPTIONS to return 204?

Yes. A preflight request does not need a business response body; returning 200 or 204 is fine. The key is that the status code is successful and includes the correct allowed Origin, methods, and headers.

Why do CORS errors appear only after login?

Requests after login usually carry cookies or authentication information, so you need to correctly configure bothAccess-Control-Allow-Credentials: true, and return a specific Origin; you cannot continue using*.

Why do CORS issues appear only when there is an error?

It is likely that the 401, 403, 429, or 500 responses do not include CORS headers. Just because the success response is configured correctly does not mean the error responses are too.

Should CORS be configured at the CDN or the origin?

Either approach can work, but it is best to choose one primary configuration layer. API permission logic is usually better managed by the origin or API gateway; the CDN can add consistent response headers, but avoid duplicate additions, overwrites, or caching errors between the origin and CDN.

  • API cross-origin
  • CORS errors
  • CDN CORS configuration
  • Access-Control-Allow-Origin
  • OPTIONS preflight request
  • CDN response headers
  • Frontend API requests blocked

Related posts

Some users cannot access the site after enabling IPv6: Is it a DNS issue or a CDN node issue?

Some users cannot access the site after enabling IPv6: Is it a DNS issue or a CDN node issue?

After IPv6 is enabled on a website, users in some regions or on certain carriers may be unable to reach it. The cause may lie in the AAAA record, the user's IPv6 network, a CDN node, TLS, MTU, or IPv6 origin fetch. This article covers DNS, curl, and route testing methods to help determine which layer the failure occurs at.

12 min read
Origin Server Accessible but CDN Returns 404? How to Troubleshoot Origin Host and Cache

Origin Server Accessible but CDN Returns 404? How to Troubleshoot Origin Host and Cache

Origin server responding normally, but you get a 404 after putting the CDN in front of it? This article shows you how to tell whether the 404 comes from a CDN node or from your origin, then walks you step by step through troubleshooting origin Host header, port, protocol, caching, URL rewriting, and multi-origin configuration issues.

10 min read
Are CDNs Right for Dynamic APIs? Distinguish Caching From Dynamic Acceleration

Are CDNs Right for Dynamic APIs? Distinguish Caching From Dynamic Acceleration

Can dynamic APIs use a CDN? This article explains the differences between API caching, dynamic acceleration, and edge computing, analyzes caching strategies for public endpoints, login endpoints, and GET versus POST requests, and covers Cache-Control, cache keys, security checks, and multi-region speed testing.

20 min read
How to Choose a CDN for Download Sites: Large-File Speed Tests Need More Than Time to First Byte

How to Choose a CDN for Download Sites: Large-File Speed Tests Need More Than Time to First Byte

When choosing a CDN for a download site, latency and time to first byte are not the only factors. This article explains large-file download speed, sustained throughput, cache hit rate, byte-range chunking, resumable downloads, concurrent downloads, and multi-region speed testing—helping download sites choose a CDN that is genuinely suited to file distribution.

19 min read