Docker, Software, Varnish, WordPress

Using Varnish to Speed Up WordPress in Docker

using-varnish-to-speed-up-wordpress-in-docker

Varnish is an HTTP reverse proxy for accelerating the website that provides dynamic content. WordPress is a blogging system, but the performance is not very good. Docker is a container level virtual machine, it can make developers easily to create and combine different service.

So there is an example for integrating Varnish with WordPress to super speed up your WordPress site.

Here is the Varnish configuration file for WordPress. The source from here. I have modified some config to fit Docker environment.

Path: wordpress-varnish/custom-varnish/wordpress.vcl

# A heavily customized VCL to support WordPress
# Some items of note:
# Supports https
# Supports admin cookies for wp-admin
# Caches everything
# Support for custom error html page

# Assumed 'wordpress' host, this can be docker servicename
backend default {
  .host = "wordpress";
  .port = "80";
}

sub vcl_recv {

  # pass wp-admin urls
  # before any action to fix visual editor disappear
  if (req.url ~ "(wp-login|wp-admin)" || req.url ~ "preview=true" || req.url ~ "xmlrpc.php") {
    return (pass);
  }

  # Only a single backend
  set req.backend_hint= default;

  # Setting http headers for backend
  set req.http.X-Forwarded-For = client.ip;
  set req.http.X-Forwarded-Proto = "https";

  # Unset headers that might cause us to cache duplicate infos
  unset req.http.Accept-Language;
  unset req.http.User-Agent;

  # The purge...no idea if this works
  if (req.method == "PURGE") {
    if (!client.ip ~ purge) {
      return(synth(405,"Not allowed."));
    }
    return (purge);
  }
      
  if ( std.port(server.ip) == 6080) {
    set req.http.x-redir = "https://" + req.http.host + req.url;
    return (synth(750, "Moved permanently"));
  }

  # drop cookies and params from static assets
  if (req.url ~ "\.(gif|jpg|jpeg|swf|ttf|css|js|flv|mp3|mp4|pdf|ico|png)(\?.*|)$") {
    unset req.http.cookie;
    set req.url = regsub(req.url, "\?.*$", "");
  }

  # drop tracking params
    if (req.url ~ "\?(utm_(campaign|medium|source|term)|adParams|client|cx|eid|fbid|feed|ref(id|src)?|v(er|iew))=") {
      set req.url = regsub(req.url, "\?.*$", "");
  }

  # pass wp-admin cookies
  if (req.http.cookie) {
    if (req.http.cookie ~ "(wordpress_|wp-settings-)") {
      return(pass);
    } else {
      unset req.http.cookie;
    }
  }
}

sub vcl_backend_response {
  # retry a few times if backend is down
  if (beresp.status == 503 && bereq.retries < 3 ) {
    return(retry);
  }

  if (bereq.http.Cookie ~ "(UserID|_session)") {
    # if we get a session cookie...caching is a no-go
    set beresp.http.X-Cacheable = "NO:Got Session";
    set beresp.uncacheable = true;
    return (deliver);
  } elsif (beresp.ttl <= 0s) {
    # Varnish determined the object was not cacheable
    set beresp.http.X-Cacheable = "NO:Not Cacheable";
  } elsif (beresp.http.set-cookie) {
    # You don't wish to cache content for logged in users
    set beresp.http.X-Cacheable = "NO:Set-Cookie";
    set beresp.uncacheable = true;
    return (deliver);
  } elsif (beresp.http.Cache-Control ~ "private") {
    # You are respecting the Cache-Control=private header from the backend
    set beresp.http.X-Cacheable = "NO:Cache-Control=private";
    set beresp.uncacheable = true;
    return (deliver);
  } else {
    # Varnish determined the object was cacheable
    set beresp.http.X-Cacheable = "YES";

    # Remove Expires from backend, it's not long enough
    unset beresp.http.expires;

    # Set the clients TTL on this object
    set beresp.http.cache-control = "max-age=900";

    # Set how long Varnish will keep it
    set beresp.ttl = 1w;

    # marker for vcl_deliver to reset Age:
    set beresp.http.magicmarker = "1";
  }

  # unset cookies from backendresponse
  if (!(bereq.url ~ "(wp-login|wp-admin)"))  {
    set beresp.http.X-UnsetCookies = "TRUE";
    unset beresp.http.set-cookie;
    set beresp.ttl = 1h;
  }

  # long ttl for assets
  if (bereq.url ~ "\.(gif|jpg|jpeg|swf|ttf|css|js|flv|mp3|mp4|pdf|ico|png)(\?.*|)$") {
    set beresp.ttl = 365d;
  }

  set beresp.grace = 1w;
}

sub vcl_hash {
  if ( req.http.X-Forwarded-Proto ) {
    hash_data( req.http.X-Forwarded-Proto );
  }
}

sub vcl_backend_error {
  # display custom error page if backend down
  if (beresp.status == 503 && bereq.retries == 3) {
    synthetic(std.fileread("/etc/varnish/error503.html"));
    return(deliver);
  }
}

sub vcl_synth {
  # redirect for http
  if (resp.status == 750) {
    set resp.status = 301;
    set resp.http.Location = req.http.x-redir;
    return(deliver);
  }
  # display custom error page if backend down
  if (resp.status == 503) {
    synthetic(std.fileread("/etc/varnish/error503.html"));
    return(deliver);
  }
}


sub vcl_deliver {
  # oh noes backend is down
  if (resp.status == 503) {
    return(restart);
  }

  if (resp.http.magicmarker) {
    # Remove the magic marker
    unset resp.http.magicmarker;

    # By definition we have a fresh object
    set resp.http.age = "0";
  }

  if (obj.hits > 0) {
    set resp.http.X-Cache = "HIT";
  } else {
    set resp.http.X-Cache = "MISS";
  }

  set resp.http.Access-Control-Allow-Origin = "*";
}

sub vcl_hit {
  if (req.method == "PURGE") {
    return(synth(200,"OK"));
  }
}

sub vcl_miss {
  if (req.method == "PURGE") {
    return(synth(404,"Not cached"));
  }
}

 

Varnish Dockerfile: wordpress-varnish/custom-varnish/Dockerfile

FROM eeacms/varnish
COPY wordpress.vcl /etc/varnish/conf.d/wordpress.vcl

 

Docker-Compose file: wordpress-varnish/docker-compose.yml:

version: "3.4"
services:
  wordpress:
    image: wordpress:5.1.0-php7.3-apache
    depends_on:
      - db
    restart: always
    volumes:
      - ./wordpress:/var/www/html
    logging:
      options:
        max-size: 10m
  varnish:
    build:
      context: ./custom-varnish
      dockerfile: Dockerfile
    depends_on:
      - wordpress
    restart: always
    ports:
      - 6081:6081
    environment:
      BACKENDS: "wordpress"
      BACKENDS_PORT: 80
      BACKENDS_PROBE_ENABLED: "false"
    logging:
      options:
        max-size: 10m
  db:
    image: mysql:5.7
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: wordpress
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: wordpress

 

Run command: docker-compose up –build