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.
Contents
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
- 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
- Install Varnish:
sudo apt install -y varnish
Configure Apache for Varnish
- 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>
- Restart Apache:
sudo systemctl restart apache2
Configure SSL with Let’s Encrypt
- Install Certbot:
sudo apt install -y certbot python3-certbot-apache
- Obtain SSL Certificate:
sudo certbot certonly --standalone -d yourdomain.com
- Certificates will be stored in /etc/letsencrypt/live/yourdomain.com/.
- 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
- 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.
- 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?
- 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.
- 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).
- 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).
- Security:
- Adds headers like X-Frame-Options, X-XSS-Protection.
- Hides server information.
- Debugging:
- X-Cache header shows cache status (HIT or MISS).
Finalize Configuration
- Restart Varnish:
sudo systemctl restart varnish
- Open Firewall Ports:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw reload
- Test HTTPS:
curl -I https://yourdomain.com
- Look for X-Cache: HIT or X-Cache: MISS headers.
- Test HTTP-to-HTTPS Redirect:
curl -I http://yourdomain.com
- Should return a 301 redirect to HTTPS.
WordPress Integration
- Install Varnish HTTP Purge Plugin:
- Install the Varnish HTTP Purge plugin to automatically clear cache when content is updated.
- 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
- Check Varnish Logs:
sudo varnishlog
- Verify Backend Health:
varnishadm backend.list
- 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.