← Back to DevBytes

Varnish Cache: Complete Setup and Configuration Guide

Introduction to Varnish Cache

Varnish Cache is a high-performance HTTP accelerator and reverse proxy designed to dramatically speed up dynamic, content-heavy web applications. Written in C and built around a sophisticated event-driven architecture, Varnish sits between your users and your backend web servers, caching responses in memory and serving subsequent requests directly without touching the origin server.

Unlike many caching solutions that operate at the application layer, Varnish operates purely at the HTTP level. It does not understand your database, your templating engine, or your business logic. Instead, it treats every request as an HTTP transaction and makes intelligent decisions about what to cache, how long to store it, and when to invalidate it. This simplicity is what makes Varnish extraordinarily fast — it can serve hundreds of thousands of requests per second from a single modest server.

Why Varnish Matters

Modern web applications face increasing pressure to deliver content quickly. Users expect sub-second load times, search engines penalize slow sites, and traffic spikes during promotions or viral events can overwhelm even well-provisioned backends. Varnish addresses these challenges by:

How Varnish Works

At its core, Varnish is a reverse proxy that intercepts incoming HTTP requests, checks its cache for a matching response, and either serves the cached object immediately or forwards the request to a backend server. The decision-making logic is controlled by a domain-specific language called VCL (Varnish Configuration Language).

The request lifecycle flows through several stages. When a request arrives, Varnish parses it and executes the vcl_recv subroutine, where you decide whether to attempt a cache lookup, pass the request to the backend, or pipe it directly. If a cache lookup is attempted, Varnish checks its in-memory store. On a cache hit, vcl_hit runs and the cached object is delivered through vcl_deliver. On a miss, vcl_miss runs, the request goes to the backend via vcl_backend_fetch, and the response is processed through vcl_backend_response before being stored and delivered.

Varnish stores cached objects in a shared memory segment called a slab. This design avoids the overhead of copying data between user space and kernel space, which is one reason Varnish achieves such high throughput. The cache size is configured at startup and typically consumes a large portion of available RAM.

Installing Varnish

Varnish is available in the package repositories of most Linux distributions. The examples below assume a Debian or Ubuntu-based system, but equivalent commands exist for RHEL, CentOS, and other distributions.

Installation on Debian/Ubuntu

# Add the official Varnish repository
curl -s https://packagecloud.io/install/repositories/varnishcache/varnish71/script.deb.sh | sudo bash

# Install Varnish
sudo apt-get update
sudo apt-get install varnish

# Verify installation
varnishd -V

Installation on RHEL/CentOS

# Add the official Varnish repository
curl -s https://packagecloud.io/install/repositories/varnishcache/varnish71/script.rpm.sh | sudo bash

# Install Varnish
sudo yum install varnish

# Verify installation
varnishd -V

After installation, the main configuration files are typically located at /etc/varnish/default.vcl for the VCL configuration and /etc/default/varnish or /etc/varnish/varnish.params for daemon parameters such as storage size and listen port.

Basic Configuration

The default VCL file provides a sensible starting point, but real-world deployments require customization. Let us build a configuration step by step. First, examine the default daemon parameters to understand how Varnish is launched.

Daemon Parameters

On systemd-based systems, Varnish parameters are often defined in /etc/varnish/varnish.params:

# /etc/varnish/varnish.params
RELOAD_VCL=1
VARNISH_VCL_CONF=/etc/varnish/default.vcl
VARNISH_LISTEN_PORT=80
VARNISH_ADMIN_LISTEN_ADDRESS=127.0.0.1
VARNISH_ADMIN_LISTEN_PORT=6082
VARNISH_SECRET_FILE=/etc/varnish/secret
VARNISH_STORAGE="malloc,2G"
VARNISH_TTL=120

The VARNISH_STORAGE parameter defines how Varnish stores cached objects. The malloc storage type uses system memory and is the most common choice for production. The value 2G allocates 2 gigabytes. Alternatively, you can use file storage for disk-backed caching, though this is slower and generally discouraged for performance-critical workloads.

A Minimal VCL Configuration

Here is a minimal but functional VCL file that caches static assets and passes dynamic requests to a backend:

# /etc/varnish/default.vcl

vcl 4.1;

import std;

# Define the backend server
backend default {
    .host = "127.0.0.1";
    .port = "8080";
}

sub vcl_recv {
    # Bypass cache for POST, PUT, DELETE requests
    if (req.method != "GET" && req.method != "HEAD") {
        return (pass);
    }

    # Strip cookies for static assets so they can be cached
    if (req.url ~ "\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$") {
        unset req.http.Cookie;
        return (hash);
    }

    # Pass requests with authorization headers
    if (req.http.Authorization) {
        return (pass);
    }

    return (hash);
}

sub vcl_backend_response {
    # Cache static assets for 1 hour regardless of backend headers
    if (bereq.url ~ "\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$") {
        unset beresp.http.Set-Cookie;
        set beresp.ttl = 1h;
    }

    # Do not cache responses with Set-Cookie headers
    if (beresp.http.Set-Cookie) {
        set beresp.uncacheable = true;
        set beresp.ttl = 120s;
    }
}

sub vcl_deliver {
    # Add a header to indicate cache status for debugging
    if (obj.hits > 0) {
        set resp.http.X-Cache = "HIT";
    } else {
        set resp.http.X-Cache = "MISS";
    }
}

This configuration defines a single backend listening on port 8080, which is where your web server or application server should run. Varnish itself listens on port 80 and receives all incoming traffic. Static assets have their cookies stripped so they become cacheable, and a debug header is added to responses so you can verify whether content was served from cache.

Advanced VCL Techniques

Multiple Backends and Load Balancing

Varnish can distribute requests across multiple backend servers using directors. The vmod_directors module provides round-robin, random, and hash-based directors.

vcl 4.1;

import std;
import directors;

backend server1 {
    .host = "10.0.0.11";
    .port = "8080";
    .max_connections = 200;
    .connect_timeout = 3s;
    .first_byte_timeout = 20s;
    .between_bytes_timeout = 5s;
}

backend server2 {
    .host = "10.0.0.12";
    .port = "8080";
    .max_connections = 200;
    .connect_timeout = 3s;
    .first_byte_timeout = 20s;
    .between_bytes_timeout = 5s;
}

sub vcl_init {
    new cluster = directors.round_robin();
    cluster.add_backend(server1);
    cluster.add_backend(server2);
}

sub vcl_recv {
    set req.backend_hint = cluster.backend();
    return (hash);
}

Health Checks and Grace Mode

To ensure Varnish only sends traffic to healthy backends and can serve stale content during outages, configure health probes and grace periods:

backend server1 {
    .host = "10.0.0.11";
    .port = "8080";
    .probe = {
        .url = "/health";
        .timeout = 2s;
        .interval = 5s;
        .window = 5;
        .threshold = 3;
    }
}

sub vcl_backend_response {
    # Keep objects in cache for up to 24 hours after they expire
    set beresp.grace = 24h;
}

sub vcl_hit {
    # Serve stale content if the backend is sick
    if (obj.ttl >= 0s) {
        return (deliver);
    }
    if (std.healthy(req.backend_hint)) {
        if (obj.ttl + 10s > 0s) {
            return (deliver);
        }
    } else {
        if (obj.ttl + obj.grace > 0s) {
            return (deliver);
        }
    }
    return (miss);
}

The probe checks the /health endpoint every 5 seconds. A backend is considered healthy if at least 3 of the last 5 checks succeed. Grace mode allows Varnish to serve expired content when the backend is unavailable, providing a seamless experience during outages.

Cache Invalidation with Bans

Varnish supports bans, which are rules that mark cached objects as stale without immediately purging them. Bans are useful for invalidating groups of objects matching a pattern:

# Purge a single URL
curl -X PURGE http://localhost:6081/about.html

# Ban all objects from a specific host
curl -X BAN http://localhost:6081/ -H "X-Ban-Host: example.com"

To handle these requests in VCL, add the following logic:

acl purge_acl {
    "localhost";
    "127.0.0.1";
    "10.0.0.0"/24;
}

sub vcl_recv {
    if (req.method == "PURGE") {
        if (!client.ip ~ purge_acl) {
            return (synth(403, "Forbidden"));
        }
        return (purge);
    }

    if (req.method == "BAN") {
        if (!client.ip ~ purge_acl) {
            return (synth(403, "Forbidden"));
        }
        if (!req.http.X-Ban-Host) {
            return (synth(400, "Missing X-Ban-Host header"));
        }
        ban("obj.http.host == " + req.http.X-Ban-Host);
        return (synth(200, "Ban added"));
    }

    return (hash);
}

Handling Cookies and Personalized Content

One of the most common challenges with Varnish is caching pages that include personalized elements. A common strategy is to strip non-essential cookies and only bypass the cache when specific session cookies are present:

sub vcl_recv {
    # Remove Google Analytics cookies
    if (req.http.Cookie) {
        set req.http.Cookie = regsuball(req.http.Cookie, "__utm[a-z]=[0-9.]+;?", "");
        set req.http.Cookie = regsuball(req.http.Cookie, "_ga=[0-9.]+;?", "");
        set req.http.Cookie = regsuball(req.http.Cookie, "_gid=[0-9.]+;?", "");
        set req.http.Cookie = regsuball(req.http.Cookie, "^\s*;", "");
        set req.http.Cookie = regsuball(req.http.Cookie, ";\s*;", ";");
    }

    # If no cookies remain, unset the header entirely
    if (req.http.Cookie ~ "^\s*$") {
        unset req.http.Cookie;
    }

    # Bypass cache for authenticated users
    if (req.http.Cookie ~ "session_id=") {
        return (pass);
    }

    return (hash);
}

Monitoring and Troubleshooting

Varnish provides several tools for monitoring cache performance and diagnosing issues. The most important is varnishstat, which displays real-time statistics about cache hits, misses, backend connections, and memory usage.

# View live statistics
varnishstat

# View statistics with a 1-second refresh
varnishstat -1

# Log requests in real time
varnishlog

# Log only backend requests
varnishlog -b

# Log only client requests
varnishlog -c

# View request history
varnishncsa

Key metrics to watch in varnishstat include cache_hit and cache_miss, which indicate your hit ratio. A healthy deployment typically achieves a hit ratio above 80% for static content and 50% or higher for dynamic content. The n_lru_nuked counter indicates how many objects were evicted to make room for new ones; a high value suggests you should increase the cache size.

For deeper analysis, varnishlog provides a detailed transaction log showing every step Varnish takes for each request. This is invaluable for debugging why a particular response is not being cached. Look for the TTL and VCL_return tags to understand caching decisions.

Best Practices

Validating and Reloading Configuration

# Validate VCL syntax without applying
varnishd -C -f /etc/varnish/default.vcl > /dev/null

# Reload VCL without dropping cache (recommended)
varnishadm vcl.load new_config /etc/varnish/default.vcl
varnishadm vcl.use new_config

# Or use the reload utility
varnishreload

The vcl.load and vcl.use approach is preferred in production because it compiles the new configuration and atomically switches to it without restarting the daemon or losing cached objects.

Conclusion

Varnish Cache is a powerful tool that can transform the performance and scalability of web applications. By sitting in front of your backend servers and serving cached responses from memory, it reduces latency, lowers infrastructure costs, and provides resilience during traffic spikes and backend failures. While the VCL language has a learning curve, the investment pays off quickly through fine-grained control over caching behavior. Start with a simple configuration, monitor your hit ratios, and iteratively refine your VCL to handle cookies, personalization, and cache invalidation. With proper tuning and adherence to best practices, Varnish can easily deliver order-of-magnitude performance improvements for most web workloads.

— Ad —

Google AdSense will appear here after approval

← Back to all articles