Boosting Drupal performance with file system caching

Submitted by Darren Oh on
Breitling orange biplane ascending in blue sky trailing smoke with stunt performer standing on wing.

I use cheap shared hosting from Dreamhost for my Drupal sites. Dreamhost shared hosting does not support memory-based caches like Redis and Varnish. But that doesn’t mean I have to settle for poor performance. The File Cache and Boost modules use the file system to provide an experience as good as memory-based caching.

Drupal’s internal cache

Drupal’s built-in cache backend stores data in the database. Every time it retrieves data it has to make a database query. File Cache stores data in the file system. By eliminating database queries, it vastly improves performance. File Cache is easy to use:

  1. Install File Cache like any other Drupal module.
  2. Make sure private files are set up for Drupal.
  3. Add the following lines to settings.php:

    $settings['cache']['default'] = 'cache.backend.file_system';
    $settings['filecache']['directory']['default'] = 'private://filecache';

Proxy cache

Drupal’s internal page cache can use File Cache to retrieve cached pages. I could settle for that; but, if a cached page is available, I don’t even want Drupal to start. The Boost module saves a static copy of every page that is visited by a user who is not logged in. For example, you can find a static copy of this page at https://darren.oh.name/sites/default/files/boost/node/86.html. The Web server returns the static copy instead of starting Drupal when the page is requested. Boost handles removing the static copy when it expires.

Boost is currently available for Drupal 11 as a developer preview. To download it, run

composer require drupal/boost:@dev

After installing Boost, configure the Web server to return static copies instead of starting Drupal. Dreamhost uses Apache, so I do this with a .htaccess file. Drupal replaces the .htaccess file during code updates, so I use a Composer script to patch it:

    "scripts": {
        "post-drupal-scaffold-cmd": [
            "cd web && patch -p1 < ../patches/boost.patch"
        ]
    }

And this is what I put in patches/boost.patch:

diff --git a/.htaccess b/.htaccess
index 1ac01a117b2..aad2aecce1d 100644
--- a/.htaccess
+++ b/.htaccess
@@ -3,7 +3,7 @@
 #
 
 # Protect files and directories from prying eyes.
-<FilesMatch "\.(engine|inc|install|make|module|profile|po|sh|.*sql|theme|twig|tpl(\.php)?|xtmpl|yml)(~|\.sw[op]|\.bak|\.orig|\.save)?$|^(\.(?!well-known).*|Entries.*|Repository|Root|Tag|Template|composer\.(json|lock)|web\.config|yarn\.lock|package\.json)$|^#.*#$|\.php(~|\.sw[op]|\.bak|\.orig|\.save)$">
+<FilesMatch "\.(engine|inc|install|make|module|profile|po|sh|.*sql|theme|twig|tpl(\.php)?|xtmpl|yml)(~|\.sw[op]|\.bak|\.orig|\.save)?$|^(\.(?!well-known|html$).*|Entries.*|Repository|Root|Tag|Template|composer\.(json|lock)|web\.config|yarn\.lock|package\.json)$|^#.*#$|\.php(~|\.sw[op]|\.bak|\.orig|\.save)$">
   <IfModule mod_authz_core.c>
     Require all denied
   </IfModule>
@@ -12,6 +12,24 @@
   </IfModule>
 </FilesMatch>
 
+# Boost caches the homepage as the dotfile ".html".
+# Apache's global <FilesMatch "^\.ht"> deny matches that name, so this block
+# must explicitly allow ".html" and force text/html to avoid 403/downloads.
+# Related safeguards in this file:
+# - The top deny FilesMatch excludes "html" in its dotfile negative lookahead.
+# - The mod_rewrite hidden-path deny rule skips /sites/default/files/boost/**/.html
+#   so Boost cache hits are not blocked by the "/\." forbid rule.
+<Files ".html">
+  <IfModule mod_authz_core.c>
+    Require all granted
+  </IfModule>
+  <IfModule !mod_authz_core.c>
+    Order allow,deny
+    Allow from all
+  </IfModule>
+  ForceType text/html
+</Files>
+
 # Don't show directory listings for URLs which map to a directory.
 Options -Indexes
 
@@ -87,6 +105,7 @@ AddType image/webp .webp
   # If you do not have mod_rewrite installed, you should remove these
   # directories from your webroot or otherwise protect them from being
   # downloaded.
+  RewriteCond %{REQUEST_URI} !^/sites/default/files/boost/(?:|.*/)\.html$
   RewriteRule "/\.|^\.(?!well-known/)" - [F]
 
   # If your site can be accessed both with and without the 'www.' prefix, you
@@ -122,6 +141,23 @@ AddType image/webp .webp
   RewriteCond %{REQUEST_URI} !core
   RewriteRule ^ %1/core/%2 [L,QSA,R=301]
 
+  # ---- BEGIN BOOST ----
+
+  RewriteCond %{REQUEST_METHOD} ^(GET|HEAD)$ [NC]
+  RewriteCond %{QUERY_STRING} ^$
+  RewriteCond %{HTTP_COOKIE} !(SESS|SSESS) [NC]
+  RewriteCond %{DOCUMENT_ROOT}/sites/default/files/boost%{REQUEST_URI}.html -f
+  RewriteRule ^ sites/default/files/boost%{REQUEST_URI}.html [L,E=BOOST_CACHE:1]
+  #  # IMPORTANT: allow Basic Auth testing (do NOT bypass on Authorization)
+  #  # So: do NOT add any Authorization-based skip.
+
+  <IfModule mod_headers.c>
+    Header set X-Boost-Cache "full" env=BOOST_CACHE
+    Header set X-Boost-Cache "full" env=REDIRECT_BOOST_CACHE
+  </IfModule>
+
+  # ---- END BOOST ----
+
   # Rewrite install.php during installation to see if mod_rewrite is working
   RewriteRule ^core/install\.php core/install.php?rewrite=ok [QSA,L]
 

Photo by Abhinav on Unsplash

Tags