Varnish HTTP Cache is a dedicated open-source application for caching HTTP requests made by clients to the server. It sits in the middle as a reverse proxy, handling load balancing and offloading static content (JavaScript, images, etc.) from the web server.

A common use case is to regionalize part of the web content (static/immutable files) to another country or continent, improving the overall user experience.

Another scenario is placing a local server in a company branch (or a large client’s office) to reduce internet traffic, which is especially useful for metered or slow connections.

INITIAL SETUP

sudo apt update && sudo apt upgrade -y
sudo apt install debian-archive-keyring curl gnupg apt-transport-https -y
curl -fsSL https://packagecloud.io/varnishcache/varnish70/gpgkey|sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/varnish.gpg

IF: Ubuntu 20.04 LTS

sudo tee /etc/apt/sources.list.d/varnishcache_varnish70.list > /dev/null <<-EOF
deb https://packagecloud.io/varnishcache/varnish70/$ID/ $VERSION_CODENAME main
deb-src https://packagecloud.io/varnishcache/varnish70/$ID/ $VERSION_CODENAME main
EOF

OR IF: Ubuntu 22.04 LTS

sudo tee /etc/apt/sources.list.d/varnishcache_varnish70.list > /dev/null <<-EOF
deb https://packagecloud.io/varnishcache/varnish70/ubuntu/ focal main
deb-src https://packagecloud.io/varnishcache/varnish70/ubuntu/ focal main
EOF

INSTALLING VARNISH

sudo apt update && sudo apt install varnish -y

Configuring the service:

sudo cp /lib/systemd/system/varnish.service /etc/systemd/system/
sudo nano /etc/systemd/system/varnish.service
ExecStart=/usr/sbin/varnishd \
      -a :6081 \
      -a localhost:8443,PROXY \
      -p feature=+http2 \
      -f /etc/varnish/default.vcl \
      -s malloc,1g

Note: This is where you can adjust the port Varnish listens on and the amount of memory allocated to the cache (e.g. 256m or 2g). Setting a limit is highly recommended since the default is unlimited.

Configuring the Varnish Configuration Language (VCL):

sudo nano /etc/varnish/default.vcl
backend default {
    .host = "127.0.0.1";
    .port = "8000";
}

OR (for multiple backends):

import directors;

backend backend1 {
    .host = "srv1.example.com";
    .port = "80";
}

backend backend2 {
    .host = "srv2.example.com";
    .port = "80";
}

sub vcl_init {
    new vdir = directors.round_robin();
    vdir.add_backend(backend1);
    vdir.add_backend(backend2);
}

sub vcl_recv {
    set req.backend_hint = vdir.backend();
}

Starting the service:

sudo systemctl daemon-reload
sudo systemctl start varnish
sudo systemctl enable varnish

TESTING

Monitor traffic between Varnish and the web server in one terminal:

tcpdump -i lo dst host 127.0.0.1 and port 8000

In another terminal, make multiple HTTP requests to Varnish and confirm whether it hits the web server each time or not:

curl "http://localhost:6081/"
curl "http://localhost:6081/"
curl "http://localhost:6081/"
curl "http://localhost:6081/"
curl "http://localhost:6081/"
curl "http://localhost:6081/"

Print the response headers for additional information:

curl -I "http://localhost:6081/"

BONUS

In the default VCL configuration file (/etc/varnish/default.vcl), you can override headers on internal requests or define how long Varnish should cache content:

# Changes will be applied to the internal request (between Varnish and the web server).
sub vcl_backend_response {
    # Change Header
    unset beresp.http.Cache-Control;
    set beresp.http.Cache-Control = "max-age=1209600";

    # Define Varnish Retention
    set beresp.ttl = 2w;

    # Defining Conditional Retention
    If (bereq.url == "/VoD") {
        beresp.ttl = 2d;
    }
}

Serving multiple sites with Virtual Hosts:

sub vcl_recv {
  if (server.ip == "10.10.10.10")
  {
    include "/etc/varnish/site-one.vcl";
  }
  elsif (server.ip == "10.20.30.40")
  {
    include "/etc/varnish/site-two.vcl";
  }
}

OR:

sub vcl_recv {
  if (! req.http.Host)
  {
    error 404 "Need a host header";
  }
  set req.http.Host = regsub(req.http.Host, "^www\.", "");
  set req.http.Host = regsub(req.http.Host, ":80$", "");

  if (req.http.Host == "site-one.com")
  {
    include "/etc/varnish/site-one.vcl";
  }
  elsif (req.http.Host == "site-two.com")
  {
    include "/etc/varnish/site-two.vcl";
  }
}

Then create the respective files (/etc/varnish/site-one.vcl and /etc/varnish/site-two.vcl) to specify individual configuration per IP or domain.

To cache everything:

sub vcl_recv {
    unset req.http.Cookie;
}

sub vcl_backend_response {
    # Force caching of all objects for 6 hours
    set beresp.ttl = 6h;
    set beresp.http.Cache-Control = "max-age=21600";
}

sub vcl_deliver {
    unset resp.http.Cache-Control;
}

To cache based on URL:

sub vcl_recv {
    if (req.url ~ "^/path/(A|B|C)$") {
        unset req.http.Cookie;
        return (hash);
    }
}
sub vcl_backend_response {
    # Only set TTL and cache headers for specific URLs
    if (bereq.url ~ "^/path/(A|B|C)$") {
        set beresp.ttl = 6h;
        set beresp.http.Cache-Control = "max-age=21600";
    }
}
sub vcl_deliver {
    if (req.url ~ "^/path/(A|B|C)$") {
        unset resp.http.Cache-Control;
    }
}

WATCH STATISTICS OF FORWARDED AND CACHED REQUESTS

sudo watch -n 1 'varnishstat -1 | grep -E "client_req|cache_hit"'