# Fortifying Craft CMS for High Traffic


- Presented on: 20-Sept-2019
- Event: Dot All 2019
- Locations: Canada, Montréal
- Slides: <iframe class="speakerdeck-iframe" style="border: 0px; background: rgba(0, 0, 0, 0.1) padding-box; margin: 0px; padding: 0px; border-radius: 6px; box-shadow: rgba(0, 0, 0, 0.2) 0px 5px 40px; width: 100%; height: auto; aspect-ratio: 560 / 315;" frameborder="0" src="https://speakerdeck.com/player/450ed732ae5e498a8679474e7d3cbfdc" title="Fortifying Craft for High Traffic" allowfullscreen="true" data-ratio="1.7777777777777777"></iframe>
- Video: <iframe src="https://player.vimeo.com/video/365645619" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe>
- Tags: Performance, Web Development, Craft
- URL: https://miranj.in/blog/2020/fortifying-craft-for-high-traffic


In the vast, multi-layered subject area of web performance, server response time is an important metric. From a CMS standpoint however, it is one of the most significant. Best practise recommends a 200ms or lower [time-to-first-byte][ttfb] (TTFB). That is the time in which a CMS has to figure out what to do with the request, parse templates, query the database, render the HTML, capture it all and send it back as the response. 

[Craft CMS][1] -- our preferred CMS for content-heavy websites here at Miranj -- is quite fast out of the box, but as our pages and content models grow in complexity so does the server response time. Add traffic to that mix and it can quickly lead to poor TTFBs and slow overall experience. 

In this talk we go through a multi-tiered caching strategy using Craft and [Nginx][] that enables a single VPS to consistently deliver sub-200ms response times, even while handling loads of 10 to 100 concurrent requests per second. It covers our learnings from optimising a low-powered server to handle millions of visitors each month on the [Guiding Tech website][gt] project. We achieve this by caching the website at two places — at the web server level using Nginx's FastCGI micro-caching, and at the CMS level using flag-based template caches in Craft CMS. We also factor in real-world edge cases such as bypassing the cache, delivering variations to different visitors, etc, that are necessary to account for in a robust, production-ready system.

<div class="stretch clearfix" style="clear: both"><div class="embed-16-9">
<iframe src="https://player.vimeo.com/video/365645619?h=2e94abdbe9&title=0&byline=0&portrait=0" loading="lazy" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe>
</div></div>

<p class="caption"><a href="https://vimeo.com/365645619">Fortifying Craft for High Traffic with Prateek Rungta</a> from <a href="https://vimeo.com/craftcms">Craft CMS</a> on <a href="https://vimeo.com">Vimeo</a>.</p>

We also put out a [sample Nginx config for the micro-caching strategy][3] discussed in the slides.

<div class="stretch clearfix">
<script src="https://gist.github.com/rungta/e35bd486e9439da46d9796cd12faae1f.js"></script>
<noscript>
<pre><code>#
# WARNING: This is not a complete NGINX configuration file
# It tries to highlight just the directives used to enable FastCGI microcaching
# as explained in my “Micro-caching in Nginx for High Performance” talk
# https://speakerdeck.com/rungta/micro-caching-in-nginx-for-high-performance
# Video: https://youtu.be/termdb4vepA?t=2199
#

http {
    #...
    
    # Init cache
    fastcgi_cache_path /var/run/fastcgicache_folder levels=1:2 keys_zone=zone_name:100m inactive=1d;
    
    # Configure
    fastcgi_cache_lock on;
    fastcgi_cache_use_stale updating error timeout invalid_header http_500;
    fastcgi_cache_background_update on;
    fastcgi_cache_methods GET HEAD;
    fastcgi_cache_key "$scheme$request_method$host$request_uri";
    
    # Detect mobile browsers
    # from https://gist.github.com/dangayle/2919206 
    # map $http_user_agent $is_mobile {
    #     default 0;
    #     "~*android.+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino" 1;
    #     "~*^(1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(di|rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-)" 1;
    # }
    # fastcgi_cache_key "$is_mobile$scheme$request_method$host$request_uri";
    
    # Cache status (debug)
    add_header X-Cache $upstream_cache_status;
    fastcgi_pass_header X-Accel-Expires;
    
    server {
        #...
        
        # Ensure admin / user-specific requests skip the cache
        # using a custom location handler
        location ^~ /admin {
          try_files $uri $uri/ @phpfpm_nocache;
        }
        
        location ~ \.php$ {
            # Enable cache
            fastcgi_cache zone_name;
            fastcgi_ignore_headers Cache-Control Expires;
            fastcgi_cache_valid 200 301 302 404 1s;
            fastcgi_cache_bypass $arg_token $cookie_session_cookie_name;
            fastcgi_no_cache     $arg_token $cookie_session_cookie_name;
            
            # Regular PHP handling
            # ...
        }
        
        location @phpfpm_nocache {
            # No FastCGI Cache
            fastcgi_cache_bypass 1;
            fastcgi_no_cache 1;
          
            # Regular PHP handling
            # ...
        }
    }
}
</code></pre>
</noscript>
</div>

---

This talk was prepared for and delivered at [Dot All 2019 in Montréal, Canada][2]. This was my first time speaking at a conference outside India, but any nervousness I carried on stage was quickly dispelled by the warm engagement and [wonderful conversations][4] with the Craft community.

<p class="panel-2 stretch clearfix" markdown="1">
![Prateek speaking at Dot All 2019](https://miranj.in/media/talk/fortifying-craft-for-high-traffic/DotAll2019-Sep20-089.jpg)
![Souvik and Prateek share a laugh with Ben Parizek of Barrel Strength Design](https://miranj.in/media/talk/fortifying-craft-for-high-traffic/DotAll2019-Sep19-052.jpg)
</p>
<p class="caption" markdown="1">
  Photographs courtesy [Pixel & Tonic][6]
</p>

I’m extremely grateful to the folks at [Pixel & Tonic][6] for extending me an opportunity to present to an international audience, and for placing their trust in us a second time after [Souvik's talk the previous year at Dot All 2018, Berlin][5].

I [would love to hear][mail] about your mileage from adopting any of the caching strategies mentioned in the talk, or any alternate approaches you have implemented to optimise Craft CMS for heavy loads and high traffic.

[1]:https://craftcms.com
[2]:https://craftcms.com/events/dot-all-2019/
[3]:https://gist.github.com/rungta/e35bd486e9439da46d9796cd12faae1f
[4]:https://twitter.com/ben_pylo/status/1175612708991000577
[5]:https://miranj.in/blog/2019/modular-architecture-for-building-content-websites
[6]:https://pixelandtonic.com/ "Pixel & Tonic – the team behind Craft CMS"
[nginx]:http://nginx.org/
[ttfb]:https://web.dev/articles/ttfb
[gt]:https://miranj.in/work/guiding-tech-2020
[mail]:mailto:rungta@miranj.in

*[TTFB]:
*[TTFBs]: Time to First Byte
*[CMS]: Content Management System