Howto, Linux

Caddy, Varnish, WordPress, Ubuntu = Fun

Architecture:

What’s the point of all this?

Why are there so many layers?

Why Caddy?

The primary goal of this architecture is to massively and immediately improve any given WordPress site with a quick and easy upgrade to HTTP/2, always-on HTTPS, and robust caching and compression middleware. To that end, we can envision a stack that looks something like:

                                                    +-----------+
          Request +->                               |  Dynamic  |
                                                 +-->  Content  |
+----------+     +----------+     +-----------+  |  |           |
|          +----->          +-----> Cache &   <--+  +-----------+
|  Client  |     | SSL/Edge |     | Compress  |
|          <-----+          <-----+           <--+  +-----------+
+----------+     +----------+     +-----------+  |  |  Static   |
                                                 +--+  Assets   |
         <-+ Response                               |           |
                                                    +-----------+

Varnish enables caching and compression of static assets as well as dynamic content and is the best in its class, so let’s plug that in. This leaves us wanting for implementations of the following components:

  • SSL termination and certificate management
  • Request sanitization and XSS attack mitigation*
  • Request and error logging
  • IP-based whitelisting and rate-limiting
  • URL rewriting (in request headers)
  • URL canonicalization (in response bodies)
  • Static asset serving
  • Hand-off to Varnish via reverse proxy
  • Hand-off to PHP-FPM via FastCGI

Caddy does (*almost) all of these things right out of the box. It makes the “happy path” very happy. And although HAProxy might be better at the edge, and NGINX might have more features, Caddy is a nice fit for this use-case. Performance is still an open question, but so far it’s held up under load quite capably for me.

Our architecture starts to take shape as we fill in the boxes:

                                                    +-----------+
          Request +->                               | FastCGI   |
                                                 +--> (PHP-FPM) |
+----------+     +----------+     +-----------+  |  |           |
|          +----->          +----->           <--+  +-----------+
|  Client  |     |  Caddy   |     |  Varnish  |
|          <-----+          <-----+           <--+  +-----------+
+----------+     +----------+     +-----------+  |  |  Static   |
                                                 +--+  Assets   |
        <-+ Response                                |           |
                                                    +-----------+

Ah, but there’s a problem: Varnish can’t talk to FastCGI or serve static assets from disk. We need to add at least one more component:

                                                             +-----------+
        Request +->                                          | FastCGI   |
                                                          +--> (PHP-FPM) |
+----------+   +---------+   +-----------+   +---------+  |  |           |
|          +--->         +--->           +--->         <--+  +-----------+
|  Client  |   |  Caddy  |   |  Varnish  |   |  Caddy  |
|          <---+         <---+           <---+         <--+  +-----------+
+----------+   +---------+   +-----------+   +---------+  |  |  Static   |
                                                          +--+  Assets   |
       <-+ Response                                          |           |
                                                             +-----------+

             |--------Distribution---------|--------Origination----------|                                                              

This should (hopefully) illustrate why we need so many different layers. It may help to think of the left side of the stack as content distrbution, and the right side of the stack as content origination. Keeping these two halves separate makes it easy to scale when you’re ready by letting you swap in a content distribution network (CDN)—which is essentially caching and compression as a service with anycast and SSL termination—while keeping the same origin:

                Hypothetical Future State
                =========================

                                              +-----------+
        Request +->                           | FastCGI   |
                                           +--> (PHP-FPM) |
+----------+   +----------+   +---------+  |  |           |
|          +--->          +--->         <--+  +-----------+
|  Client  |   |   CDN    |   |  Caddy  |
|          <---+          <---+         <--+  +-----------+
+----------+   +----------+   +---------+  |  |  Static   |
                                           +--+  Assets   |
       <-+ Response                           |           |
                                              +-----------+

             |-Distribution-|--------Origination----------|                                                              

Alternatively, if you don’t go the CDN route, you have the ability to add more Caddy, Varnish, and/or PHP-FPM instances distributed and load-balanced across multiple servers.

Preperation:

  • Install Ubuntu 16.04 in DigitalOcean following this tutorial.
  • MySQL installed using this.
  • Caddy setup with this.
  • Configure a domain to point to your droplet using this.
  • Install wordpress with caddy.

Firewall:

Set up your firewall, minimally:

$ sudo ufw disable
$ sudo ufw reset
$ sudo ufw limit OpenSSH
$ sudo ufw allow to any port 80,443 proto tcp
$ sudo ufw enable

Varnish:

  • Add Varnish 5 repo with curl -s https://packagecloud.io/install/repositories/varnishcache/varnish5/script.deb.sh | sudo bash
  • Install varnish with sudo apt-get install varnish

Caddyfile:

Change your caddyfile following my reference:

junayeed.me {
  tls nirjhor@chorompotro.com

  proxy / localhost:6081 {
    transparent
  }
}

junayeed.me:8888 {
  bind localhost

  tls off

  root /var/www/wordpress/

    gzip
    fastcgi / /run/php/php7.0-fpm.sock php

   rewrite {
    if {path} not_match ^\/wp-admin
    to {path} {path}/ /index.php?{query}
}

  header /wp-content Cache-Control "max-age=2592000, s-maxage=86400"
  header /wp-includes Cache-Control "max-age=2592000, s-maxage=86400"
 
  internal /xmlrpc.php
  header / -X-Pingback
}

VCL:

Change /etc/varnish/default.vcl like so:

# Marker to tell the VCL compiler that this VCL has been adapted to the
# new 4.0 format.
vcl 4.0;

# Default backend definition. Set this to point to your content server.
backend default {
    .host = "localhost";
    .port = "8888";
}

WordPress:

Most WordPress optimization guides suggest unsetting cache control headers such as Cache-Control, ETag, Last-Modified, Expires, etc. Instead, we’re going to set them and let Varnish serve posts, pages, and attachments as slowly-changing dynamic content. Yes, you read that correctly: your WordPress site is now a glorified static site generator!

  • Change your WordPress and Site Addresses to https:// in wp-config.php, or by using the wp-cli tool.
  • Apply this wp-config.php snippets
// Insert *before* requiring wp-settings.php
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO']) {
  $_SERVER['HTTPS'] = 'on';
}

// Insert *after* requiring wp-settings.php
// Alternatively, you can add this to your child theme's functions.php, or into its own (mu) plugin.
add_filter('addh_options', 'my_addh_options');
function my_addh_options($options) {
  // We only want the last-modified header, so disable these other ones.
  $options['add_etag_header'] = false;
  $options['add_expires_header'] = false;
  $options['add_cache_control_header'] = false;

  return $options;
}

Credits:

Tagged , , , , ,

Leave a Reply

Your email address will not be published. Required fields are marked *