So, here's my conundrum! I have been trying to get Cloudfront to play nicely with my nginx server for the past three days...have read countless StackOverflow posts and blog articles...scoured the interwebs and I am still stuck with issues surrounding cross-domain access policies when it comes to Cloudfront serving fonts. I am going to post my complete setup in hopes that someone with more expertise may help me figure out what is going on. In the future, I hope this post will serve many others facing similar issues. Here goes...
I have a nginx webserver with the following server block configuration. (...truncated for brevity)
server {
server_name example.com www.example.com;
root /var/www/example.com/html;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
location /assets {
autoindex on;
}
# Media
location ~* \.(jpe?g|gif|png|ico|cur|gz|svgz?|mp4|ogg|ogv|webm|htc|webp)$ {
expires 1M;
access_log off;
add_header Cache-Control public;
}
# Fonts
location ~* \.(eot|ttf|woff|woff2|svg)$ {
expires 365d;
access_log off;
add_header Cache-Control public;
add_header Access-Control-Allow-Origin example.com;
// Have also tried setting the "Access-Control-Allow-Origin" header to "*", but I'd prefer not to do this for security reasons.
}
}
FYI: All of my website files I would like to serve and offload to CloudFront are in the /assets
directory of my virtual host. (ie. http://example.com/assets/..)
I have created a new CloudFront distribution with the following settings:
Note: I am not using S3 to host my website files and assets.
A portion of my Zone file...
example.com. 1800 IN A 12.34.567.890 //faked IP here for privacy reasons
www.example.com. 1800 IN CNAME example.com.
static.example.com. 1800 IN CNAME kg72kgf83nhfy3.cloudfront.net. //faked CloudFront dist. domain name here for privacy reasons
So, CloudFront does its thing processing and deploying my distribution, and I'm assuming pulling the assets from my '/assets/..' web directory. All src
, href
, and CSS url()
references point to http://static.example.com in my current HTML and CSS documents, including all font-face
references. After the distribution is deployed, I hit my site http://example.com in a browser.
It appears that all static site assets are served correctly from CloudFront w/ appropriate caching headers as defined in my nginx config...EXCEPT...webfonts. I am getting missing fonts on the page and cross-domain access policy error messages in my browser console.
An image, for reference (when pinging my server):
curl -I "http://example.com/assets/images/image1.png"
HTTP/1.1 200 OK
Server: nginx/1.6.3
Date: Sun, 15 May 2016 02:09:25 GMT
Content-Type: image/png
Content-Length: 194665
Last-Modified: Sun, 15 May 2016 01:52:35 GMT
Connection: keep-alive
ETag: "5737d663-2f869"
Expires: Tue, 14 Jun 2016 02:09:25 GMT
Cache-Control: max-age=2592000
Cache-Control: public
Accept-Ranges: bytes
A font (when pinging my server):
curl -I "http://example.com/assets/fonts/webfont.woff"
HTTP/1.1 200 OK
Server: nginx/1.6.3
Date: Sun, 15 May 2016 02:10:29 GMT
Content-Type: application/font-woff
Content-Length: 8752
Last-Modified: Sun, 15 May 2016 01:51:55 GMT
Connection: keep-alive
Vary: Accept-Encoding
ETag: "5737d63b-2230"
Expires: Mon, 15 May 2017 02:10:29 GMT
Cache-Control: max-age=31536000
Cache-Control: public
Access-Control-Allow-Origin: example.com
Accept-Ranges: bytes
Same image (when requesting from CloudFront):
curl -I "http://static.example.com/assets/images/image1.png"
HTTP/1.1 200 OK
Content-Type: image/png
Content-Length: 194665
Connection: keep-alive
Server: nginx/1.6.3
Date: Sun, 15 May 2016 02:39:16 GMT
Last-Modified: Sun, 15 May 2016 01:52:35 GMT
ETag: "5737d663-2f869"
Expires: Tue, 14 Jun 2016 02:39:16 GMT
Cache-Control: max-age=2592000
Cache-Control: public
Accept-Ranges: bytes
X-Cache: Miss from cloudfront
Via: 1.1 lots_of_random_characters_i_dont_know_if_should_share_here.cloudfront.net (CloudFront)
X-Amz-Cf-Id: lSM1plINYENbYycBn424LJ2wdtDhS3CpqAFiDSoxQDEctP_WM09bUQ==
Same font (when requesting from CloudFront):
curl -I "http://static.example.com/assets/fonts/webfont.woff"
HTTP/1.1 200 OK
Content-Type: application/font-woff
Content-Length: 8752
Connection: keep-alive
Server: nginx/1.6.3
Date: Sun, 15 May 2016 02:41:00 GMT
Last-Modified: Sun, 15 May 2016 01:51:55 GMT
ETag: "5737d63b-2230"
Expires: Mon, 15 May 2017 02:41:00 GMT
Cache-Control: max-age=31536000
Cache-Control: public
Access-Control-Allow-Origin: example.com
Accept-Ranges: bytes
Vary: Accept-Encoding
X-Cache: Miss from cloudfront
Via: 1.1 lots_of_random_characters_i_dont_know_if_should_share_here.cloudfront.net (CloudFront)
X-Amz-Cf-Id: vNfiyurS8pjosofnpLNSrnZuaGFg0V4xIs4ySCm05NKDMZ_PozhuOg==
Loading my website at http://example.com, everything seems to work (ie. images) EXCEPT the webfonts. Checking the browser console outputs the following message for every font:
Font from origin 'http://static.example.com' has been blocked from loading by Cross-Origin Resource Sharing policy: The 'Access-Control-Allow-Origin' header contains the invalid value 'example.com'. Origin 'http://example.com' is therefore not allowed access.
So, anyone have any thoughts?? I would greatly appreciate the help/input. I'm a young web developer just trying to learn.
Thank you! :)
–Kyle
Footnotes:
//static.example.com/assets/images/image.png?622c6911
) to invalidate the CloudFront cache. This way I do not have to always re-upload new assets with changing names...I can simply control invalidation from the HTML and append a new query string to assets when I want CloudFront to request the latest version of that file from my webserver Origin.The explanation is here:
The 'Access-Control-Allow-Origin' header contains the invalid value 'example.com'. Origin 'http://example.com' is therefore not allowed access.
The origin is http://example.com
... not example.com
. Your response header has the wrong value. An origin is, by definition, scheme + hostname + port (with the implicit ports 80 and 443 omitted for http and https on standard ports).
When the request isn't subject to cross-origin rules, such as is the case with images, this misconfiguration on your web server is being ignored by the browser. For fonts, you're hitting this wall because the value is malformed.
add_header Access-Control-Allow-Origin example.com;
This is the root of your problem. You need to respond with the same origin sent by the browser in the Origin:
header, assuming that origin is indeed valid and should be allowed. I'm not an nginx specialist, by according to this answer, you can validate the incoming origin with a regex, and set the response accordingly:
if ($http_origin ~* "^https?://.*example\.com$" ) {
add_header Access-Control-Allow-Origin $http_origin;
}
Not being familiar with the quirks of nginx regexes, mine is a little permissive, but you get the idea.
Before this will work, we need to fix the CloudFront cache behavior.
Forward Headers: Whitelist
Access-Control-Allow-Origin
That isn't a request header... that's a response header, so whitelisting it doesn't actually do anything.
You will, instead, need to whitelist the Origin
header so that it will be forwarded to the web server by CloudFront, so that the server can respond with that same value in Access-Control-Allow-Origin:
, as illustrated above.
Access-Control-Request-Headers
and Access-Control-Request-Method
should probably also be whitelisted, though for GET
requests, I don't know that they will matter.
You also should probably modify the allowed methods to include OPTIONS
in addition to GET
and HEAD
.
Bonus material:
My plan is to append hashed query strings to the end of every file (ie. //static.example.com/assets/images/image.png?622c6911) to invalidate the CloudFront cache.
In that case, you need Forward Query Strings
set to Yes
.
CloudFront caches objects based on what it actually sends to the server. Appending a query string will only invalidate the browser cache, not the CloudFront cache, if query strings are not forwarded to the origin. Yes, you need this enabled even if your server doesn't want or need the query strings, if the query strings are intended to be used for cache-busting CloudFront. That's why not forwarding them "improves caching." It will be "improved" by CloudFront ignoring them entirely when determining whether it already has a cached version of an object.
Also, note that the Via
header doesn't contain sensitive information, neither does X-Amz-Cf-Id
.