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:

bash
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.woff2 shows no Access-Control-Allow-Origin header
  • 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-Origin header 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 Origin header by default
  • CSS @font-face rule uses a different origin than the page

Step-by-Step Fix

  1. 1.Configure CDN CORS headers for font files. For AWS CloudFront, configure the origin response to include headers. For Nginx as CDN origin:
  2. 2.```nginx
  3. 3.location ~* \.(woff2|woff|ttf|otf|eot)$ {
  4. 4.add_header Access-Control-Allow-Origin *;
  5. 5.add_header Cache-Control "public, max-age=31536000, immutable";
  6. 6.expires 30d;
  7. 7.}
  8. 8.`
  9. 9.Configure Apache for font CORS:
  10. 10.```apache
  11. 11.<FilesMatch "\.(woff2|woff|ttf|otf|eot)$">
  12. 12.Header set Access-Control-Allow-Origin "*"
  13. 13.Header set Cache-Control "public, max-age=31536000, immutable"
  14. 14.</FilesMatch>
  15. 15.`
  16. 16.For AWS S3 + CloudFront, set the CORS configuration on the S3 bucket:
  17. 17.```xml
  18. 18.<CORSConfiguration>
  19. 19.<CORSRule>
  20. 20.<AllowedOrigin>*</AllowedOrigin>
  21. 21.<AllowedMethod>GET</AllowedMethod>
  22. 22.<AllowedHeader>*</AllowedHeader>
  23. 23.<MaxAgeSeconds>3000</MaxAgeSeconds>
  24. 24.</CORSRule>
  25. 25.</CORSConfiguration>
  26. 26.`
  27. 27.Then invalidate the CloudFront cache for font files.
  28. 28.Verify the CORS header is present after configuration:
  29. 29.```bash
  30. 30.curl -I -H "Origin: https://www.example.com" https://cdn.example.com/fonts/roboto.woff2 | grep -i "access-control"
  31. 31.`
  32. 32.Expected response:
  33. 33.`
  34. 34.access-control-allow-origin: *
  35. 35.`
  36. 36.For Google Fonts via CDN, self-host the fonts instead of proxying them through a different origin:
  37. 37.```css
  38. 38./* Download fonts and serve from same origin */
  39. 39.@font-face {
  40. 40.font-family: 'Roboto';
  41. 41.src: url('/fonts/roboto.woff2') format('woff2');
  42. 42.font-weight: 400;
  43. 43.font-display: swap;
  44. 44.}
  45. 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: swap in your @font-face rules 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);
  • }
  • });
  • });
  • `