Introduction
When web fonts are served from a CDN domain (e.g., cdn.example.com) that differs from the page origin (e.g., www.example.com), browsers enforce CORS policy on font requests. Firefox is particularly strict about this and will silently fail to load fonts without the correct CORS headers:
Access to font at 'https://cdn.example.com/fonts/roboto.woff2' from origin
'https://www.example.com' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested resource.The result is missing or fallback fonts, affecting the visual design of the page.
Symptoms
- Fonts do not load in Firefox but work in Chrome
- Browser console shows CORS error for font files (.woff2, .woff, .ttf)
- Page displays fallback fonts instead of the intended custom fonts
curl -I https://cdn.example.com/fonts/roboto.woff2shows noAccess-Control-Allow-Originheader- Font files return 200 OK in the Network tab but are not applied to the page
Common Causes
- CDN does not forward or add
Access-Control-Allow-Originheader to font responses - Firefox enforces stricter CORS for fonts than Chrome or Safari
- Font served from a different subdomain without CORS configuration
- AWS CloudFront or similar CDN strips
Originheader by default - CSS
@font-facerule uses a different origin than the page
Step-by-Step Fix
- 1.Configure CDN CORS headers for font files. For AWS CloudFront, configure the origin response to include headers. For Nginx as CDN origin:
- 2.```nginx
- 3.location ~* \.(woff2|woff|ttf|otf|eot)$ {
- 4.add_header Access-Control-Allow-Origin *;
- 5.add_header Cache-Control "public, max-age=31536000, immutable";
- 6.expires 30d;
- 7.}
- 8.
` - 9.Configure Apache for font CORS:
- 10.```apache
- 11.<FilesMatch "\.(woff2|woff|ttf|otf|eot)$">
- 12.Header set Access-Control-Allow-Origin "*"
- 13.Header set Cache-Control "public, max-age=31536000, immutable"
- 14.</FilesMatch>
- 15.
` - 16.For AWS S3 + CloudFront, set the CORS configuration on the S3 bucket:
- 17.```xml
- 18.<CORSConfiguration>
- 19.<CORSRule>
- 20.<AllowedOrigin>*</AllowedOrigin>
- 21.<AllowedMethod>GET</AllowedMethod>
- 22.<AllowedHeader>*</AllowedHeader>
- 23.<MaxAgeSeconds>3000</MaxAgeSeconds>
- 24.</CORSRule>
- 25.</CORSConfiguration>
- 26.
` - 27.Then invalidate the CloudFront cache for font files.
- 28.Verify the CORS header is present after configuration:
- 29.```bash
- 30.curl -I -H "Origin: https://www.example.com" https://cdn.example.com/fonts/roboto.woff2 | grep -i "access-control"
- 31.
` - 32.Expected response:
- 33.
` - 34.access-control-allow-origin: *
- 35.
` - 36.For Google Fonts via CDN, self-host the fonts instead of proxying them through a different origin:
- 37.```css
- 38./* Download fonts and serve from same origin */
- 39.@font-face {
- 40.font-family: 'Roboto';
- 41.src: url('/fonts/roboto.woff2') format('woff2');
- 42.font-weight: 400;
- 43.font-display: swap;
- 44.}
- 45.
`
Prevention
- Serve fonts from the same origin as the page when possible to avoid CORS entirely
- Include CORS header configuration for font file types in your CDN setup documentation
- Test font loading in Firefox during QA, as it is the strictest browser for font CORS
- Add font CORS verification to your deployment checklist:
- ```bash
- curl -sI -H "Origin: https://www.example.com" https://cdn.example.com/fonts/test.woff2 | grep -q "access-control-allow-origin"
`- Use
font-display: swapin your@font-facerules to ensure text is visible while custom fonts load - Monitor font loading failures using the Font Loading API:
- ```javascript
- document.fonts.ready.then(() => {
- document.fonts.forEach(font => {
- if (font.status === 'error') {
- console.error('Font failed to load:', font.family);
- }
- });
- });
`