Your site loads, but every custom font drops straight back to Times New Roman. You open DevTools and see a CORS policy error blocking your .woff2 files. I ran into this exact headache the second I shifted our static assets onto a dedicated CDN subdomain. The fix comes down to two things: setting the right headers at the origin server and making sure your CDN doesn’t cache a broken, headerless response.
Here is how to fix your origin headers in Nginx or Apache, set up proper edge caching rules for fonts and assets, and verify everything from the terminal so your typography actually renders.

Why CDN Fonts Trigger CORS When Images Don’t
Browsers treat font files loaded via @font-face under strict cross-origin rules. While standard images (.png, .svg) load freely across origins by default, fonts follow the Fetch spec. That means they require explicit Cross-Origin Resource Sharing (CORS) headers whenever they are requested from a different domain or subdomain.
If your HTML is served from example.com and your assets live on cdn.example.com, the browser treats that request as cross-origin. When it grabs Inter-Regular.woff2, it expects an Access-Control-Allow-Origin header in the response.
If your origin skips this header—or if your CDN strips it or caches a request made without an Origin header—the browser refuses to render the font. Your console logs an error like this:
Access to font at 'https://cdn.example.com/fonts/inter.woff2' from origin 'https://example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.Fixing this requires configuring both your origin server (Nginx or Apache) and your CDN edge distribution.
Inspect the Header Failure with cURL
Before changing configs, see what your origin and CDN are actually returning. Send a curl request simulating a cross-origin font request by passing the Origin header:
curl -I -H "Origin: https://example.com" https://cdn.example.com/fonts/inter.woff2Look for this in the response headers:
HTTP/2 200
date: Mon, 14 Apr 2026 10:15:20 GMT
content-type: font/woff2
content-length: 18420
cache-control: max-age=31536000, public
x-cache: HITIf access-control-allow-origin is missing, your browser will reject the font every time. Next, query your origin server directly (bypassing the CDN) to see if the origin is failing to send it in the first place:
curl -I -H "Origin: https://example.com" https://origin.example.com/fonts/inter.woff2If the origin doesn’t return the header, that is where we need to start.
Fix CORS Headers on Nginx and Apache
Your origin web server needs to explicitly serve CORS headers on static font files. Here is how to configure both Nginx and Apache.
Nginx Configuration
Open your site configuration file (typically in /etc/nginx/sites-available/example.com). Add a dedicated location block matching your font file extensions.
Use the Nginx headers module to attach the CORS headers:
# /etc/nginx/sites-available/example.com location ~* .(eot|otf|ttf|woff|woff2)$ { add_header Access-Control-Allow-Origin "*" always; add_header Access-Control-Allow-Methods "GET, OPTIONS" always; add_header Access-Control-Allow-Headers "*" always; add_header Vary "Origin, Accept-Encoding" always; expires 1y; add_header Cache-Control "public, max-age=31536000, immutable"; access_log off;
}The always parameter ensures Nginx attaches the header even on 3xx or 4xx responses. Adding Vary: Origin tells downstream caches that responses can differ depending on the request’s origin.
Test the syntax and reload Nginx:
sudo nginx -t && sudo systemctl reload nginxApache (.htaccess) Configuration
If you’re running Apache or a standard WordPress stack, add these directives inside your root .htaccess file within the mod_headers block:
Header set Access-Control-Allow-Origin "*" Header set Access-Control-Allow-Methods "GET, OPTIONS" Header set Access-Control-Allow-Headers "*" Header append Vary "Origin, Accept-Encoding" Header set Cache-Control "public, max-age=31536000, immutable"
If you are also tuning how dynamic paths interact with your cache, check out our guide on configuring CDN edge cache rules for WordPress cookies.
Configure CDN Edge Cache Rules for Static Assets
Once your origin emits CORS headers, update your CDN edge rules. The edge cache must pass those headers through and apply long time-to-live (TTL) settings to static files.
1. Header Forwarding and Preservation
Make sure your CDN distribution doesn’t strip Access-Control-Allow-Origin or Vary from origin responses. In your CDN dashboard:
- Enable Origin Header Pass-Through or Preserve CORS Headers.
- Ensure the edge respects the origin’s
Cache-Controlheaders instead of overwriting them with a zero-TTL policy. - Verify that
OPTIONSpreflight requests return a200 OKor204 No Contentstatus with CORS headers included.
2. Static Asset Edge TTL Rules
Hashed static assets (like bundle-a91bc.js or inter-v3.woff2) never change on disk. Cache them aggressively at the edge:
Asset TypeExtensionsEdge TTLBrowser TTL (Cache-Control)Web Fonts.woff, .woff2, .ttf, .otf, .eot1 Year (31536000s)public, max-age=31536000, immutableImages.webp, .avif, .png, .jpg, .svg30 Days (2592000s)public, max-age=2592000Styles & Scripts.css, .js7 Days (604800s)public, max-age=604800, stale-while-revalidate=86400
To keep multiple edge nodes from hammering your origin simultaneously for these assets, look into setting up a CDN origin shield and tiered caching architecture.
The Vary: Origin Gotcha That Breaks CDN Caches
Here is a subtle bug I ran into: fonts loaded fine in some browsers, but failed randomly for users clicking links from external apps.
If a crawler or non-browser client requests https://cdn.example.com/fonts/inter.woff2 without an Origin header, an origin server might return the file without CORS headers. If the CDN caches that response under a single cache key, subsequent requests from browsers (which do include an Origin header) get served that cached response missing Access-Control-Allow-Origin.
That is why setting Access-Control-Allow-Origin: * unconditionally on font paths—regardless of whether an Origin header was sent in the request—is the cleanest fix for public fonts. Pairing it with Vary: Origin instructs the CDN to keep separate cache entries when origin headers differ.
For more details on how this works under the hood, check the MDN Vary header documentation.
Purge the CDN Cache and Verify
After updating Nginx or Apache and verifying your edge settings, purge your CDN cache. Otherwise, the CDN will keep serving the cached, headerless response until its TTL runs out.
- Purge
/fonts/*(or run a full cache purge) in your CDN control panel. - Run a cURL request simulating a browser cross-origin fetch:
curl -I -X GET -H "Origin: https://example.com" -H "Accept-Encoding: gzip, deflate, br" https://cdn.example.com/fonts/inter.woff2Verify that your response headers contain these lines:
HTTP/2 200
access-control-allow-origin: *
access-control-allow-methods: GET, OPTIONS
access-control-allow-headers: *
vary: Origin, Accept-Encoding
cache-control: public, max-age=31536000, immutable
x-cache: MISSRun the exact same command again. The x-cache header should flip from MISS to HIT, while access-control-allow-origin: * stays intact.
If query strings from ad campaigns are causing cache misses on your static assets, check out our guide to strip marketing query strings at the CDN edge.
Handling Resilient Upstreams with Stale Cache Directives
For high-traffic production sites, static assets should stay available even if your origin server reboots or hiccups during a deployment.
Combine your CORS and caching headers with resilient caching directives by adding stale-while-revalidate and stale-if-error to your origin config:
# Inside Nginx static location block
add_header Cache-Control "public, max-age=86400, stale-while-revalidate=604800, stale-if-error=2592000" always;For a deeper dive on fallback behavior during origin outages, check out our guide on configuring stale-while-revalidate on Nginx behind a CDN.
Frequently Asked Questions
Why do images work from my CDN without CORS, but fonts fail?
Images aren’t subject to cross-origin restrictions unless they’re drawn to a canvas or requested with specific CORS attributes. Fonts loaded through CSS @font-face always use the CORS fetch mode per W3C specs, which makes CORS headers mandatory.
Can I set Access-Control-Allow-Origin to my specific domain instead of an asterisk?
Yes. If you want to prevent hotlinking, set it to your domain (like Access-Control-Allow-Origin: https://example.com). Just make sure your server returns Vary: Origin so your CDN doesn’t serve that cached domain-specific header to other subdomains.
Do font files need Access-Control-Allow-Credentials?
No. Font requests triggered by CSS @font-face don’t send cookies or auth tokens by default. You should not set Access-Control-Allow-Credentials: true for static font files.
Why does the font load when I visit the URL directly in Chrome?
Pasting the font URL directly into the address bar is a top-level navigation request, not a cross-origin font fetch. The browser downloads the file directly without checking CORS headers, masking the error that happens when your stylesheet requests it.
Next Steps
With CORS headers active and your static asset edge rules tuned, your web fonts will render reliably across all browsers without getting blocked by security policies.
Next, read our guide on stripping marketing query strings at the edge to boost your cache hit ratios and keep redundant asset requests away from your origin.

