Font Converter

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.

TL;DR

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.

Share this page to:

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

  1. Server not sending CORS headers: The server hosting fonts doesn't include Access-Control-Allow-Origin in HTTP response
  2. Missing crossorigin attribute: HTML preload links for fonts lack required crossorigin attribute
  3. CDN misconfiguration: CDN not configured to forward or add CORS headers for font files
  4. Wrong protocol: Mixed content (HTTPS page loading HTTP fonts) triggers CORS
  5. Subdomain considered cross-origin: www.example.com vs example.com are different origins
  6. 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:

  1. Access-Control-Allow-Origin header in server HTTP response
    Access-Control-Allow-Origin: *

    Or specify exact domain:

    Access-Control-Allow-Origin: https://example.com
  2. crossorigin attribute on preload links (even same-origin!)
    <link rel="preload" href="/fonts/font.woff2" as="font" crossorigin>
  3. Correct MIME type for font format
    Content-Type: font/woff2

Identifying CORS Errors

Using Browser DevTools

  1. Open Developer Tools: Press F12 or right-click → Inspect
  2. 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
  3. 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):

  1. Log into Cloudflare Dashboard
  2. Select your domain
  3. Go to Rules → Transform Rules
  4. Create "HTTP Response Header Modification" rule
  5. Set condition: File extension is woff2 or woff
  6. Set response header: Access-Control-Allow-Origin: *
  7. Deploy rule

Method 2: Page Rules:

  1. Go to Rules → Page Rules
  2. Create new page rule
  3. URL pattern: *yoursite.com/fonts/*
  4. Add setting: Browser Cache TTL, Cache Level: Everything
  5. 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 trigger

S3 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

  1. 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
  2. Check crossorigin attribute:
    • View page source (Ctrl+U)
    • Search for "preload" links
    • Verify crossorigin attribute present
    • Add if missing
  3. 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
  4. Test with cURL:
    curl -I -H "Origin: https://yoursite.com" https://yoursite.com/fonts/font.woff2
  5. 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.

Sarah Mitchell

Written by

Sarah Mitchell

Product Designer, Font Specialist

Marcus Rodriguez

Verified by

Marcus Rodriguez

Lead Developer