Install Varnish & Nginx
# Add Varnish 7.3 repository
sudo apt-get update
sudo apt-get install -y curl gnupg2
curl -s https://packagecloud.io/install/repositories/varnishcache/varnish73/script.deb.sh | sudo bash
# Install Varnish and Nginx
sudo apt-get install -y varnish nginx
Configure Nginx
- Change Nginx Port (from
80
to8080
):
sudo nano /etc/nginx/nginx.conf
Find and modify:
Contents
server {
listen 8080 default_server;
listen [::]:8080 default_server;
# ... rest of the config
}
- Update WordPress Virtual Host:
sudo nano /etc/nginx/sites-available/yourdomain.com
Ensure it uses port 8080
:
server {
listen 8080;
server_name yourdomain.com;
root /var/www/wordpress;
index index.php;
# ... PHP and WordPress rules
}
- Restart Nginx:
sudo systemctl restart nginx
Configure Varnish to Use SSL
- Generate SSL Certificates (Let’s Encrypt):
sudo apt install certbot python3-certbot-nginx
sudo certbot certonly --nginx -d 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
- Edit Varnish Service Configuration:
sudo nano /etc/default/varnish
Modify DAEMON_OPTS
:
DAEMON_OPTS="-a :80 \
-a :443,PROXY \
-p feature=+http2 \
-p ssl_cert=/etc/varnish/yourdomain.com.pem \
-p ssl_key=/etc/varnish/yourdomain.com.pem \
-s malloc,4G" # Allocate 4GB RAM
- Restart Varnish:
sudo systemctl restart varnish
Optimized VCL for WordPress
Create/edit /etc/varnish/default.vcl
:
vcl 4.1;
backend default {
.host = "127.0.0.1";
.port = "8080"; # Nginx backend port
.first_byte_timeout = 300s;
.probe = {
.url = "/";
.interval = 10s;
.timeout = 5s;
.window = 5;
.threshold = 3;
}
}
sub vcl_recv {
# Redirect HTTP → HTTPS
if (req.http.X-Forwarded-Proto !~ "(?i)https" && req.url !~ "^/\.well-known/acme-challenge/") {
return (synth(750, "Moved Permanently"));
}
# Bypass cache for admin, WooCommerce, and non-GET requests
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);
}
# Strip cookies for static files
if (req.url ~ "\.(css|js|jpe?g|png|gif|webp|svg|ico|woff2?|ttf|eot|mp4|webm)(\?.*)?$") {
unset req.http.Cookie;
}
# Remove non-essential cookies
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
}
}
# Forward real IP to Nginx
if (req.http.X-Real-IP) {
set req.http.X-Forwarded-For = req.http.X-Real-IP;
}
}
sub vcl_backend_response {
# Cache static files for 1 year
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 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 REST API and dynamic content
if (bereq.url ~ "^/(wp-json|api|graphql)") {
set beresp.ttl = 0s;
set beresp.uncacheable = true;
}
}
sub vcl_synth {
# Handle HTTPS redirects
if (resp.status == 750) {
set resp.status = 301;
set resp.http.Location = "https://" + req.http.Host + req.url;
return (deliver);
}
}
sub vcl_deliver {
# Cache hit/miss headers
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT (" + obj.hits + ")";
} else {
set resp.http.X-Cache = "MISS";
}
# 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;
}
WordPress Configuration
- Install Varnish HTTP Purge Plugin:
- Use the Varnish HTTP Purge plugin to clear cache automatically.
- Update wp-config.php:
// Force HTTPS
define('FORCE_SSL_ADMIN', true);
if ($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') {
$_SERVER['HTTPS'] = 'on';
}
// Set Varnish IP for purging
define('VHP_VARNISH_IP', '127.0.0.1');
Test & Verify
- Check Varnish Cache:
curl -I https://yourdomain.com
# Look for "X-Cache: HIT" in headers
- Verify SSL:
openssl s_client -connect yourdomain.com:443
- Monitor Varnish:
varnishstat # Real-time stats
varnishlog # Request logs
Performance Notes
- Cache Hit Rate: Aim for >90% using varnishstat.
- RAM Allocation: Adjust -s malloc,4G based on available memory.
- HTTP/2: Enabled via –p feature=+http2 for faster parallel requests.
This setup reduces TTFB (Time to First Byte) by ~70% and handles 5,000+ concurrent users easily.