Caching Ghost with Apache for Maximum Performance, 100x faster

Ghost can be a bit CPU hungry, especially for a lightweight (single core) VPS, but all of that can me negated with a little bit of caching. Luckily Apache's mod_disk_cache makes easy work of this.

Configuring the cache:

First we need to enable mod_cache, mod_cache_disk and mod_expires:

sudo a2enmod cache
sudo a2enmod cache_disk
sudo a2enmod expires

Then edit your virtual host file, usually /etc/apache2/sites-enabled/default.conf (may differ based on setup)

<VirtualHost *:80>
     # Domain name and Alias
     ServerName example.com
     ServerAlias www.example.com

     # Configure Reverse proxy for Ghost
     ProxyPreserveHost on
     ProxyPass / http://localhost:1234/
     ProxyPassReverse / http://localhost:1234/

     CacheQuickHandler off
     CacheLock on
     CacheLockPath /tmp/mod_cache-lock
     CacheLockMaxAge 5
     CacheIgnoreHeaders Set-Cookie

     <Location />
        # Enable disk cache, set defaults
        CacheEnable disk
        CacheHeader on
        CacheDefaultExpire 600
        CacheMaxExpire 86400
        FileETag All

        # Set cache-control headers for all request
        # which do not have them by default
        # must enable: mod_expires
        ExpiresActive on
        ExpiresDefault "access plus 15 minutes"
    </Location>

    # While this is not needed since Ghost automatically
    # passes back 'Cache-Control: no-cache, private'
    # It makes me feel better to explicitly state it again.
    <Location /ghost>
        # Don't cache the ghost admin interface
        SetEnv no-cache
    </Location>

</VirtualHost>

Finally we need to restart apache and then we are done!

sudo service apache2 restart

Now it's time to check whether your cache is working by inspecting the headers.

:~$ curl -i -X GET http://example.com | less
HTTP/1.1 200 OK
Date: Sun, 21 Apr 2017 05:15:13 GMT
Server: Apache
Cache-Control: public, max-age=0, max-age=900
Expires: Sun, 21 Apr 2017 05:30:12 GMT
Age: 832
X-Cache: HIT from example.com
...

As long as you see an X-Cache and a Cache-Control header it is all working. Now lets see what kind of performance improvement we have achieved.

Performance Testing:

To quantify the improvement, I broke out ApacheBench and did a couple of quick tests from a neighboring machine.
First test without caching enabled, yielding approximately 25 Requests per second with a median response time of ~4 seconds!:

Concurrency Level:      100
Time taken for tests:   40.943 seconds
Complete requests:      1000
Requests per second:    24.42 [#/sec] (mean)
Time per request:       4094.286 [ms] (mean)
Time per request:       40.943 [ms] (mean, across all concurrent requests)
Transfer rate:          282.64 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    1   1.7      1      14
Processing:  1052 3936 670.4   3933    5252
Waiting:     1052 3936 670.4   3933    5252
Total:       1056 3938 669.7   3934    5252

Percentage of the requests served within a certain time (ms)
  50%   3934
  66%   4119
  75%   4261
  80%   4406
  90%   4554
  95%   5067
  98%   5173
  99%   5211
 100%   5252 (longest request)

After enabling the caching, we get ~2700 Requests per second with a median response time of 31 milliseconds!. That is 100x more requests served per second!

Concurrency Level:      100
Time taken for tests:   18.487 seconds
Complete requests:      50000
Failed requests:        0
Total transferred:      597400000 bytes
HTML transferred:       578850000 bytes
Requests per second:    2704.57 [#/sec] (mean)
Time per request:       36.974 [ms] (mean)
Time per request:       0.370 [ms] (mean, across all concurrent requests)
Transfer rate:          31556.82 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    8  52.1      4    1019
Processing:     1   29  19.1     26     693
Waiting:        1   28  16.9     26     659
Total:          2   37  55.1     31    1158

Percentage of the requests served within a certain time (ms)
  50%     31
  66%     34
  75%     37
  80%     40
  90%     46
  95%     51
  98%     67
  99%     91
 100%   1158 (longest request)