Serving Fonts from CDN

By  on  

For maximum performance, we all know we must put our assets on CDN (another domain).  Along with those assets are custom web fonts.  Unfortunately custom web fonts via CDN (or any cross-domain font request) don't work in Firefox or Internet Explorer (correctly so, by spec) though they do work (incorrectly so) in Webkit-based browsers. They actually require custom CORS configurations to display properly.  Here's the code you'll need to make that happen.

The .htaccess or httpd.conf Code

The code can be placed with the .htaccess file or httpd.conf file:

# Apache config
<FilesMatch ".(eot|ttf|otf|woff)">
	Header set Access-Control-Allow-Origin "*"
</FilesMatch>

# nginx config
if ($filename ~* ^.*?\.(eot)|(ttf)|(woff)$){
	add_header Access-Control-Allow-Origin *;
}

This sets the Access-Control-Allow-Origin CORS configuration to allow pulling from all domains.  List specific domains by comma if you want to serve fonts up to only specific domains.  You'll want to serve up all font types appropriately in the case that the browser prefers one type.

To ensure the header is set properly, you can check using the curl utility:

$ curl -I https://some.cdn.otherdomain.net/media/fonts/somefont.ttf

# Result
HTTP/1.1 200 OK
Server: Apache
X-Backend-Server: developer1.webapp.scl3.mozilla.com
Content-Type: text/plain; charset=UTF-8
Access-Control-Allow-Origin: *
ETag: "4dece1737ba40"
Last-Modified: Mon, 10 Jun 2013 15:04:01 GMT
X-Cache-Info: caching
Cache-Control: max-age=604795
Expires: Wed, 19 Jun 2013 16:22:58 GMT
Date: Wed, 12 Jun 2013 16:23:03 GMT
Connection: keep-alive

If you see Access-Control-Allow-Origin: * in the response, you're golden!

This same strategy is used by Bootstrap CDN, so you know it's good!

Recent Features

  • By
    Vibration API

    Many of the new APIs provided to us by browser vendors are more targeted toward the mobile user than the desktop user.  One of those simple APIs the Vibration API.  The Vibration API allows developers to direct the device, using JavaScript, to vibrate in...

  • By
    5 Awesome New Mozilla Technologies You&#8217;ve Never Heard Of

    My trip to Mozilla Summit 2013 was incredible.  I've spent so much time focusing on my project that I had lost sight of all of the great work Mozillians were putting out.  MozSummit provided the perfect reminder of how brilliant my colleagues are and how much...

Incredible Demos

Discussion

  1. Great tip, thanks. You might clarify however that it’s not so much that Firefox and Internet Explorer “don’t work”, as it is the fact that Chrome and others simply have incorrect defaults when handing CORs. At least that’s my understanding.

  2. Another option is to set your CORS configuration in S3 if you’re planning on using Amazon.

  3. Related: Serve from AWS S3/CloudFront – https://coderwall.com/p/ub8zug

  4. Or for those of use on nginx:

    if ($filename ~* ^.*?\.(eot)|(ttf)|(woff)$){
      add_header Access-Control-Allow-Origin *;
    }
    
  5. Crispen Smith

    I think it’s important to state that this is an Apache specific approach. We are seeing more and more services such as Node being used as back-end. I see others have already spoken to *nix and S3 solutions but I think is likely a solution available within a pure Node as well.

    However, for Node developers I suspect the solution is much less universal and dependant on what’s being used. Connect’s static middleware can probably be overridden to write additional headers for those using anything that includes connect.

  6. Rolf

    Better allow only certain sites/domain instead of * if it is a custom or licensed font (in case of piracy).

  7. Andrew Killinger

    Any chance the @font-face syntax mentioned by Paul Irish would solve the problem? http://www.paulirish.com/2009/bulletproof-font-face-implementation-syntax/

    • No, @font-face would work for fonts hosted on your own server, but not remotely hosted fonts with Amazon S3. Amazon does not send an appropriate CORS header response with the fonts.

  8. Thanks for this, you saved me. I found the answer everywhere for the amazon hosting solution, but everyone had failed to mention you can just ad this to the .htaccess file if your hosting somewhere else.

  9. Hello (sorry don’t speak english verry well)

    I use on my website CDN (cdn4.kanucomputer.net,cdn5.kanucomputer.net,cdn6.kanucomputer.net) all to my root folder www.

    All work with chrome but with IE et FF no icons. My host send my this article to fix the problem, but when I add the code upside in my .htaccess I encounter a “500 internal server error” ???

    Someone could help me ?

    Thanks a lot.

    Have a good day

  10. Same problem:

    Additionally, a 500 Internal Server Error error was encountered while trying to use an ErrorDocument to handle the request.

    Total htaccess file:

    php_value magic_quotes_gpc 0
    
    Header set Access-Control-Allow-Origin "*"
    
    if ($filename ~* ^.*?\.(eot)|(ttf)|(woff)$){
    	add_header Access-Control-Allow-Origin *;
    }
    
  11. Kevin (and other), i fixed it using this code:

    Header set Access-Control-Allow-Origin "*"

    and paste it at the top of your .htaccess file

    And it should work.

    However, the nginx config fix, should be modified like this aswell. How that works, I would not know.

    Cheers

  12. justin

    So to be clear, does implementing this .htaccess solution make externally controlled fonts work in IE and FF? Specifically I am building a microsite for a client. I want to pull their fonts from their corporate site to the microsite I will be hosting on a VPS. Currently IE9 is kicking out bugs because of this, and I’m trying to find a good solution.

  13. Brodie

    Thanks for the article. One thing you might want to fix, I noticed the URL to Bootstrap CDN is broken (https://github.com/netdna/bootstrap-cdn/blob/master/.htaccess#L41)

    Cheers,

  14. I am using Nginx 1.4 and the configuration has changed a bit.

    Instead of an if, a location is preferred.

    location ~* "^.*?\.(eot)|(ttf)|(woff)$" {
      add_header Access-Control-Allow-Origin *;
    }
    
  15. Ryan

    For Apache I would recommend this to help prevent internal server error:

    Header set Access-Control-Allow-Origin "*"
  16. You say “List specific domains by comma if you want to serve fonts up to only specific domains.” but you should not use a comma separated list here.

    The spec only seems to allow for a space separated list or “*”, but it also notes “In practice the origin-list-or-null production is more constrained. Rather than allowing a space-separated list of origins, it is either a single origin or the string “null”.”
    See:
    http://www.w3.org/TR/cors/#access-control-allow-origin-response-header
    https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Origin

  17. For allowing multiple domains see tips at: http://stackoverflow.com/a/1850482/1362634

  18. hello
    The Google Fonts is not this displaying in my website.
    I used this code but did work, i got (500 internal server error)

    I checked for a solution in forums and blogs but it looks like nobody can resolve this issue.
    I’m using Hostgator and Cloudflare

    Any genius here could help,please

  19. nginx 1.6.* and versions above have deprecated variable: $filename
    you may replace it with variable: $request_uri

    if ($request_uri ~* ^.*?\.(eot)|(ttf)|(woff)$) {
        add_header Access-Control-Allow-Origin *;
    }
    
  20. Thanks for the code. You saved me!

  21. Douglas

    Assuming http://www.example.com & cdn.example.com are different web servers. Which is appropriate

    1) cdn responds with Access-Control-Allow-Origin www.example.com;
    2) www response with Access-Control-Allow-Origin cdn.example.com;

  22. On Hiawatha webserver in VirtualHost sections :

    VirtualHost {
        ...
        CustomHeader = Access-Control-Allow-Origin: *
        ...
    }
    
  23. HK

    Im running nginx and ive tried adding above to htaccess file however I get a 500 internal server error no matter what I try. Any ideas to fix this so that the icons show up on my wordpress site?

    Thanks.

  24. You should add woff2 to the regexp.

    • @clemos – thanks. Got it working now!

  25. Steve

    Thanks for the help. Saved a lot of time troubleshooting a mess. I know this was written over a year ago. Looks like webkit finally caught up and now no longer supports CDN fonts.

  26. Since we are checking via curl, might as well confirm the server is in fact sending in compressed format

    curl -I -H 'Accept-Encoding: gzip,deflate'

    if you do not see gzip or deflate in the response, then you have some more fun quick fixes to make.

  27. Thank you very much for this

  28. Paulie-D

    For Microsoft IIS7, merge the below into your web.config file located at the root of your application or site (eg: WWWROOT):

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <system.webServer>
    		<httpProtocol>
    			<customHeaders>
    				<add name="Access-Control-Allow-Origin" value="*" />
    			</customHeaders>
    		</httpProtocol>
    	</system.webServer>
    </configuration>
    

    If you have a web.config file, merely insert the missing section (eg: customHeaders). Be sure to include the ENTIRE section block.

    If you don’t have a web.config file already, simply create a new file called “web.config” in the wwwroot directory with the above contents.

  29. Not wanting to be a hater or anything but it’s worth a note of caution here. It’s generally considered bad security practice to load assets from 3rd parties as you have no control over them so if one of those 3rd parties was compromised, you might end up serving malware etc. Just worth bearing in mind is all I’m saying.

  30. Paulie-D

    @Neil, agreed. However, in my client’s case, they have a WordPress site with a single domain. The client leverages Vimeo PRO account where their videos are hosted and “included” into the WordPress site via Vimeo APIs. Moreover, the client’s site leveraged Google Fonts.

    All was well, until we added a second domain (alias). Neither the Vimeo videos would load nor did the Google Fonts work, until I added the aforementioned (my prior post) enhancement to the web.config file.

  31. subzey
    ^.*?\.(eot)|(ttf)|(woff)$

    Nginx RegExp is quite strange, it gives false positives. Are you sure you didn’t mean:

    ^.*?\.(eot|ttf|woff)$

    ?

  32. This is just perfect!

    Please add how to purge the files from amazon cloudfront (/* for all files, etc) so that it helps people!

    And don’t forget to clear the cache before testing any change!

  33. My server is nginx, I don’t think it works for me from any of them..
    It’s wordpress site with mutiple language plugin polylang. default domain is english page, sub domain is for foreign.
    Could you please pass me the ultimate code?

    I got this 500 errors.
    Internal Server Error

    The server encountered an internal error or misconfiguration and was unable to complete your request.
    Please contact the server administrator, postmaster@hermadeals.com and inform them of the time the error occurred, and anything you might have done that may have caused the error.

    More information about this error may be available in the server error log.

    • scat

      Did you ever get this resolved? I have the exact some issue:

      polylang +ngix
  34. Great post, thanks.
    You may consider to fix the broken mime type of the font. For Apache web-server use:

    AddType application/font-woff2    .woff2
    

    To fix mime type on Nginx or using PHP see:
    http://zinoui.com/blog/cross-domain-fonts

  35. Rachel

    This should work for wildcarding subdomains for apache’s httpd.conf:

    SetEnvIf Origin "http(s)?://(.*\.yourdomain\.com)$" AccessControlAllowOrigin=$0
    Header add Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
    

    …just in case you want to explicitly allow from a single domain instead of *

    • Navi

      Tanks this work for me

  36. Denisse

    Thank you so much for this ! it was really helpful.

  37. jordan

    If I’m a total newb and don’t know how to access my header file, what should I do? The site I’m working on is a simple WordPress theme. (Fonts are loading with you visit http://lsva.org, which is actually being redirected.)
    Thanks!

  38. Thank you thank you thank you! Worked perfectly!

  39. Neeraj

    Hi,

    We are using https://fast.fonts.net/jsapi/f6de1715-1efa-4a83-b5d7-8784f9c21d4a.js in our code base, but this is breaking our website on IE11 and the same works on other browsers.

    Please suggest what can be done in this case.

    Thanks,
    Neeraj

  40. Thank you David! Worked perfectly!

  41. Eugene

    This is a good article, but REALLY doesn’t help if you have no idea what you’re doing. Maybe have another article for noobs? thanks.

  42. Darren

    If you don’t know what your doing you should NOT be wildcarding Access-Control-Allow-Origin as it is a security vulnerability. See http://blog.portswigger.net/2016/10/exploiting-cors-misconfigurations-for.html

  43. jww

    If you see Access-Control-Allow-Origin: * in the response, you’re golden!

    This same strategy is used by Bootstrap CDN, so you know it’s good!

    Access-Control-Allow-Origin: * seems kind of dangerous. If effectively breaks same origin protections. Shouldn’t the advice be to limit the control to the origin needed?

    For example, I don’t need Access-Control-Allow-Origin: * for MathJax and its webfonts located at https://cdnjs.cloudflare.com/ajax/libs/mathjax . Instead, I should use something like:

        Header set Access-Control-Allow-Origin "https://cdnjs.cloudflare.com/ajax/libs/mathjax"
    
  44. jq4321

    Could it be that your apache code is slightly wrong? It works, but I think it is more permissive than it should. With

    .(eot|ttf|otf|woff)
    

    I think the dot means “any number of characters” which end with eot, etc.

    Shouldnt the dot be escaped, like so?

    \.
    

    so that it means “any sequence of characters that end with .eot” etc.

  45. Francois

    That’s odd.
    Using Polylang w/wildcard subdomains configured, none of this works. No matter what I do, the FA icons will not be displayed.

    Even adding

    Header set Access-Control-Allow-Origin "*"

    directly in the .htaccess does not solve the problem.

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!