Varnish Cache + SSL Setup for WordPress, Apache, and Ubuntu

Here's a clear, step-by-step guide to install and configure Varnish Cache with SSL on WordPress (Apache/Ubuntu).

Alby Andersen

Varnish Cache is a powerful HTTP accelerator that sits in front of your web server (Apache) to cache content in memory, drastically reducing server load and improving page load times. When combined with Cloudflare’s CDN and SSL, it creates a robust performance and security stack.

Prerequisites

  • Ubuntu 20.04/22.04 LTS
  • Apache and WordPress
  • Root/sudo access
  • Domain pointing to your server’s IP

Install Varnish with SSL Support

  1. Add Varnish Repository:
   sudo apt update
   sudo apt install -y curl apt-transport-https
   curl -s https://packagecloud.io/install/repositories/varnishcache/varnish72/script.deb.sh | sudo bash
  1. Install Varnish:
   sudo apt install -y varnish

Configure Apache for Varnish

  1. Change Apache’s Port:
  • Edit /etc/apache2/ports.conf:
    apache Listen 8080 # Change from 80 to 8080
  • Update your WordPress virtual host file (e.g., /etc/apache2/sites-available/000-default.conf):
    apache <VirtualHost *:8080> ServerName yourdomain.com # … other directives (DocumentRoot, ServerAdmin, etc.) </VirtualHost>
  1. Restart Apache:
   sudo systemctl restart apache2

Configure SSL with Let’s Encrypt

  1. Install Certbot:
   sudo apt install -y certbot python3-certbot-apache
  1. Obtain SSL Certificate:
   sudo certbot certonly --standalone -d yourdomain.com
  • Certificates will be stored in /etc/letsencrypt/live/yourdomain.com/.
  1. Combine Certificate and Key for Varnish:
   sudo cat /etc/letsencrypt/live/yourdomain.com/fullchain.pem /etc/letsencrypt/live/yourdomain.com/privkey.pem | sudo tee /etc/varnish/yourdomain.com.pem
   sudo chown varnish:varnish /etc/varnish/yourdomain.com.pem

Configure Varnish for SSL

  1. Edit Varnish Service File (/etc/default/varnish):
   DAEMON_OPTS="-a :80 \
                -a :443,PROXY \
                -T localhost:6082 \
                -f /etc/varnish/default.vcl \
                -s malloc,4G \
                -p feature=+http2 \
                -p ssl_cert=/etc/varnish/yourdomain.com.pem \
                -p ssl_key=/etc/varnish/yourdomain.com.pem"
  • -a :443,PROXY: Enable SSL on port 443.
  1. Create/Edit VCL File (/etc/varnish/default.vcl):

   vcl 4.1;

backend default {
    .host = "127.0.0.1";
    .port = "8080";  # Apache/Nginx backend port
    .first_byte_timeout = 300s;
    .probe = {
        .url = "/";
        .interval = 10s;
        .timeout = 5s;
        .window = 5;
        .threshold = 3;
    }
}

sub vcl_recv {
    # Redirect HTTP → HTTPS (Force all traffic to HTTPS)
    if (req.http.X-Forwarded-Proto !~ "(?i)https" && req.url !~ "^/\.well-known/acme-challenge/") {
        return (synth(750, "Moved Permanently"));
    }

    # Bypass cache for admin, login, and dynamic pages
    if (
        req.url ~ "^/(wp-admin|wp-login|cart|checkout|my-account|add-to-cart|logout|xmlrpc.php|rest-api/|graphql)" ||
        req.method != "GET"
    ) {
        return (pass);
    }

    # Clean cookies (Remove unnecessary cookies for caching)
    if (req.http.Cookie) {
        set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(_wpnonce|comment_author|woocommerce_items_in_cart|wp-postpass_|wordpress_logged_in_|wp-settings-)[^;]*", "");
        set req.http.Cookie = regsub(req.http.Cookie, "^;\s*", "");
        set req.http.Cookie = regsub(req.http.Cookie, ";\s*$", "");

        if (req.http.Cookie == "") {
            unset req.http.Cookie;
        } else {
            return (pass);  # Bypass cache if cookies remain (security)
        }
    }

    # Set real IP if no CDN (e.g., Cloudflare) is used
    if (req.http.X-Real-IP) {
        set req.http.X-Forwarded-For = req.http.X-Real-IP;
    }

    # WebSocket and Live Reload support
    if (req.http.Upgrade ~ "(?i)websocket") {
        return (pipe);
    }
}

sub vcl_backend_response {
    # Cache static files for a long time (CSS, JS, images)
    if (beresp.url ~ "\.(css|js|jpe?g|png|gif|webp|svg|ico|woff2?|ttf|eot|mp4|webm)(\?.*)?$") {
        set beresp.ttl = 365d;
        set beresp.http.Cache-Control = "public, max-age=31536000";
        unset beresp.http.Set-Cookie;
    }

    # Cache HTML pages for 2 hours
    else if (beresp.http.Content-Type ~ "text/html") {
        set beresp.ttl = 2h;
        set beresp.http.Cache-Control = "public, max-age=7200";
        set beresp.grace = 1h;  # Serve stale content if backend is down
    }

    # Bypass cache for WordPress REST API and dynamic content
    if (bereq.url ~ "^/(wp-json|api|graphql)") {
        set beresp.ttl = 0s;
        set beresp.uncacheable = true;
    }

    # Do not cache errors (504/503)
    if (beresp.status >= 500) {
        set beresp.ttl = 0s;
    }
}

sub vcl_synth {
    # Handle HTTP → HTTPS redirect
    if (resp.status == 750) {
        set resp.status = 301;
        set resp.http.Location = "https://" + req.http.Host + req.url;
        return (deliver);
    }
}

sub vcl_deliver {
    # Add cache hit/miss headers (for debugging)
    if (obj.hits > 0) {
        set resp.http.X-Cache = "HIT (" + obj.hits + ")";
    } else {
        set resp.http.X-Cache = "MISS";
    }

    # Add security headers
    set resp.http.X-Content-Type-Options = "nosniff";
    set resp.http.X-Frame-Options = "SAMEORIGIN";
    set resp.http.X-XSS-Protection = "1; mode=block";
    set resp.http.Referrer-Policy = "strict-origin-when-cross-origin";

    # Remove sensitive headers
    unset resp.http.Via;
    unset resp.http.X-Powered-By;
    unset resp.http.Server;
    unset resp.http.X-Varnish;
}

Why Is This Configuration the Best?

  1. WordPress-Specific Optimizations:
    • Bypass cache for /wp-admin, /wp-login, WooCommerce pages, and REST API.
    • Bypass cache when wordpress_logged_in_ cookie is detected.
    • Block unnecessary requests for Gravatars and emojis.
  2. SSL and Redirection:
    • All HTTP traffic is redirected to HTTPS (except .well-known/acme-challenge for Let’s Encrypt).
    • Ready for HSTS implementation (can be added manually).
  3. Performance:
    • Static files cached for 1 year, HTML pages for 2 hours.
    • beresp.grace allows serving stale content if the backend is down.
    • WebSocket support (Upgrade header handling).
  4. Security:
    • Adds headers like X-Frame-Options, X-XSS-Protection.
    • Hides server information.
  5. Debugging:
    • X-Cache header shows cache status (HIT or MISS).

Finalize Configuration

  1. Restart Varnish:
   sudo systemctl restart varnish
  1. Open Firewall Ports:
   sudo ufw allow 80/tcp
   sudo ufw allow 443/tcp
   sudo ufw reload
  1. Test HTTPS:
   curl -I https://yourdomain.com
  • Look for X-Cache: HIT or X-Cache: MISS headers.
  1. Test HTTP-to-HTTPS Redirect:
   curl -I http://yourdomain.com
  • Should return a 301 redirect to HTTPS.

WordPress Integration

  1. Install Varnish HTTP Purge Plugin:
  • Install the Varnish HTTP Purge plugin to automatically clear cache when content is updated.
  1. Configure wp-config.php:
    Add this to wp-config.php to ensure WordPress recognizes HTTPS:
   define('FORCE_SSL_ADMIN', true);
   if ($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') {
       $_SERVER['HTTPS'] = 'on';
   }

Troubleshooting

  1. Check Varnish Logs:
   sudo varnishlog
  1. Verify Backend Health:
   varnishadm backend.list
  1. Renew SSL Certificates:
   sudo certbot renew --dry-run

This setup ensures:

  • Varnish handles SSL termination.
  • Apache serves content on port 8080.
  • Automatic HTTP-to-HTTPS redirects.
  • Optimized caching for WordPress.
Share This Article