Configuraciones específicas / ejemplos



Contenido de la caché


Eliminar cookies/encoding/auth y cachear ficheros estáticos

Algunos de los contenidos más susceptibles de ser cacheados son los ficheros estáticos, para lo cual tenemos que asegurarnos de que el cliente no manda cookies, autenticación ni Encoding:

    if (req.url ~ "\.(gif|jpg|swf|css|js|png|jpg|jpeg|gif|png|tiff|tif|
                      pdf|zip|gz|rar|arj|svg|swf|ico|doc|ppt|pps|xls|
                      odc|odb|odf|odg|odi|odp|ods|odt|sxc|sxd|sxi|sxw)$") 
    {
        unset req.http.Cookie;
        unset req.http.Authorization;
        unset req.http.Accept-Encoding;
        set req.backend = nginx;
        return(lookup);
    }


Establecer un período de gracia a los datos de caché

En caso de que el backend no responda cuando tratamos de renovar en caché un objeto, podemos indicarle a Varnish que sirva objetos expirados durante un tiempo determinado (grace).

Esta circunstancia ocurre en 2 casos:

  • El backend/director está caído y no responde.
  • Un hilo diferente de Varnish ha realizado una petición igual al backend y todavía no ha terminado.
sub vcl_recv 
{
    set req.grace = 5m;
    # (...)
}


sub vcl_fetch
{
    set obj.grace = 5m;
    # (...)
}

Si queremos contemplar ambas posibilidades por separado, podemos hacerlo con la siguiente lógica VCL:

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


sub vcl_fetch 
{
    set beresp.grace = 1h;
}


Evitar que los robots cacheen contenidos antiguos

sub vcl_miss 
{
    # Si el user-agent indica que es un "robot" tipo Google
    if (req.http.user-agent ~ ”spider”) 
    {
        # No continuar con el proceso de cacheo: devolver error
        error 503 ”Not presently in cache”;
    }
}


Cachear contenido específico de usuarios/sesiones

Manipulando la función que genera el hash (por defecto, la URL) de forma que se añada un determinado elemento propio y personalizado de cada usuario autenticado de la web podemos forzar a que se cachee contenido propio para ese usuario.

Por ejemplo, para cachear la página de perfil de cada usuario aprovechando las cookies, podemos:

sub vcl_hash
{
    if( req.url ~ "^/profile$" && req.http.Cookie )
    {
        set req.hast += req.http.cookie;
    }
}

Podemos incluso hacerlo no sólo para /profile/, sino para todo la web:

sub vcl_hash 
{
    # Cacheamos la web para cada usuario: ojo, mucho uso de caché
    if (req.http.Cookie) 
    {
        set req.hash += req.http.Cookie;
    }
}


Cachear versión específica para dispositivos móviles

Podemos también manipular vcl_hash() para almacenar objetos de caché diferentes en el caso de usuarios de dispositivos móviles, por lo que tendremos cacheada tanto la versión "de escritorio" como la versión "para móviles":

sub vcl_hash 
{
    # Cacheamos la web también para Android, iPod, iPad e iPhone por separado.
    if (req.http.host == "www.domain.org" && 
        req.http.User-Agent ~ ("iPhone|iPad|iPod|Android)") 
    {
        set req.hash += "mobile";
    }
}


No cachear páginas o dominios específicas

sub recv
{

    # Excluir paginas concretas O un dominio concreto:
    if (req.url ~ "(cron|upload|mailing)\.php$" ||
        req.http.host ~ "domain2.org")
    {
        return(pass);
    }

    # Excluir paginas concretas DE un dominio concreto:
    if (req.http.host ~ "domain3.org") &&
        req.url ~ "blah\.php$")
        
    {
        return(pass);
    }

}


No cachear ni inspeccionar ficheros grandes

Conviene que hagamos un pipe de los ficheros especialmente grandes (vídeo, audio, etc) de forma que se transfieran "directamente" desde el backend HTTP al cliente sin que Varnish tenga que "inspeccionarlos" ni cachearlos:

    # Ficheros grandes, directamente a nginx:
    if (req.url ~ "\.(mp3|mp4|m4a|ogg|mov|avi|wmv)$" && req.url !~ "audio/download") 
    {
        set req.backend = nginx;
        return(pipe);
    }

    # Ficheros de audio no estáticos, "escupidos" por un .php (drupal):
    if (req.url ~ "audio/play" || req.url ~ "audio/download") 
    {
        set req.backend = apache;
        return(pipe);
    }


Soporte para Pragma: nocache

Un servidor HTTP 1.0 podría enviar a Varnish la cabecera "Pragma: nocache", que es actualmente ignorada por Varnish. Podemos implementar soporte para esta cabecera con lógica VCL:

sub vcl_fetch
{
    #(...)
    
    if (beresp.http.Pragma ~ "nocache") 
    {
        pass;
    }
}


Cabecera Cache-Control

Las cabeceras Cache-Control informan a las cachés cómo deben manejar el contenido al que referencian. Varnish hace uso del parámetro max-age de esta cabecera y lo utiliza para calcular el TTL de un objeto.

Al igual que en el caso de "Pragma: nocache", la cabecera "Cache-Control: nocache" es ignorada por Varnish pero es fácil implementar soporte para ella.

    if( obj.http.Pragma ~ "no-cache" ||
        obj.http.Cache-Control ~ "no-cache" ||
        obj.http.Cache-Control ~ "private") 
    {
        return(pass);
    }

Es importante asegurarse de que nuestro servidor envía una cabecera Cache-Control con "max-age".


Establecer un valor correcto para beresp.ttl

Antes de que Varnish ejecute vcl_fetch(), la variable beresp.ttl tiene ya un valor inicial. Varnish utilizará el primer valor que encuentre de los siguientes:


  • Cabecera Cache-Control → campo "s-maxage".
  • Cabecera Cache-Control → campo "max-age".
  • Cabecera Expires de la respuesta.
  • Valor de "default_ttl" (por defecto 120s a menos que lo modifiquemos en la configuración de arranque de Varnish).


Como el valor se establece antes de entrar en vcl_fetch(), podemos alterar Cache-Control sin afectar a beresp.ttl, y viceversa.

Hay que tener en cuenta que sólo los códigos 200, 203, 300, 301, 302, 307, 410 y 404 son cacheados por defecto. Se pueden cachear otros códigos de error poniendo beresp.ttl a un valor > 0 en vcl_fetch().

En el caso de múltiples niveles de caché, hay que tener cuidado con la cabecera "Age", ya que puede provocar que si el servidor Web manda una cabecera Cache-Control y no eliminamos "Age" en la respuesta, Varnish podría enviar una cabecera Age que exceda el max-age, haciendo que los navegadores no cacheen el contenido.



Reglas para software de terceros


Wordpress MU

Soluciona un problema de los temas de Wordpress MU con el cacheo de estáticos:

sub vcl_recv
{
    // ...
    
    # Ubicar antes de las reglas de lookup de contenidos estáticos.
    if (req.http.host == "domain.org" && 
        req.url ~ "/blogs/(.*)/wp-content/themes") 
    {
        return (pass);
    }
}

(Gracias a Evan Donovan).


Moodle

sub vcl_recv
{
    // ...
    
    // Cachear los ficheros de temas de Moodle:
    if (req.url ~ "/pix/.*\.gif$") 
    {
        return (lookup);
    }

    // Pero no cachear a moodle en sí mismo:
    if (req.http.Cookie ~ "(MoodleSession|MoodleSessionTest)") 
    {
        return (pass);
    }
    
    if (req.http.host == "www.domain.edu" && req.url ~ "^/courses") 
    {
        return (pass);
    }
    
    if (req.url ~ "file.php") 
    {
        return (pass);
    }
    
}

(Gracias a Evan Donovan y gchaix ).



Control de acceso


Bloquear acceso a /server-status/ excepto para sysadmins

sub vcl_recv
{
    if( req.url ~ "^/server-status" &&
        !(client.ip ~ acl_ips_sysadmins))
    {
        error 404 "Not Found";
    }
}



Reescrituras y redirecciones


Reescribir subdominio como URL del dominio principal

A continuación se muestra un ejemplo que reescribe la URL blog.dominio.com como dominio.com/blog, guardando una copia de las cabeceras originales.

Nótese que el código no sólo reescribe blog.dominio.com como dominio.com/blog sino que los contenidos también se ven afectados por la reescritura (blog.dominio.com/pagina.php se reescribe como dominio.com/pagina.php):

sub vcl_recv 
{
    # Hacemos una copia de las cabeceras originales
    set req.http.x-host = req.http.host;
    set req.http.x-url = req.url;

    # Normalizamos www.dominio a dominio:
    set req.http.host = regsub(req.http.host, "^www\.", "");

    # Reescribimos subdominio como /subdominio:

    # Opcion 1, por asignacion y sustitucion:
    if (req.http.host == "blog.dominio.com") 
    {
        set req.http.host = "dominio.com";
        set req.url = regsub(req.url, "^", "/blog");
    }

    # Opcion 2: con sustituciones de regexps (matchea mas subdominios):
    #if (req.http.host ~ "^blog\.") 
    #{
    #    set req.http.host = regsub(req.http.host,"^blog\.", "");
    #    set req.url = regsub(req.url, "^", "/blog");
    #}
}


Realizar redirecciones en vcl_error

Es posible redirigir a los usuarios (realizar reescritura de direcciones permanentes) usando vcl_error(), devolviendo al usuario final un error 301 (Moved permanently):

sub vcl_recv 
{
    if (req.http.host == "www.example.com") 
    {
        set req.http.Location = "http://example.com" + req.url;
        error 750 "Permanently moved";
    }
}


sub vcl_error 
{
    if (obj.status == 750) 
    {
        set obj.http.location = req.http.Location;
        set obj.status = 301;
        return (deliver);
    }
}


Realizar un redirect sin que lo realice el cliente HTTP

Podemos utilizar return(restart) para realizar un redirect sin tener que esperar a que la petición la realice de nuevo el cliente:

sub vcl_fetch 
{
    # Con req.restarts == 0 evitamos bucle infinito:
    if (req.restarts == 0 &&
        req.request == "GET" &&
        beresp.status == 301) 
    {
        set beresp.http.location = regsub(beresp.http.location,"^http://","");
        set req.http.host = regsub(beresp.http.location,"/.*$","");
        set req.url = regsub(beresp.http.location,"[^/]*","");
        return (restart);
    }
}



Logs, estadísticas, errores y troubleshooting


Utilizar curl para estudiar las cabeceras devueltas

Podemos utilizar curl con el flag -I para estudiar las cabeceras que devuelve una petición HTTP. La sintaxis básica es:

# curl -I -H "Host: host-header" URL

Por ejemplo:

$ curl -I -H "Host: www.sromero.org" http://www.sromero.org/wiki/
HTTP/1.1 200 OK
Date: Wed, 02 May 2012 12:37:25 GMT
Server: Apache/2.2.21 (Unix) mod_ssl/2.2.21 OpenSSL/0.9.8e-fips-rhel5 
        mod_auth_passthrough/2.1 mod_bwlimited/1.4 FrontPage/5.0.2.2635
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Set-Cookie: DokuWiki=95138d04c6fc70674f21704a0d63f00d; path=/wiki/; HttpOnly
Set-Cookie: DWcb100c0f811c1b6d691e3e7c406b56ba=deleted; expires=Tue, 
            03-May-2011 12:37:25 GMT; path=/wiki/; httponly
Vary: Accept-Encoding,User-Agent
Content-Type: text/html; charset=utf-8

Es importante destacar que también podemos forzar un User-Agent concreto con el flag -A (por ejemplo si queremos acceder a versiones "mobile" de las webs). Por ejemplo, para aparentar que accedemos desde un iPad:

# Partido para facilitar la lectura, pegar en una sola linea:
$ curl -I -H "Host: www.sromero.org" http://www.sromero.org -A 
    "Mozilla/5.0(iPad; U; iPhone OS 3_2 like Mac OS X; en-us)<espacio>
    AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4<espacio>
    Mobile/7B314 Safari/531.21.10"

Atacar a un servidor Varnish con curl tiene la ventaja de que podremos ver las cabeceras que establece Varnish (X-Cache-*):

$ curl -I -H "Host: www.wikipedia.org" http://www.wikipedia.org
HTTP/1.0 200 OK
Date: Wed, 02 May 2012 12:41:33 GMT
Server: Apache
X-Content-Type-Options: nosniff
Cache-Control: s-maxage=3600, must-revalidate, max-age=0
Last-Modified: Sat, 14 Apr 2012 00:41:17 GMT
Vary: Accept-Encoding
Content-Length: 48305
Content-Type: text/html; charset=utf-8
X-Cache: MISS from sq63.wikimedia.org
X-Cache-Lookup: HIT from sq63.wikimedia.org:3128
X-Cache: MISS from amssq38.esams.wikimedia.org
X-Cache-Lookup: HIT from amssq38.esams.wikimedia.org:3128
Age: 4
X-Cache: HIT from amssq38.esams.wikimedia.org
X-Cache-Lookup: HIT from amssq38.esams.wikimedia.org:80
Connection: close

En la documentación oficial de Varnish utilizan, en lugar de curl, la herramienta GET de libwww-perl:

# yum/apt-get install libwww-perl
# GET -H 'Host: www.dominio.com' -Used http://www.dominio.com/


Información sobre la vida de los objetos en Varnish

Varnish añade a las respuestas HTTP una cabecera llamada Age que indica cuánto tiempo ha mantenido el objeto. Podemos estudiar estas cabeceras con Varnishlog mediante:

varnishlog -i TxHeader -I ^Age


Definir el tiempo de cacheo para hit_for_pass

Los objetos de tipo "hit_for_pass" se cachearán durante el TTL por defecto (120s) de Varnish. Si queremos alterar esta lógica, podemos hacerlo en vcl_fetch():

sub vcl_fetch 
{
    if (!obj.cacheable) 
    {
        # Limitar el tiempo de vida de los hit_for_pass a 30 segundos
        obj.ttl = 30s;
        return(hit_for_pass);
    }
}


Personalizar la salida de error de Varnish

En caso de error, Varnish utiliza para presentar la página de error al cliente HTTP su función vcl_error() por defecto, que es la siguiente:

sub vcl_error {
    set obj.http.Content-Type = "text/html; charset=utf-8";

    synthetic {"
        <?xml version="1.0" encoding="utf-8"?>
        <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
            "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
        <html>
            <head>
                <title>"} obj.status " " obj.response {"</title>
            </head>
            <body>
                <h1>Error "} obj.status " " obj.response {"</h1>
                <p>"} obj.response {"</p>
                <h3>Guru Meditation:</h3>
                <p>XID: "} req.xid {"</p>
                <address><a href="http://www.varnish-cache.org/">Varnish</a></address>
            </body>
        </html>
    "};
    return(deliver);
}

Podemos personalizar la salida de error en función del código con "markups" personalizados para 404, 500, etc:

sub vcl_error {
    set obj.http.Content-Type = "text/html; charset=utf-8";
    if (obj.status == 404) {
        synthetic {"
            <!-- Markup for the 404 page goes here -->
        "};
    } else if (obj.status == 500) {
        synthetic {"
            <!-- Markup for the 500 page goes here -->
        "};
    } else {
        synthetic {"
            <!-- Markup for a generic error page goes here -->
        "};
    }
}


Añadir cabeceras de HIT y MISS

Podemos modificar la función vcl_deliver() para añadir múltiples cabeceras informativas, como por ejemplo indicar si se ha producido un HIT o un MISS en la caché:

sub vcl_deliver
{
    # Eliminamos cabecera Age pero guardamos una copia:
    set resp.http.X-Age = resp.http.Age;
    unset resp.http.Age;
    
    if (obj.hits > 0)
    {
        set resp.http.X-Cache = "HIT";
    }
    else
    {
        set resp.http.X-Cache = "MISS";
    }
}



<Volver a Página de VARNISH>