post image is here by allegralchaple0

Apache + Nginx = <3

In tonight's episode, we're going to go over running our ghetto CDN setup using Apache for our dynamic content and Nginx for everything else. This is the setup I use in my live environments partly because all my VPSes have two IPs, partly because Nginx goes like the clappers when it comes to static files.

Apache-wise, the only difference between my dev and live environments is the former has Drupal living in a VirtualHost with a bunch of ServerAliases while the latter it just lives in the default DocumentRoot so there is literally nothing to do there except make sure it's sticking to one of the IPs.

I'm not going to go over the setup and basic configuration of Apache or Nginx because I'd imagine of you're reading this you probably have a fairly good idea of how to install Nginx and get it running on the IP you want (or using Apache's proxying, if that's your thing) so I'm not going to go in to it here. Instead, lets start easy with the ghettocdn.conf I dropped in /etc/nginx/conf.d:

server_tokens off;
tcp_nopush on;
tcp_nodelay on;
types_hash_max_size 2048;

gzip on;
gzip_disable "msie6";
gzip_http_version 1.0;
gzip_static on;
gzip_proxied any;

The top bit tells nginx to be very quiet and sets some TCP options, the bottom is pretty important: This tells Nginx when it can and can't use gzipped content transfers. This is also how you serve Content-Encoding: gzip through Cloudfront using Nginx so I think people really need to stop saying that can't be done.

Both of the configs for the hosts themselves, static.conf and content.conf, start off the same way inside their server { } block:

listen i.p.add.ress:80;
server_name ~^(origin\.)?static\.(?.+)$;

The server_name regular expression uses one of Nginx's most awesome features: The ability to capture regular expression matches in to a config file variable. Naturally, if we were using this to define our content domain, we'd replace static\. with content \. in the regular expression.

Next, and we'll be working with static.conf first, we configure our default location:

location / {
    root /var/www/html;
    expires 5w;
    add_header 'Cache-Control' 'public';
    add_header 'Access-Control-Allow-Origin' $scheme://$domain;
    add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS';

As with Apache, we use the same webroot as Drupal and hard-waire Nginx to spit out 5 week expiries and add some CORS headers. Again, we don't just set it to * because that's a pretty scrubby thing to do.

    location ~ ^/assets/.*\.(js|css)$ {
        try_files $uri @advaggredir;

This little shortcut ensures that missing JavaScript and CSS aggregation files don't 404 when they dont exist, but get rewritten back to the apex host for generation.

    location ~ \.(css|js|jpe?g|png|gif|ico|svgz?|ttf|otf|woff2?|eot|html?|txt|swf|gz|json)$ {
        try_files $uri =404;

    return 404;

Since we're only interested in serving static files, we check the extension of the request against our whitelist regular expression, serve or 404 it as appropriate if it matches, straight up 404 if it doesn't.

Now that's out of the way, we define a location for the redirect we put in place above:

location @advaggredir {
    return 307 $scheme://$domain$request_uri;

Quick and simple: Redirect the request back to the apex domain using result code 307 (Temporary Redirect), which most CDNs and browsers are smart enough not to cache.

As with my other posts, config examples made this thing pretty long, so if you get where I'm going with this content is basically the same thing but with a dynamic root directory. If that (understandably) sounds clear as mud, check out the content post, otherwise let's jump ahead to setting up a CDN.