Login Logout

Howto Varnish

Varnish est un reverse-proxy HTTP. Il se met typiquement devant des serveurs HTTP et garde en cache les réponses autant que possible. Il gère également (un peu) le load-balancing entre les serveurs HTTP.

Installation

# apt install varnish

# varnishd -V
varnishd (varnish-6.5.1 revision 1dae23376bb5ea7a6b8e9e4b9ed95cdc9469fb64)
Copyright (c) 2006 Verdens Gang AS
Copyright (c) 2006-2020 Varnish Software

Configuration de base

varnishd

Le fichier /etc/default/varnish n’est plus utilisé avec Systemd. Pour éviter toute confusion, on supprime ces fichiers :

# rm /etc/default/varnish*

Pour modifier la configuration, on copie l’unité Systemd :

# cp -a /lib/systemd/system/varnish.service /etc/systemd/system

Puis l’on personnalise la configuration via /etc/systemd/system/varnish.service (ne pas oublier de systemctl daemon-reload à chaque modification) :

ExecStart=/usr/sbin/varnishd \
          -j unix,user=vcache \
          -F \
          -a 0.0.0.0:80 \
          -T localhost:6082 \
          -f /etc/varnish/default.vcl \
          -S /etc/varnish/secret \
          -s malloc,256m
          -p thread_pools=<Nombre de cores CPU> -p thread_pool_add_delay=2 -p thread_pool_min=500 -p thread_pool_max=5000

Détails de certaines options de varnishd :

  • -a : spécifie IP:port sur lequel Varnish écoute pour les requêtes HTTP. On peut ainsi spécifier une IP secondaire pour coexister avec un autre service HTTP (Apache, Nginx) sur le port 80 (-a 192.0.2.1:80) ou faire écouter Varnish uniquement en local (-a 127.0.0.1:8080) ou alors le faire écouter de partout (-a 0.0.0.0:80) ou même spécifier plusieurs IP (-a 0.0.0.0:80,127.0.0.1:81)
  • -T : spécifie l’interface d’admin de Varnish, accessible avec varnishadm
  • -f : spécifie le fichier des règles Varnish au format VCL (Varnish Configuration Language)
  • -s : spécifie où est stocké le cache. Cela peut être en mémoire (-s malloc,2G) et/ou dans un fichier (-s file,varnish_storage.bin,8G)

Note : Pour Debian 8, le script /usr/share/varnish/reload-vcl utilise toujours /etc/default/varnish… on remplace donc ExecReload= par un script minimal :

ExecReload=/etc/varnish/reload-vcl.sh

avec /etc/varnish/reload-vcl.sh (à mettre en chmod 700 bien sûr) :

#!/bin/sh
UUID=`cat /proc/sys/kernel/random/uuid`
/usr/sbin/varnishd -C -f /etc/varnish/default.vcl >/dev/null \
 && /usr/bin/varnishadm -T localhost:6082 -S /etc/varnish/secret "vcl.load vcl_$UUID /etc/varnish/default.vcl" \
 && /usr/bin/varnishadm -T localhost:6082 -S /etc/varnish/secret "vcl.use vcl_$UUID"

Note : avant Debian 8, Varnish ne supportait pas d’être lancé avec umask 077, c’est corrigé en Debian Jessie

default.vcl

Les règles Varnish définissent la mise en cache en utilisant une syntaxe particulière : le VCL (Varnish Configuration Language).

Ces règles sont définies dans le fichier /etc/varnish/default.vcl et peuvent contenir des include.

Il faut au minimum configurer le backend :

backend default {
    .host = "127.0.0.1";
    .port = "8080";
}

On écrit ensuite ses règles au sein de sous-routines avec la syntaxe VCL.

Pour vérifier ses règles (ceci est fait par défaut lors d’un redémarrage) :

# sudo -u vcache varnishd -Cf /etc/varnish/default.vcl > /dev/null

Note : si on obtient le message du type :

Message from dlopen:
Could not load compiled VCL.
        dlopen() = vcl_.CflagTest.7071.1631806560.240605354/vgc.so: cannot open shared object file: Permission denied
Running dlopen failed, exited with 1

… c’est probablement qu’on utilise un dossier temporaire monté en noexec. On peut suivre la solution préconisée ici.

Pour visualiser les règles “builtin” (les règles par défaut si l’on ne change rien) :

# varnishd -x builtin | less

Gestion des logs

Par défaut, Varnish n’écrit pas ses logs dans un fichier, mais dans un segment mémoire, ce qui permet d’augmenter grandement les performances. Quand l’espace est plein, Varnish réécrit par dessus en repartant de l’origine, ce qui fait que la mémoire allouée pour les logs n’augmente pas. On peut voir des logs en direct avec les outils varnishstat (stats de Varnish), varnishtop (top pour Varnish), varnishlog (logs verbeux) ou varnishnsca (logs au format NCSA comme Apache) :

# varnishstat
# varnishtop -i ReqURL
# varnishlog
# varnishnsca

Des filtres peuvent être appliqués sur ces commandes, voici des exemples pratiques pour le debug :

# varnishlog -q 'TxHeader eq MISS' -q "ReqHeader ~ '^Host: example\.com$'" |grep RxURL
# varnishlog -q "ReqHeader:host ~ '^example\.com$' and ReqHeader:X-Forwarded-For ~ '1\.2\.3\.4' and RespHeader:X-Cache eq MISS"
# varnishncsa -q "ReqHeader eq 'X-Cache: MISS'"

Il est aussi possible d’écrire ces logs dans des fichiers en lançant varnishlog et varnishnsca en mode démon (lancés par défaut sous Debian 8). Ce démon est indépendant de varnishd, ce qui a l’avantage de ne pas ralentir les performances ; varnishd n’attend pas que la ligne de log soit écrite dans le fichier avant de servir la page : il l’inscrit en mémoire, et c’est ensuite varnishlog ou varnishncsa qui se chargera de copier la ligne dans le fichier sur le disque.

on peut aussi faire comme ceci (pour filtrer tout ce qui provient d’une IP donnée), voir en direct et stocker dans fichier en même temps :

# varnishlog -q 'ReqStart ~ "1\.2\.3\.4"' | tee /tmp/varnish.log

Syntaxe VCL

La syntaxe VCL est complexe mais puissante. On découpe un fichier VCL en plusieurs sous-routines dans lesquelles on définit des actions/comportements en fonction de certaines conditions. Les sous-routines principales sont vcl_recv et vcl_backend_response :

  • vcl_recv est appelé AVANT le début de la requête au backend. On peut donc choisir vers quel backend renvoyer la requête. On peut aussi modifier la requête (modifier des entêtes HTTP, supprimer des demandes de cookies, etc.). Seules les actions set req. sont possibles.
  • vcl_backend_response (remplace vcl_fetch depuis Varnish 4) est appelé APRÈS la réception de la réponse du backend. Les actions set bereq. (équivalentes à set req. dans vcl_recv) sont possibles, mais aussi set beresp. (pour backend response).

On peut définir plusieurs les sous-routines, permettant ainsi de surcharger la configuration et d’utiliser astucieusement les include et les conditions if : une requête s’arrêtera de parcourir la sous-routine dès qu’elle tombe sur un return.

Attention, Varnish charge ses propres sous-routines par défaut et si on veut changer son comportement il est impératif de copier la sous-routine par défaut (voir #configuration-par-défaut) puis de la modifier !

Voyons un exemple simple :

sub vcl_recv {
    if (req.http.host == "example.com" || 
            req.http.host == "www.example.com") {
        set req.backend_hint = default;
    }
    # Version avec une regex (plus coûteux) :
    #if (req.http.host ~ "(example\.com|www\.example\.com)") {
    #    set req.backend_hint = default;
    #}
    
    if (req.url ~ "^/images") {
        unset req.http.cookie;
    }
}

sub vcl_backend_response {
   if (bereq.url ~ "\.(png|gif|jpg)$") {
     unset beresp.http.set-cookie;
     set beresp.ttl = 3600s;
  }
}

sub vcl_deliver {
  if (resp.http.X-Varnish ~ "[0-9]+ +[0-9]+") {
    set resp.http.X-Cache = "HIT";
  } else {
    set resp.http.X-Cache = "MISS";
  }
}

Configuration par défaut

Voici les règles par défaut de Varnish visualisables via varnishd -x builtin :

sub vcl_recv {
    if (req.method == "PRI") {
        /* We do not support SPDY or HTTP/2.0 */
        return (synth(405));
    }
    if (req.method != "GET" &&
      req.method != "HEAD" &&
      req.method != "PUT" &&
      req.method != "POST" &&
      req.method != "TRACE" &&
      req.method != "OPTIONS" &&
      req.method != "DELETE") {
        /* Non-RFC2616 or CONNECT which is weird. */
        return (pipe);
    }

    if (req.method != "GET" && req.method != "HEAD") {
        /* We only deal with GET and HEAD by default */
        return (pass);
    }
    if (req.http.Authorization || req.http.Cookie) {
        /* Not cacheable by default */
        return (pass);
    }
    return (hash);
}

sub vcl_pipe {
    return (pipe);
}

sub vcl_pass {
    return (fetch);
}

sub vcl_hash {
    hash_data(req.url);
    if (req.http.host) {
        hash_data(req.http.host);
    } else {
        hash_data(server.ip);
    }
    return (lookup);
}

sub vcl_purge {
    return (synth(200, "Purged"));
}

sub vcl_hit {
    if (obj.ttl >= 0s) {
        // A pure unadultered hit, deliver it
        return (deliver);
    }
    if (obj.ttl + obj.grace > 0s) {
        // Object is in grace, deliver it
        // Automatically triggers a background fetch
        return (deliver);
    }
    // fetch & deliver once we get the result
    return (fetch);
}

sub vcl_miss {
    return (fetch);
}

sub vcl_deliver {
    return (deliver);
}

sub vcl_synth {
    set resp.http.Content-Type = "text/html; charset=utf-8";
    set resp.http.Retry-After = "5";
    synthetic( {"<!DOCTYPE html>
<html>
  <head>
    <title>"} + resp.status + " " + resp.reason + {"</title>
  </head>
  <body>
    <h1>Error "} + resp.status + " " + resp.reason + {"</h1>
    <p>"} + resp.reason + {"</p>
    <h3>Guru Meditation:</h3>
    <p>XID: "} + req.xid + {"</p>
    <hr>
    <p>Varnish cache server</p>
  </body>
</html>
"} );
    return (deliver);
}

sub vcl_backend_fetch {
    return (fetch);
}

sub vcl_backend_response {
    if (beresp.ttl <= 0s ||
      beresp.http.Set-Cookie ||
      beresp.http.Surrogate-control ~ "no-store" ||
      (!beresp.http.Surrogate-Control &&
        beresp.http.Cache-Control ~ "no-cache|no-store|private") ||
      beresp.http.Vary == "*") {
        /*
        * Mark as "Hit-For-Pass" for the next 2 minutes
        */
        set beresp.ttl = 120s;
        set beresp.uncacheable = true;
    }
    return (deliver);
}

sub vcl_backend_error {
    set beresp.http.Content-Type = "text/html; charset=utf-8";
    set beresp.http.Retry-After = "5";
    synthetic( {"<!DOCTYPE html>
<html>
  <head>
    <title>"} + beresp.status + " " + beresp.reason + {"</title>
  </head>
  <body>
    <h1>Error "} + beresp.status + " " + beresp.reason + {"</h1>
    <p>"} + beresp.reason + {"</p>
    <h3>Guru Meditation:</h3>
    <p>XID: "} + bereq.xid + {"</p>
    <hr>
    <p>Varnish cache server</p>
  </body>
</html>
"} );
    return (deliver);
}

sub vcl_init {
    return (ok);
}

sub vcl_fini {
    return (ok);
}

Listes des conditions/actions/comportements

Voici un certain nombre de conditions possibles :

# Condition sur l'entête HTTP Host:
if (req.http.host ~ "^regex$")
# Présence d'un cookie
if (req.http.cookie)
# Condition sur l'URL demandée
if (req.url ~ "^/regex$")
# Si le backend est accessible
if (req.backend.healthy)
# Présence de l'entête Accept-Encoding
if (req.http.Accept-Encoding)
# Condition sur la requête faite
if (req.method != "GET" && req.method != "HEAD")
# Présence de l'entête X-Forwarded-For
if (req.http.x-forwarded-for)
# Condition sur les entêtes envoyés
if (req.http.Authorization || req.http.Cookie)
# Condition sur la politique de mise en cache
if (req.http.Cache-Control ~ "no-cache")
# Condition sur le temps de mise en cache (Cache-Control: max-age a priori)
if (beresp.ttl < 120s)
# Condition sur le statut des réponses
if (beresp.status == 404 || beresp.status == 503 || beresp.status == 500)

Voici un certain nombre d’actions possibles :

# Renvoyer vers un backend
set req.backend_hint = baz;
# Supprimer les cookies dans la requête
unset req.http.cookie;
# Supprimer un certain nombre d'entêtes HTTP
unset req.http.X-Forwarded-For;
unset req.http.Accept-Encoding;
# Positionner un certain nombre d'entêtes HTTP pour la requête
set req.http.X-Forwarded-For = client.ip;
set req.http.Accept-Encoding = "gzip";
set req.http.Accept-Encoding = "deflate";
# Positionner un certain nombre d'entêtes HTTP pour la réponse
set beresp.http.expires = "Mon, 1 Jan 2007 00:00:01 GMT";
set beresp.http.X-foo = "bar";
# Renvoyer une erreur HTTP
return(synth(404, "Page not found"));

Enfin, voici les principaux comportements possibles :

# Renvoie vers le backend (pas de cache)
return (pass);
# Renvoie la version en cache (si possible)
return (lookup);
return (deliver);
# Renvoie "directement" vers le backend sans inspection du contenu
return (pipe);
# Redémarre la demande au backend (et incrémente le compteur de restarts)
return (restart);

Gestion du cache

Par défaut Varnish respecte le comportement standard d’un reverse-proxy : pas de cache en présence de cookie, respect des entêtes HTTP envoyés par le client et backend : sa configuration par défaut devrait convenir pour les sites codés correctement ! L’avantage (ou le piège) est que l’on peut facilement intervenir sur ce comportement standard pour ajouter des exceptions… si le code d’un site est incorrect.

Grâce aux règles VCL on peut vraiment définir finement la mise en cache ou pas, en complément des entêtes de cache renvoyés par le code. On peut ainsi mettre en cache même si certains cookies sont présents, en les supprimant de la requête.

sub vcl_recv {
    if (req.http.Authorization) {
        return (pass);
    }

    if (req.http.Cookie ~ "(VARNISH|DRUPAL_UID)") {
        return (pass);
    }

    if (req.http.cookie) {
        set req.http.Cookie = regsuball(req.http.Cookie, "_gat=[^;]+(; )?", "");       # Google Analytics
        set req.http.Cookie = regsuball(req.http.Cookie, "_ga=[^;]+(; )?", "");        # Google Analytics
        set req.http.Cookie = regsuball(req.http.Cookie, "_gaq=[^;]+(; )?", "");       # Google Analytics
        set req.http.Cookie = regsuball(req.http.Cookie, "__utm[^=]*=[^;]+(; )?", ""); # Google Analytics
        set req.http.Cookie = regsuball(req.http.Cookie, "_gid[^=]*=[^;]+(; )?", ""); # Google Analytics

        if (req.http.cookie ~ "^ *$") {
            unset req.http.cookie;
        }
    }
}

sub vcl_backend_response {
    if (beresp.status == 403 || beresp.status == 404 || beresp.status == 503 || beresp.status == 500) {
        set beresp.ttl = 10s;
    }

  if (beresp.uncacheable) {
    set beresp.http.X-Cacheable = "FALSE";
  } else {
    set beresp.http.X-Cacheable = "TRUE";
  }

}

sub vcl_deliver {
  if (resp.http.X-Varnish ~ "[0-9]+ +[0-9]+") {
    set resp.http.X-Cache = "HIT";
  } else {
    set resp.http.X-Cache = "MISS";
  }
}

TTL

Le temps de mise en cache (TTL) - si il y a mise en cache - est défini par la variable beresp.ttl.

Si le backend renvoie un entête HTTP Cache-Control contenant max-age= (ou s-maxage=) et/ou Expires, Varnish ajuste tout seul sa variable beresp.ttl.

On peut bien sûr définir beresp.ttl, mais attention cela va écraser la valeur déduite de Varnish, on conseille ainsi de faire :

sub vcl_backend_response {

# Default TTL if the backend does not send Expires or max-age/s-max-age headers
    if (!beresp.expires && beresp.http.cache-control !~ "max-age=") {
        set beresp.ttl = 1h;
        #set beresp.http.Cache-Control = "max-age=3600";
    }

}

À noter que l’on peut définir le TTL par défaut via l’option -t de #varnishd mais on déconseille de le faire pour éviter toute confusion.

vcl_hash / Vary:

L’action return (hash) est le défaut de la sous-routine vcl_recv : cela fait passer le contenu dans la sous-routine vcl_hash qui stocke le contenu renvoyé afin de le resservir si applicable. Par défaut cela indexe le contenu grâce à l’URL et le Host: (pour gérer du multi-domaine) :

sub vcl_hash {
    hash_data(req.url);
    if (req.http.host) {
        hash_data(req.http.host);
    } else {
        hash_data(server.ip);
    }
    return (lookup);
}

Cela n’est pas explicite mais si le serveur HTTP renvoie un entête Vary: (par exemple, Vary: Accept-Encoding ou Vary: User-Agent), alors Varnish va automatiquement utiliser ce paramètre pour indexer le contenu, comme si l’on rajoutait un hash_data() sur les valeurs de Vary:.

Purge du cache

On peut purger le cache en ligne de commande.

Purge en une seule ligne :

# varnishadm 'ban req.url ~ .'

Ou si le port et/ou le chemin vers le secret n’est pas celui par défaut :

# varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082 'ban req.url ~ .'

Il est recommandé d’utiliser le CLI Varnish (on obtiendra ainsi un code de retour 200) :

# varnishadm

varnish> ban req.url ~ .
200

varnish> ban req.url ~ ".png$"
200

varnish> ban req.http.host == www.example.com
200

varnish> ban req.http.host ~ .
200

varnish> ban req.http.host ~ .example.com
200

On peut aussi purger avec une requête HTTP PURGE :

$ curl -X PURGE http://www.example.org/foo.txt

Il faut alors configurer Varnish pour accepter cette requête :

acl local {
    "localhost";
    "192.0.2.12";
}

sub vcl_recv {
    # placer cette directive en premier, sinon une autre risque de matcher avant et la purge ne sera jamais effectuée
    if (req.method == "PURGE") {
        if (client.ip ~ local) {
            return(purge);
        }
    }
}

Taille du cache

Varnish ne permet pas de lister le contenu de son cache. En revanche, pour savoir si la taille du cache est correctement dimensionnée on peut se baser sur certaines valeurs retournées par la commande varnishstat, en particulier MAIN.n_lru_nuked qui est incrémentée à chaque fois qu’un objet est expulsé du cache pour pouvoir en cacher un autre :

# varnishstat -1 -f MAIN.n_lru_nuked

Load-balancing

Si vous avez plusieurs serveurs web, Varnish gère le load-balancing : il permet de mettre plusieurs serveurs en backend et d’y accéder avec du round-robin. On peut également configurer une vérification de chaque backend, en précisant la page qui sert à la vérification et les paramètres (timeout, intervalle, etc.).

Voici un exemple avancé :

backend www00 {
    .host = "192.0.2.6";
    .port = "80";
    .connect_timeout = 1s;
    .first_byte_timeout = 3s;
    .between_bytes_timeout = 2s;
    .max_connections = 50;
    .probe = {
     .request = "GET / HTTP/1.1"
             "Host: www.example.com"
             "User-Agent: test Varnish"
             "Connection: close"
             "Accept-Encoding: text/html" ;
     .timeout = 1000ms;
     .interval = 5s;
     .window = 8;
     .threshold = 6;
   }
}

backend www01 {
    .host = "127.0.0.1";
    .port = "80";
    .probe = {
     .url = "/"
     .timeout = 800ms;
     .interval = 10s;
     .window = 8;
     .threshold = 6;
   }
}

director baz round-robin {
        { .backend = www00; }
        { .backend = www01; }
}

Une fois les backends ou directors définis, il faut les utiliser dans les règles. Par exemple :

sub vcl_recv {
   if (req.http.host ~ "^.*$") {
       set req.backend = baz;
   }

Pour finir, quelques informations sur le load-balancing avec Varnish

  • Gestion d’un poids pour chaque backend ? Oui, depuis Varnish 2.1.4 on peut préciser des poids pour chaque backend.
  • Gestion du maximum de connexions pour un backend ? via le paramètre .max_connections
  • Gestion d’un backend de secours ? via des règles VCL du type if (!req.backend.healthy) { set req.backend = default; }
  • Gestion d’un mode sticky (par IP, URL ou user agent) ? cela se fait en remplaçant round-robin par client dans la définition du director. On peut ensuite définir quel est le paramètre à prendre en compte : sub vcl_recv { set req.backend = baz; set client.identity = req.ip; /* ou client.url ou req.http.user-agent */ }

Si besoin, on pourra aussi utiliser en complément le logiciel http://trac.evolix.net/infogerance/wiki/HowtoHaproxy

Cache ESI

http://www.varnish-cache.org/trac/wiki/ESIfeatures

Grace mode

https://www.varnish-cache.org/docs/6.5/users-guide/vcl-grace.html

Varnish a une “killer feature” : le grace mode. En cas de backend HS, le contenu en cache continuera à être délivré pendant un certain temps même si il est sensé être expiré. Exemple de configuration :

sub vcl_backend_response {
   set beresp.grace = 4h;
}

Note : le saint mode (qui permet de laisser tranquille pendant un temps définir un backend qui aurait une erreur 500) n’existe plus avec Varnish 4.0… une nouvelle implémentation sera disponible en version 4.1 !

Monitoring

Nagios

On peut se servir du retour de la commande varnishadm pour s’assurer du bon état de santé du démon. Ce plugin Nagios utilise ce principe : http://exchange.nagios.org/directory/Plugins/Websites%2C-Forms-and-Transactions/check_varnish_health/details.

Munin

# apt install libxml-parser-perl
# cd /etc/munin/plugins
# ln -s /usr/share/munin/plugins/varnish_ varnish_expunge
# ln -s /usr/share/munin/plugins/varnish_ varnish_hit_rate
# ln -s /usr/share/munin/plugins/varnish_ varnish_memory_usage
# ln -s /usr/share/munin/plugins/varnish_ varnish_objects
# ln -s /usr/share/munin/plugins/varnish_ varnish_request_rate
# ln -s /usr/share/munin/plugins/varnish_ varnish_threads
# ln -s /usr/share/munin/plugins/varnish_ varnish_transfer_rates
# ln -s /usr/share/munin/plugins/varnish_ varnish_uptime

Exemples de règles VCL

Exemple pour Wordpress

Voici un exemple de règles pour un site utilisant WordPress :

backend default {
  .host = "127.0.0.1";
  .port = "80";
}

sub vcl_recv {
    set req.backend = default;

    # Compatiblity with Apache log
    unset req.http.X-Forwarded-For;
    set   req.http.X-Forwarded-For = client.ip;

    # Normalize Content-Encoding
    if (req.http.Accept-Encoding) {
        # Compress a compressed format is silly
        if (req.url ~ "\.(jpg|jpeg|png|gif|gz|tgz|bz2|lzma|tbz|zip|rar)(\?.*|)$") {
            unset req.http.Accept-Encoding;
        }
        # use gzip when possible, otherwise use deflate
        if (req.http.Accept-Encoding ~ "gzip") {
            set req.http.Accept-Encoding = "gzip";
        } elsif (req.http.Accept-Encoding ~ "deflate") {
            set req.http.Accept-Encoding = "deflate";
        } else {
            # unknown algorithm, remove accept-encoding header
            unset req.http.Accept-Encoding;
        }
    }


    # Ignore cookies de Google Analytics
    if (req.http.cookie) {
        set req.http.Cookie = regsuball(req.http.Cookie, "__utm.=[^;]+(; )?", "");
        if (req.http.cookie ~ "^ *$") {
            unset req.http.cookie;
        }
    }

    # Remove cookies and query string for real static files
    if (req.url ~ "^/[^?]+\.(jpeg|jpg|png|gif|ico|js|css|txt|gz|zip|lzma|bz2|tgz|tbz|html|htm)(\?.*|)$") {
        unset req.http.cookie;
    }

    # Remove query string for some files (exclude js and css)
    #if (req.url ~ "^/[^?]+\.(jpeg|jpg|png|gif|ico|txt|gz|zip|lzma|bz2|tgz|tbz|html|htm)(\?.*|)$") {
    #    set req.url = regsub(req.url, "\?.*$", "");
    #}

    # Pas de cache pour l'interface d'administration de WordPress ni les connectes
    if(req.url ~ "^/wp-(login|admin)" || req.http.Cookie ~ "wordpress_logged_in_" ) {
        return(pass);
    }

    # Grace mode
    if (req.backend.healthy) {
        set req.grace = 30s;
    } else {
        set req.grace = 1h;
    }
}

sub vcl_backend_response {
    if (beresp.cacheable) {

        # do not use the header Expires sent by backend but this value instead:
        # we can't use a high value since the cache is not purged
        # (only for dynamic files with a final / or the home page
        # Other files (css...) will use the Expires header
        if (req.url ~ "/$") {
            set beresp.ttl = 60s;
        } elsif (beresp.ttl < 120s) {
            set beresp.ttl = 120s;
        }
    }

    set beresp.grace = 4h;
}

Autre exemple

sub vcl_recv {

    unset req.http.X-Forwarded-For;
    set   req.http.X-Forwarded-For = client.ip;

    # Normalize Content-Encoding
    if (req.http.Accept-Encoding) {
        # Compress a compressed format is silly
        if (req.url ~ "\.(jpg|jpeg|png|gif|gz|tgz|bz2|lzma|tbz|zip|rar)(\?.*|)$") {
            unset req.http.Accept-Encoding;
        }
        # use gzip when possible, otherwise use deflate
        if (req.http.Accept-Encoding ~ "gzip") {
            set req.http.Accept-Encoding = "gzip";
        } elsif (req.http.Accept-Encoding ~ "deflate") {
            set req.http.Accept-Encoding = "deflate";
        } else {
            # unknown algorithm, remove accept-encoding header
            unset req.http.Accept-Encoding;
        }
    }

    if (req.http.cookie) {
        set req.http.Cookie = regsuball(req.http.Cookie, "__utm.=[^;]+(; )?", "");
        if (req.http.cookie ~ "^ *$") {
            unset req.http.cookie;
        }
    }

    if (req.url ~ "^/[^?]+\.(jpeg|jpg|png|gif|ico|js|css|txt|gz|zip|lzma|bz2|tgz|tbz|html|htm)(\?.*|)$") {
        unset req.http.cookie;
    }

    if (req.url ~ "^/[^?]+\.(jpeg|jpg|png|gif|ico|txt|gz|zip|lzma|bz2|tgz|tbz|html|htm)(\?.*|)$") {
        set req.url = regsub(req.url, "\?.*$", "");
    }

  if (req.backend.healthy) {
    set req.grace = 30s;
  } else {
    set req.grace = 4h;
  }
}

sub vcl_backend_response {
    set beresp.ttl = 1800s;
    set beresp.grace = 12h;

    if (beresp.status == 403 || beresp.status == 404 || beresp.status == 503 || beresp.status == 500) {
        set beresp.ttl = 10s;
        set beresp.cacheable = true;
    }
    if (!beresp.cacheable) {
        set beresp.http.X-Cacheable = "NO";
        return (pass);
    } else {
        set beresp.http.cache-control = "max-age=1800";
    }
}

sub vcl_deliver {

   if (resp.http.X-Varnish ~ "[0-9]+ +[0-9]+") {
     set resp.http.X-Cache = "HIT";
   } else {
     set resp.http.X-Cache = "MISS";
   }
}

FAQ

Taille maximum d’un objet en cache ?

La taille maximum d’un objet en cache ne semble limitée que par la taille du cache lui-même. Cela n’est malheureusement pas configurable comme avec Squid.

Taille des cookies

Il est communément admis que les cookies devraient rester sous la barre des 4096 octets (4 ko). Si vous avez beosin de faire transiter des cookies plus importants, il peut devenir nécessaire d’augmenter la valeur de workspace_client. la valeur par défaut est de 64k et il ets préférable qu’elle reste un multiple de 4k. Attention que cette quantité de mémoire est allouée pour chaque requête, donc sur une instance à fort traffic ça peut grimper vite.

Pour augmenter la valeur, il faut ajuster la commande de démarrage de varnishd en ajoutant -p workspace_client=96k par exemple.

Comment prendre en compte un changement de configuration / règles ?

Un reload ne suffit parfois pas, un restart sera nécessaire dans certains cas. De plus, il faut vider du cache les éventuels objets concernés.

Temps d’attente du client HTTP ?

Quand un client HTTP interroge Varnish, il va le mettre en attente afin d’interroger le serveur HTTP final (si l’objet n’est pas caché). Pendant que le serveur HTTP final renvoie l’objet demandé à Varnish, le client HTTP est toujours mis en attente, le contenu lui sera renvoyé seulement une fois l’objet reçu à 100% par Varnish. Cela peut poser différents problèmes : dans le cas d’une grosse vidéo le démarrage sera lent, si le timeout du client HTTP est bas il peut fermer la connexion trop tôt, etc. Pour contourner ce problème, on peut utiliser le return (pipe) :

if (req.url ~ ".mp4") {
    return (pipe);
}

À savoir

Avant Debian 8, Varnish ne supportait pas d’être lancé avec umask 077, c’est corrigé en Debian Jessie. Il faut donc impérativement faire :

# echo "umask 022" >> /etc/default/varnish

Debian 7

https://varnish-cache.org/docs/5.1/whats-new/upgrading-4.0.html

Sous Debian 7 (Wheezy), Varnish 3 a pas mal de différences :

  • il faut forcer l’umask à 022 (voir ci-dessus)
  • la sous-routine vcl_backend_response était appelée vcl_fetch
  • req.requestreq.method
  • vcl_errorvcl_backend_error
  • obj.httpresp.http
  • removeunset
  • pour avoir un entête HTTP X-Cache avec HIT ou MISS, il faut modifier la sous-routine vcl_deliver ainsi :
sub vcl_deliver {

    if (obj.hits > 0) {
        set resp.http.X-Cache = "HIT";
        set resp.http.X-Cache-Hits = obj.hits;
    }
    else {
        set resp.http.X-Cache = "MISS";
    }

     return (deliver);
}

hit_for_pass

Lorsque Varnish interroge un serveur de backend pour une URL donnée, il fait patienter les autres clients qui demandent la même URL… ce qui est optimisé SAUF si l’URL n’est finalement pas cachable ! Une manière de permettre la parallélisation de ces requêtes vers une ressource pas cachable est que Varnish crée une note “hit_for_pass” pour retenir cela. Ça se fait en mettant un TTL et en forçant une requête à ne pas être cachable :

# mark as "hit_for_pass" for 30s
set beresp.ttl = 30s;
set beresp.uncacheable = true;

Voir https://info.varnish-software.com/blog/hit-for-pass-varnish-cache