Caching a Craft CMS Site With Nginx

December 13 2019

I recently wrapped up building my first client website with Craft CMS, and am very nearly ready to launch my new portfolio with the service as well. Craft is amazing, and completely blows away the competition for any sort of visually intensive website with complicated layouts.

That being said, one of the things I really like about the WordPress setup I use is my caching setup. I wanted something similar for Craft — something that doesn’t require a ton of work to set up or maintain.

This is largely based on Tim de Pater’s existing gist for WordPress caching, but customized to deal with Craft’s requirements. I’m also borrowing liberally from NYStudio107’s excellent article.

A couple things that are worth noting: I’m using Forge by Laravel, along with Linode, for my basic hosting needs. So some of this code will be related to that setup in particular. I’ve also removed some of this code for the sake of streamlining what you see here.

You’ll also need a Craft plugin to handle busting the cache whenever you update your site. I’m using FastCGI Cache Bust (once again from NYStudio107) to get it done, but if you wanted to write your own script or use somebody else’s plugin, I’m sure you could. (And if you’re a masochist who wants to SSH in every time you make a chance and bust the cache yourself, that’s between you and God.)

The only other thing you’ll need to do is bust the cache whenever you pull from git and run composer update on the server. I use a simple bash script for this, which you’ll find all over the web.

Everything you need to tweak in your Nginx config file should be visible below in ALL CAPS. This has been working seamlessly for me so far, and has been an even stronger solution than what I was doing for my WordPress sites.

fastcgi_cache_path /etc/nginx/cache/yourfolder/ levels=1:2 keys_zone=ZONENAME:100m inactive=60m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name YOURURL.com;
    root /home/forge/YOURURL.com/web;

    # Your SSL settings will go right here

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";

    index index.html index.htm index.php;

    charset utf-8;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
    
    # Directives to send expires headers and turn off 404 error logging.
    location ~* ^.+\.(ogg|ogv|svg|svgz|eot|otf|woff|woff2|mp4|ttf|css|rss|atom|js|jpg|jpeg|gif|png|ico|zip|tgz|gz|rar|bz2|doc|xls|exe|ppt|tar|mid|midi|wav|bmp|rtf)$ {
           access_log off; log_not_found off; expires max;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    access_log off;
    error_log  /var/log/nginx/YOURURL.com-error.log error;

    error_page 404 /index.php?$query_string;

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php/php7.3-fpm.sock;
        fastcgi_index index.php;
        fastcgi_cache ZONENAME;
        fastcgi_cache_valid 200 1w;
        fastcgi_cache_bypass $no_cache;
        fastcgi_no_cache $no_cache;
        fastcgi_cache_use_stale updating error timeout invalid_header http_500;
        fastcgi_cache_lock on;
        fastcgi_ignore_headers Cache-Control Expires Set-Cookie;
        include fastcgi_params;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
    
    # Troll WordPress bots/users
    location ~ ^/(wp-login|wp-admin|wp-config|wp-content|wp-includes|(.*)\.exe) {
        return 301 https://wordpress.com/wp-login.php;
    }
    
    #Cache everything by default
    set $no_cache 0;
    
    #Don't cache POST requests
    if ($request_method = POST)
    {
        set $no_cache 1;
    }
    
    #Don't cache if the URL contains a query string
    if ($query_string != "")
    {
        set $no_cache 1;
    }
    
    #Don't cache the following URLs
    if ($request_uri ~* "/(admin/|cpresources/)")
    {
        set $no_cache 1;
    }
    
    # Don't cache uris containing the following segments
    if ($request_uri ~* "/feed/|sitemap(_index)?.xml") {
        set $no_cache 1;
    }   
    
}

# FORGE CONFIG (DO NOT REMOVE!)
include forge-conf/YOURURL.com/after/*;

Side note: I’ve been building websites professionally for close to a decade now, and this is the first time I’ve ever included a snippet of code on my blog. I’ll have to do this more often.

Development