Fixing Font CORS Errors: Complete Troubleshooting Guide
Comprehensive guide to understanding and fixing Cross-Origin Resource Sharing (CORS) errors that prevent web fonts from loading. Master server configuration, CDN setup, and HTML fixes.
In Simple Terms
CORS errors block font loading when the server doesn't send Access-Control-Allow-Origin header. Check DevTools Console for "CORS policy" errors.Fix: Add header Access-Control-Allow-Origin "*" (Nginx) or Header set Access-Control-Allow-Origin "*" (Apache) for font file types (.woff2, .woff, .ttf).HTML preload requires crossorigin attribute: <link rel="preload" href="font.woff2" as="font" crossorigin>. Even same-origin fonts need this.
In this article
CORS (Cross-Origin Resource Sharing) errors are among the most frustrating web development issues, causing fonts to fail silently while displaying cryptic error messages in the browser console. When a CORS error occurs, your website attempts to load fonts from a server (your own server, a CDN, or a subdomain), but the browser blocks the request due to security restrictions, leaving text to render in fallback fonts or remain invisible. Unlike other web resources like images or scripts, fonts have uniquely strict CORS requirements—browsers enforce CORS policies for all font files regardless of whether they're hosted on the same domain or cross-origin, making proper CORS configuration absolutely essential for web fonts to function.
The root cause of font CORS errors lies in web security architecture designed to prevent malicious websites from stealing fonts and other resources from legitimate sites. Browsers implement the Same-Origin Policy, which blocks cross-origin requests by default unless the server explicitly permits them through CORS headers. For fonts specifically, browsers require the Access-Control-Allow-Origin header to be present in the server's HTTP response, even when loading fonts from your own domain. This seemingly counterintuitive requirement exists because fonts can contain embedded code and metadata that could potentially be exploited, making them a security-sensitive resource type that demands explicit permission from the server.
Understanding CORS errors requires knowledge of several interconnected systems: how browsers enforce origin policies and when they trigger CORS checks, how web servers send HTTP response headers and how to configure them correctly, how CDNs (Content Delivery Networks) handle CORS headers and cache behavior, how HTML preload links interact with CORS through the crossorigin attribute, and how different hosting platforms (Netlify, Vercel, AWS, etc.) handle CORS configuration. Each of these systems must be properly configured for fonts to load successfully, and a misconfiguration at any level can cause complete font loading failure.
This comprehensive guide provides everything you need to diagnose and permanently fix font CORS errors. You'll learn to identify CORS errors in browser developer tools and distinguish them from other font loading issues, configure web servers (Apache, Nginx, Node.js, IIS) to send proper CORS headers for font files, set up major CDNs (Cloudflare, AWS CloudFront, Fastly) with correct CORS policies, add the crossorigin attribute to HTML preload links and understand why it's required even for same-origin fonts, troubleshoot persistent CORS issues using systematic debugging approaches, and implement best practices that prevent CORS errors from occurring in production. Whether you're dealing with fonts on your own server, third-party CDNs, or complex multi-domain setups, this guide provides clear, actionable solutions.
Understanding CORS Errors
What Is CORS?
CORS (Cross-Origin Resource Sharing) is a security mechanism implemented by web browsers to control which websites can access resources from other domains. An "origin" consists of three parts: protocol (http/https), domain (example.com), and port (80/443). Any difference in these three components makes a request "cross-origin."
Origin Examples:
Same Origin:
• https://example.com/page → https://example.com/fonts/font.woff2 ✓
Cross-Origin (Different):
• https://example.com → https://cdn.example.com (different subdomain) ✗
• https://example.com → http://example.com (different protocol) ✗
• https://example.com:443 → https://example.com:8080 (different port) ✗
• https://example.com → https://fonts.googleapis.com (different domain) ✗
Common CORS Error Messages
Chrome/Edge Error:
Access to font at 'https://cdn.example.com/fonts/font.woff2' from origin 'https://example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Firefox Error:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://cdn.example.com/fonts/font.woff2. (Reason: CORS header 'Access-Control-Allow-Origin' missing).
Safari Error:
Origin https://example.com is not allowed by Access-Control-Allow-Origin. Status code: 200
Why CORS Errors Occur
- Server not sending CORS headers: The server hosting fonts doesn't include Access-Control-Allow-Origin in HTTP response
- Missing crossorigin attribute: HTML preload links for fonts lack required crossorigin attribute
- CDN misconfiguration: CDN not configured to forward or add CORS headers for font files
- Wrong protocol: Mixed content (HTTPS page loading HTTP fonts) triggers CORS
- Subdomain considered cross-origin: www.example.com vs example.com are different origins
- Caching issues: Browser or CDN serving cached response without CORS headers
Why Fonts Need CORS
Unique Font Requirements
Unlike images, CSS, or JavaScript, fonts have special CORS requirements that can seem counterintuitive. Browsers enforce CORS for fonts even when they're loaded from the same origin as your website.
Why Fonts Are Special:
- • Security concern: Font files can contain executable code and metadata
- • Intellectual property: Fonts are valuable copyrighted assets worth thousands of dollars
- • Licensing enforcement: CORS helps prevent unauthorized font redistribution
- • Origin checking: Ensures fonts are only used on permitted domains
- • Browser security model: Fonts treated as security-sensitive resources
CORS Requirements for Fonts
All fonts must have:
- Access-Control-Allow-Origin header in server HTTP response
Access-Control-Allow-Origin: *
Or specify exact domain:
Access-Control-Allow-Origin: https://example.com
- crossorigin attribute on preload links (even same-origin!)
<link rel="preload" href="/fonts/font.woff2" as="font" crossorigin>
- Correct MIME type for font format
Content-Type: font/woff2
Identifying CORS Errors
Using Browser DevTools
- Open Developer Tools: Press F12 or right-click → Inspect
- Check Console Tab:
- Look for red error messages mentioning "CORS", "Access-Control-Allow-Origin", or "Cross-Origin"
- Note the font file URL and origin in error message
- Copy exact error text for reference
- Check Network Tab:
- Filter by "Font" or search for .woff2/.woff
- Click on font file request
- Look at "Headers" section → "Response Headers"
- Check if "Access-Control-Allow-Origin" is present
- Status code should be 200 OK (CORS errors can show 200 but still fail)
Command Line Testing
Test CORS Headers with cURL:
# Check if CORS headers are present curl -I -H "Origin: https://example.com" https://cdn.example.com/fonts/font.woff2 # Look for this in response: # Access-Control-Allow-Origin: * # or # Access-Control-Allow-Origin: https://example.com # If missing, CORS is not configured
Test with Different Origins:
# Test from your domain curl -I -H "Origin: https://yoursite.com" https://cdn.example.com/fonts/font.woff2 # Test from different domain curl -I -H "Origin: https://different-site.com" https://cdn.example.com/fonts/font.woff2 # Compare responses - CORS header should allow both or specify allowed origin
Server Configuration
Apache (.htaccess)
Basic Configuration (Allow All Origins):
# Add to .htaccess in root or fonts directory
<FilesMatch "\.(woff2|woff|ttf|otf|eot)$">
Header set Access-Control-Allow-Origin "*"
</FilesMatch>
# Or use IfModule for safety
<IfModule mod_headers.c>
<FilesMatch "\.(woff2|woff|ttf|otf|eot)$">
Header set Access-Control-Allow-Origin "*"
</FilesMatch>
</IfModule>Specific Domain Only:
<FilesMatch "\.(woff2|woff|ttf|otf|eot)$">
Header set Access-Control-Allow-Origin "https://yoursite.com"
</FilesMatch>
# Multiple domains
<FilesMatch "\.(woff2|woff|ttf|otf|eot)$">
SetEnvIf Origin "https://site1\.com" AccessControlAllowOrigin=$0
SetEnvIf Origin "https://site2\.com" AccessControlAllowOrigin=$0
Header set Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
</FilesMatch>Complete Font Configuration:
# Comprehensive font serving configuration
<FilesMatch "\.(woff2|woff|ttf|otf|eot)$">
# CORS
Header set Access-Control-Allow-Origin "*"
# Cache control
Header set Cache-Control "public, max-age=31536000, immutable"
# Correct MIME types
<IfModule mod_mime.c>
AddType font/woff2 .woff2
AddType font/woff .woff
AddType font/ttf .ttf
AddType font/otf .otf
</IfModule>
</FilesMatch>Nginx
Basic Configuration:
# Add to nginx.conf or site config file
location ~* \.(woff2|woff|ttf|otf|eot)$ {
add_header Access-Control-Allow-Origin "*";
add_header Cache-Control "public, max-age=31536000, immutable";
}Specific Domain:
location ~* \.(woff2|woff|ttf|otf|eot)$ {
add_header Access-Control-Allow-Origin "https://yoursite.com";
add_header Vary Origin;
}Multiple Domains with Conditional Logic:
map $http_origin $cors_header {
default "";
"~^https://site1\.com$" "$http_origin";
"~^https://site2\.com$" "$http_origin";
"~^https://site3\.com$" "$http_origin";
}
location ~* \.(woff2|woff|ttf|otf|eot)$ {
add_header Access-Control-Allow-Origin $cors_header;
add_header Vary Origin;
}Node.js / Express
Using cors Middleware:
const express = require('express');
const cors = require('cors');
const app = express();
// Allow CORS for all routes
app.use(cors());
// Or specifically for fonts directory
app.use('/fonts', cors());
// Serve static files
app.use('/fonts', express.static('public/fonts'));Manual CORS Headers:
app.use('/fonts', (req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});
app.use('/fonts', express.static('public/fonts'));Specific Origins Only:
const corsOptions = {
origin: ['https://yoursite.com', 'https://www.yoursite.com'],
optionsSuccessStatus: 200
};
app.use('/fonts', cors(corsOptions));
app.use('/fonts', express.static('public/fonts'));IIS (Windows Server)
web.config:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Methods" value="GET" />
</customHeaders>
</httpProtocol>
<staticContent>
<mimeMap fileExtension=".woff2" mimeType="font/woff2" />
<mimeMap fileExtension=".woff" mimeType="font/woff" />
</staticContent>
</system.webServer>
</configuration>CDN Configuration
Cloudflare
Method 1: Transform Rules (Recommended):
- Log into Cloudflare Dashboard
- Select your domain
- Go to Rules → Transform Rules
- Create "HTTP Response Header Modification" rule
- Set condition:
File extension is woff2 or woff - Set response header:
Access-Control-Allow-Origin: * - Deploy rule
Method 2: Page Rules:
- Go to Rules → Page Rules
- Create new page rule
- URL pattern:
*yoursite.com/fonts/* - Add setting: Browser Cache TTL, Cache Level: Everything
- Note: Page Rules can't add CORS headers directly - must be set at origin
AWS CloudFront
Using Lambda@Edge:
// Lambda function to add CORS headers
exports.handler = async (event) => {
const response = event.Records[0].cf.response;
const headers = response.headers;
// Add CORS header
headers['access-control-allow-origin'] = [{
key: 'Access-Control-Allow-Origin',
value: '*'
}];
return response;
};
// Attach to CloudFront distribution's Origin Response triggerS3 Bucket CORS Configuration:
<!-- S3 Bucket → Permissions → CORS configuration -->
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "HEAD"],
"AllowedOrigins": ["*"],
"ExposeHeaders": [],
"MaxAgeSeconds": 3000
}
]Netlify
_headers file in root:
# Netlify _headers file /fonts/* Access-Control-Allow-Origin: * Cache-Control: public, max-age=31536000, immutable # Or specific file types /*.woff2 Access-Control-Allow-Origin: * /*.woff Access-Control-Allow-Origin: *
Vercel
vercel.json configuration:
{
"headers": [
{
"source": "/fonts/(.*)",
"headers": [
{
"key": "Access-Control-Allow-Origin",
"value": "*"
},
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
}
]
}HTML and CSS Fixes
Critical: crossorigin Attribute
❌ WRONG - Missing crossorigin:
<link rel="preload" href="/fonts/font.woff2" as="font" type="font/woff2"> <!-- Font will fail to load or be downloaded twice -->
✓ CORRECT - With crossorigin:
<link rel="preload" href="/fonts/font.woff2" as="font" type="font/woff2" crossorigin> <!-- Required even for same-origin fonts! -->
Why crossorigin is Required:
- • Fonts are fetched in "CORS mode" regardless of origin
- • Without crossorigin, preload uses different mode than @font-face
- • Browser downloads font twice: once for preload, once for @font-face
- • Always add crossorigin, even for same-domain fonts
Complete HTML Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- Preload fonts with crossorigin -->
<link rel="preload"
href="/fonts/myfont-regular.woff2"
as="font"
type="font/woff2"
crossorigin>
<link rel="preload"
href="https://cdn.example.com/fonts/myfont-bold.woff2"
as="font"
type="font/woff2"
crossorigin> <!-- crossorigin for CDN fonts too -->
<style>
@font-face {
font-family: 'MyFont';
src: url('/fonts/myfont-regular.woff2') format('woff2');
font-weight: 400;
font-display: swap;
}
</style>
</head>
<body>
<h1>Content loads with proper CORS</h1>
</body>
</html>Troubleshooting Guide
Systematic Debugging Process
- Verify CORS headers are being sent:
- Open DevTools → Network tab
- Click font file request
- Check Response Headers for Access-Control-Allow-Origin
- If missing, server configuration is wrong
- Check crossorigin attribute:
- View page source (Ctrl+U)
- Search for "preload" links
- Verify crossorigin attribute present
- Add if missing
- Clear all caches:
- Hard refresh browser (Ctrl+Shift+R)
- Clear browser cache completely
- Purge CDN cache if using CDN
- Disable cache in DevTools during testing
- Test with cURL:
curl -I -H "Origin: https://yoursite.com" https://yoursite.com/fonts/font.woff2
- Check for mixed content:
- Ensure HTTPS page doesn't load HTTP fonts
- All resources must use same protocol
Common Issues and Solutions
Issue: CORS headers not appearing
Cause: Server configuration not applied or wrong file location
Solution: Verify .htaccess is in correct directory, check server has mod_headers enabled, restart server after config changes
Issue: Fonts work locally but fail in production
Cause: Development server automatically adds CORS, production doesn't
Solution: Configure production server with CORS headers, test in production-like environment
Issue: CDN fonts still failing after configuration
Cause: CDN serving cached response without CORS headers
Solution: Purge CDN cache, wait for cache to clear (can take hours), add version query parameter (?v=2)
Best Practices
CORS Best Practices Checklist
- ☐ Configure server to send Access-Control-Allow-Origin header for all font files
- ☐ Add crossorigin attribute to ALL font preload links (even same-origin)
- ☐ Use wildcard (*) for public fonts or specify exact domains for private fonts
- ☐ Set proper MIME types (font/woff2, font/woff) on server
- ☐ Configure CORS at origin server not just CDN
- ☐ Test CORS headers with cURL before deploying
- ☐ Add Cache-Control headers along with CORS headers
- ☐ Use HTTPS for all font requests (avoid mixed content)
- ☐ Document CORS configuration for future developers
- ☐ Monitor production for CORS errors in error tracking
Summary: Fixing Font CORS Errors
CORS errors prevent fonts from loading due to browser security policies that require explicit permission from servers. Fix CORS errors by configuring your web server (Apache, Nginx, Node.js) to send the Access-Control-Allow-Origin header for all font files, adding the crossorigin attribute to HTML preload links even for same-origin fonts, and ensuring CDNs forward or add CORS headers properly. Always test with browser DevTools and cURL to verify headers are present.
The two critical requirements are: server must send Access-Control-Allow-Origin in HTTP response headers, and HTML must include crossorigin on preload links. Without both, fonts will fail to load with CORS errors. Use Access-Control-Allow-Origin: * for public fonts or specify exact domains for private fonts. After configuration changes, clear all caches (browser, server, CDN) and test thoroughly in production environment.

Written by
Sarah Mitchell
Product Designer, Font Specialist

Verified by
Marcus Rodriguez
Lead Developer
