Enhancing Drupal Performance with Varnish on Platform.sh

In the quest for blazing-fast website performance, caching is king. At dseven, we’re using different HTTP reverse proxy technologies like Cloudflare, Fastly or Varnish for that.

In this Blog we leverage the power of Varnish Cache in conjunction with Drupal on Platform.sh to deliver unparalleled speed and efficiency for our web projects. This guide will walk you through the steps to integrate Varnish with Drupal on Platform.sh, ensuring your sites are not just fast, but also scalable.

Enable Varnish as a Service on Platform.sh

First things first, to utilize Varnish on Platform.sh, you need to enable it as a service. Follow the detailed steps outlined in the Platform.sh documentation to get Varnish up and running: Enable Varnish on Platform.sh.

You do that by enabling Varnish in you services.yml file. After a redeply you see Varnish in your Application and Services setup:

varnish:
    type: varnish:7.2
    relationships:
        application: 'app:http'
    configuration:
        vcl: !include
            type: string
            path: config.vcl

Most important here is the config.vcl file. In Varnish it the the most important configuration file that defines caching rules and policies for how Varnish caches and serves content, enabling efficient content delivery and performance optimization. This is and basic example:

backend default {
.host = "127.0.0.1";
.port = "8080";
.connect_timeout = 600s;
.first_byte_timeout = 600s;
.between_bytes_timeout = 600s;
.max_connections = 800;
}

sub vcl_recv {


# Now we use the different backends based on the uri of the site. Again, this is
# not needed if you're running a single site on a server
#if (req.http.host ~ "sitea.com$") {
#  set req.backend = sitea;
#} else if (req.http.host ~ "siteb.com$") {
#set req.backend = siteb;
#} else {
# Use the default backend for all other requests
set req.backend = default;
#}

  # Add a unique header containing the client address
  remove req.http.X-Forwarded-For;
  set    req.http.X-Forwarded-For = client.ip;

  # Get rid of progress.js query params
  if (req.url ~ "^/misc/progress\.js\?[0-9]+$") {
    set req.url = "/misc/progress.js";
  }

  # Pipe these paths directly to Apache for streaming.
  if (req.url ~ "^/admin/content/backup_migrate/export") {
    return (pipe);
 }

# If global redirect is on
# if (req.url ~ "node\?page=[0-9]+$") {
#  set req.url = regsub(req.url, "node(\?page=[0-9]+$)", "\1");
#  return (lookup);
# }

# Do not cache these paths.
  if (req.url ~ "^/status\.php$" ||
      req.url ~ "^/update\.php" ||
      req.url ~ "^/install\.php" ||
      req.url ~ "^/admin" ||
      req.url ~ "^/admin/.*$" ||
      req.url ~ "^/user" ||
      req.url ~ "^/user/.*$" ||
      req.url ~ "^/users/.*$" ||
      req.url ~ "^/info/.*$" ||
      req.url ~ "^/flag/.*$" ||
      req.url ~ "^.*/ajax/.*$" ||
      req.url ~ "^.*/ahah/.*$") {
      return (pass);
  }

  # Always cache the following file types for all users.
  if (req.url ~ "(?i)\.(png|gif|jpeg|jpg|ico|swf|css|js|html|htm)(\?[a-z0-9]+)?$") {
    unset req.http.Cookie;
  }

  # Remove all cookies that Drupal doesn't need to know about. ANY remaining
  # cookie will cause the request to pass-through to Apache. For the most part
  # we always set the NO_CACHE cookie after any POST request, disabling the
  # Varnish cache temporarily. The session cookie allows all authenticated users
  # to pass through as long as they're logged in.
  ## See: http://drupal.stackexchange.com/questions/53467/varnish-problem-user-log...  # 1. Append a semi-colon to the front of the cookie string.
  # 2. Remove all spaces that appear after semi-colons.
  # 3. Match the cookies we want to keep, adding the space we removed
  # previously, back. (\1) is first matching group in the regsuball.
  # 4. Remove all other cookies, identifying them by the fact that they have
  # no space after the preceding semi-colon.
  # 5. Remove all spaces and semi-colons from the beginning and end of the
  # cookie string.
  if (req.http.Cookie) {
    set req.http.Cookie = ";" + req.http.Cookie;
    set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";");
    set req.http.Cookie = regsuball(req.http.Cookie, ";(S{1,2}ESS[a-z0-9]+|NO_CACHE|CHOCOLATECHIP|OATMEAL)=", "; \1=");
    set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", "");
    set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", "");

    if (req.http.Cookie == "") {
      # If there are no remaining cookies, remove the cookie header. If there
      # aren't any cookie headers, Varnish's default behavior will be to cache
      # the page.
      unset req.http.Cookie;
    }
    else {
      # If there is any cookies left (a session or NO_CACHE cookie), do not
      # cache the page. Pass it on to Apache directly.
      return (pass);
    }
  }

    # Remove the "has_js" cookie
    set req.http.Cookie = regsuball(req.http.Cookie, "has_js=[^;]+(; )?", "");

    # Remove the "Drupal.toolbar.collapsed" cookie
    set req.http.Cookie = regsuball(req.http.Cookie, "Drupal.toolbar.collapsed=[^;]+(; )?", "");

    # Remove any Google Analytics based cookies
    set req.http.Cookie = regsuball(req.http.Cookie, "__utm.=[^;]+(; )?", "");

    # Remove the Quant Capital cookies (added by some plugin, all __qca)
    set req.http.Cookie = regsuball(req.http.Cookie, "__qc.=[^;]+(; )?", "");

    # Are there cookies left with only spaces or that are empty?
    if (req.http.cookie ~ "^ *$") {
        unset req.http.cookie;
    }

    # Cache static content unique to the theme (so no user uploaded images)
    if (req.url ~ "^/themes/" && req.url ~ ".(css|js|png|gif|jp(e)?g)") {
        unset req.http.cookie;
    }
}

sub vcl_hit {
	if (req.request == "PURGE") {
		purge;
		error 200 "Purged.";
	}
}

sub vcl_miss {
        if (req.request == "PURGE") {
                purge;
                error 200 "Purged.";
        }
}

Disable Other Caches

Now that you have basic setup up and running, you need to ensure Varnish operates effectively. Therefore it’s crucial to disable other caching mechanisms:

NGINX Cache: Modify your routes.yml file on Platform.sh to disable NGINX caching.

# The routes of the project.
#
# Each route describes how an incoming URL is going
# to be processed by Platform.sh.

"https://www.{default}/":
    type: upstream
    upstream: "varnish:http"
    cache:
      enabled: false

"https://{default}/":
    type: redirect
    to: "https://www.{default}/"

"http://{all}/":
  type: redirect
  to: "https://www.{all}/"

  • Drupal Cache: Navigate to admin/config/development/performance in your Drupal admin and ensure all internal Drupal caching is turned off.

Step 3: Configure Page Header Lifetime

For Varnish to store your pages effectively, set the maximum age for cacheable pages to 1 year:

  • Go to admin/config/development/performance and adjust the page cache maximum age to the maximum allowed value.
  • Reflect this setting in your system.performance.yml configuration file to ensure consistency.

Step 4: Customize Your VCL File

Varnish Configuration Language (VCL) files dictate how Varnish handles your requests. Include a VCL file in your project with the following key configurations:

  • Outgoing IPs (Lines 3-6): Specify the outgoing IP addresses for Platform.sh region fr-4, allowing purging requests from these IPs only.
  • Drupal Module Integration (Lines 61-72): Integrate with our “Custom Varnish Purge” Drupal module to enable cache clearing from the Drupal backend.
  • Cookie Handling (Line 105): List any additional cookies your project requires beyond Drupal’s default set.
  • Grace Mode (Line 208): Configure Varnish’s grace mode following the guidelines at Varnish Grace Mode.

Step 5: Implement Purging

To keep your cache fresh and relevant, set up purging:

  1. Install Purge Modules: Utilize the Drupal Purge and Varnish Purge modules, available at Drupal Purge Module and Varnish Purge Module.
  2. Configure Purger Settings: In your settings.php, configure the purger with the correct environment-specific settings, ensuring the Purger-ID is unique to your project.

Attention to Crons

Adjust your cron settings to handle the purge queue efficiently, especially for projects with high purge request volumes. Ensure your cron runs frequently enough to keep the purge queue manageable.

Step 6: Install the Varnish Module

For enhanced editorial control over the Varnish cache, install our custom Varnish Purge Module via Composer:

{ "type": "vcs", "url": "https://xxxx/scm/av/dseven-varnish-purge.git" }

Then, run composer require xxx/dseven-varnish-purge to add it to your project.

Conclusion

Integrating Varnish with Drupal on Platform.sh can significantly boost your website’s performance, but it requires careful configuration and management. By following these steps, you can ensure your Drupal sites are not only fast and responsive but also scalable and secure.

At dseven, we’re committed to delivering top-notch web solutions that meet the highest standards of performance and security. Stay tuned to our blog for more insights and tips on leveraging the best tools and practices in web development.