La caché del lado del servidor evita repetir trabajo caro (renderizar una página, consultar la base de datos) en cada petición. Nginx, Varnish, Redis y Memcached resuelven ese problema en capas distintas: las dos primeras guardan respuestas HTTP completas delante de la aplicación, y las dos últimas guardan datos que la aplicación pide de forma explícita. En esta guía verás qué hace cada una, un ejemplo funcional de cada una en Ubuntu 24.04 y cómo decidir cuál usar.

Requisitos previos

  • Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath.
  • Un usuario no root con privilegios sudo.
  • Una aplicación web escuchando en local para las pruebas de caché HTTP. En los ejemplos se supone que responde en http://127.0.0.1:3000.

Comparación rápida

Caché de NginxVarnishRedisMemcached
Qué guardaRespuestas HTTPRespuestas HTTPDatos de la aplicación (claves con tipos)Datos de la aplicación (clave-valor)
Dónde viveDisco (con índice en RAM)RAMRAM, persistencia opcionalSolo RAM
Cambios en la aplicaciónNingunoNinguno (reglas en VCL)Hay que programar la lectura y escrituraHay que programar la lectura y escritura
InvalidaciónPor TTL; purga con módulo externo o borrando ficherosTTL, ban y purge muy flexiblesDEL, EXPIRE, TTL por clavedelete, TTL por clave
TLSSíNo (necesita un terminador delante)OpcionalNo
Ideal paraSitios con Nginx ya delante, WordPress, APIs públicasMucho tráfico HTML con reglas de caché complejasResultados de consultas, sesiones, contadores, colasCaché de objetos simple y distribuida

Las cachés HTTP son transparentes para la aplicación pero solo sirven para respuestas iguales para todos los usuarios. Las cachés de datos funcionan también en páginas personalizadas, a cambio de tocar el código.

Caché de Nginx (proxy_cache y fastcgi_cache)

Si Nginx ya hace de proxy inverso, su caché es la opción con menos piezas nuevas. proxy_cache sirve para backends HTTP (Node.js, Python, Go) y fastcgi_cache para PHP-FPM; la configuración es equivalente.

Define la zona de caché en el contexto http:

sudo nano /etc/nginx/conf.d/cache.conf
proxy_cache_path /var/cache/nginx/app levels=1:2 keys_zone=app_cache:10m
                 max_size=1g inactive=60m use_temp_path=off;

keys_zone=app_cache:10m reserva 10 MB de RAM para el índice (unas 80.000 claves), max_size limita el disco e inactive borra lo que nadie pide en 60 minutos. Nginx crea el directorio por sí mismo.

Usa la zona en tu sitio, saltando la caché para peticiones con cookie de sesión:

sudo nano /etc/nginx/sites-available/your_domain
server {
    listen 80;
    server_name your_domain;

    location / {
        proxy_cache app_cache;
        proxy_cache_key "$scheme$host$request_uri";
        proxy_cache_valid 200 301 10m;
        proxy_cache_valid 404 1m;
        proxy_cache_use_stale error timeout updating http_500 http_502 http_503;
        proxy_cache_lock on;

        # No cachear ni servir de caché a usuarios con sesión
        proxy_cache_bypass $cookie_sessionid;
        proxy_no_cache $cookie_sessionid;

        add_header X-Cache-Status $upstream_cache_status always;
        proxy_set_header Host $host;
        proxy_pass http://127.0.0.1:3000;
    }
}

Cambia sessionid por el nombre real de la cookie de sesión de tu aplicación. proxy_cache_use_stale sirve la copia antigua si el backend falla, y proxy_cache_lock evita que diez peticiones simultáneas a la misma URL lleguen las diez al backend.

sudo ln -s /etc/nginx/sites-available/your_domain /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Comprueba el comportamiento pidiendo dos veces la misma URL:

curl -s -o /dev/null -D - http://your_domain/ | grep X-Cache-Status
curl -s -o /dev/null -D - http://your_domain/ | grep X-Cache-Status
X-Cache-Status: MISS
X-Cache-Status: HIT

Para medir la tasa de aciertos, añade el estado de la caché al log. En /etc/nginx/nginx.conf, dentro de http:

log_format cache '$remote_addr [$time_local] "$request" $status $upstream_cache_status';

Y en el bloque server: access_log /var/log/nginx/your_domain-cache.log cache;. Tras recargar, cuenta los estados:

awk '{print $NF}' /var/log/nginx/your_domain-cache.log | sort | uniq -c | sort -rn
   8421 HIT
    612 MISS
    233 BYPASS
     41 EXPIRED

Nginx de código abierto no incluye un comando de purga por URL. Para vaciar toda la caché borra el contenido del directorio: sudo find /var/cache/nginx/app -type f -delete. Si necesitas invalidar URLs concretas a menudo, Varnish es mejor opción.

Varnish

Varnish es un proxy de caché HTTP en memoria con un lenguaje de configuración propio (VCL). Encaja cuando tienes mucho tráfico y necesitas reglas finas: limpiar cookies de analítica, TTL distintos por ruta o invalidar grupos de URLs al publicar contenido. No termina TLS, así que en producción se coloca detrás de Nginx o HAProxy.

sudo apt install varnish

En Ubuntu 24.04 Varnish escucha en el puerto 6081 y lee /etc/varnish/default.vcl. Edita ese fichero:

sudo nano /etc/varnish/default.vcl
vcl 4.1;

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

acl purgers {
    "127.0.0.1";
}

sub vcl_recv {
    if (req.method == "PURGE") {
        if (client.ip !~ purgers) {
            return (synth(405, "Not allowed"));
        }
        return (purge);
    }

    # Quitar cookies de analítica que no afectan al contenido
    if (req.http.Cookie) {
        set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(_ga[^=]*|_gid)=[^;]*", "");
        set req.http.Cookie = regsub(req.http.Cookie, "^;\s*", "");
        if (req.http.Cookie == "") {
            unset req.http.Cookie;
        }
    }
}

sub vcl_backend_response {
    # Servir contenido caducado hasta 1 hora si el backend cae
    set beresp.grace = 1h;
}

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

Varnish no cachea por defecto peticiones con cookies ni respuestas con Set-Cookie, por eso se eliminan las cookies que no importan. El TTL sale de las cabeceras Cache-Control del backend, o 120 segundos si no las hay.

sudo systemctl restart varnish
curl -s -o /dev/null -D - http://127.0.0.1:6081/ | grep -E 'X-Cache|Age'
curl -s -o /dev/null -D - http://127.0.0.1:6081/ | grep -E 'X-Cache|Age'
X-Cache: MISS
Age: 0
X-Cache: HIT
Age: 3

Invalidar es donde Varnish destaca. Una URL concreta con PURGE desde el propio servidor, o todas las que cumplan una expresión con ban:

curl -X PURGE http://127.0.0.1:6081/blog/mi-articulo
sudo varnishadm 'ban req.url ~ ^/blog/'

Para ver la tasa de aciertos:

varnishstat -1 -f MAIN.cache_hit -f MAIN.cache_miss

Para cambiar el puerto o el tamaño de memoria (256 MB por defecto) crea un override con sudo systemctl edit varnish y redefine ExecStart.

Redis como caché de aplicación

Redis guarda datos en memoria con tipos (cadenas, hashes, listas, conjuntos) y TTL por clave. Se usa para cachear resultados de consultas, fragmentos de HTML, sesiones o límites de peticiones. A diferencia de las cachés HTTP, funciona también con contenido personalizado, pero la aplicación decide qué guarda y cuándo lo invalida.

sudo apt install redis-server

Configúralo como caché pura: límite de memoria, expulsión de las claves menos usadas y sin volcados a disco. Edita /etc/redis/redis.conf y ajusta estas directivas (ya existen comentadas en el fichero):

sudo nano /etc/redis/redis.conf
maxmemory 256mb
maxmemory-policy allkeys-lru
save ""
appendonly no
sudo systemctl restart redis-server
redis-cli CONFIG GET maxmemory-policy
1) "maxmemory-policy"
2) "allkeys-lru"

Sin maxmemory, Redis crece hasta agotar la RAM del servidor. Con allkeys-lru, al llegar al límite descarta las claves menos usadas en lugar de rechazar escrituras.

El patrón más habitual es cache-aside: la aplicación busca en Redis, y si no está, consulta la fuente y guarda el resultado con un TTL. Un ejemplo en Python:

sudo apt install python3-redis
nano ~/cache_aside.py
import json
import time

import redis

r = redis.Redis(host="127.0.0.1", port=6379, decode_responses=True)


def load_product_from_db(product_id):
    time.sleep(0.5)  # simula una consulta lenta
    return {"id": product_id, "name": "Producto de ejemplo", "price": 19.9}


def get_product(product_id, ttl=300):
    key = f"product:{product_id}"
    cached = r.get(key)
    if cached is not None:
        return json.loads(cached), "HIT"
    product = load_product_from_db(product_id)
    r.set(key, json.dumps(product), ex=ttl)
    return product, "MISS"


def update_product(product_id, data):
    # guardar en la base de datos y después invalidar
    r.delete(f"product:{product_id}")


if __name__ == "__main__":
    for _ in range(2):
        start = time.perf_counter()
        _, status = get_product(42)
        print(f"{status} en {(time.perf_counter() - start) * 1000:.1f} ms")
python3 ~/cache_aside.py
MISS en 502.3 ms
HIT en 0.4 ms

Comprueba la clave y el tiempo que le queda:

redis-cli TTL product:42
(integer) 297

Memcached

Memcached es un almacén clave-valor en memoria, multihilo y sin persistencia ni tipos de datos. Hace una sola cosa y consume muy pocos recursos. Tiene sentido si tu framework ya lo soporta (PHP, Django) o necesitas repartir una caché simple entre varios nodos; si no, Redis cubre lo mismo y más.

sudo apt install memcached

La configuración está en /etc/memcached.conf, una opción por línea. Ajusta la memoria (64 MB por defecto) y comprueba que solo escucha en local:

sudo nano /etc/memcached.conf
-m 256
-p 11211
-u memcache
-l 127.0.0.1
sudo systemctl restart memcached
printf 'stats\r\nquit\r\n' | nc 127.0.0.1 11211 | grep -E 'limit_maxbytes|get_hits|get_misses'
STAT get_hits 0
STAT get_misses 0
STAT limit_maxbytes 268435456

Patrones de invalidación

La parte difícil de cualquier caché es saber cuándo borrar. Estos son los patrones que funcionan en la práctica:

  • Solo TTL. El dato caduca solo. Es lo más sencillo y válido cuando se tolera ver datos con unos minutos de antigüedad (listados, contadores públicos).
  • Borrar al escribir. Al actualizar un registro, la aplicación borra su clave (DEL product:42). La siguiente lectura la regenera. Mantén también un TTL como red de seguridad.
  • Claves con versión. Guarda un número de versión (INCR catalog:version) e inclúyelo en las claves (catalog:v7:page:1). Al subir la versión, todas las claves antiguas quedan huérfanas y caducan por TTL, sin tener que buscarlas.
  • Invalidación por patrón en caché HTTP. En Varnish, ban con una expresión regular sobre la URL al publicar contenido.

Evita borrar con comodines en Redis (KEYS product:* seguido de DEL): KEYS bloquea el servidor mientras recorre todas las claves. Si lo necesitas, usa claves con versión o redis-cli --scan --pattern.

TTL orientativos

Tipo de datoTTL orientativo
Configuración y datos de referencia1 a 24 horas
Páginas públicas y listados1 a 10 minutos
Fichas de producto o artículos10 a 60 minutos, con borrado al editar
Respuestas de APIs externas (divisas, clima)Lo que indique su límite de uso, por ejemplo 1 hora
Stock, precios en vivo5 a 30 segundos, o no cachear
Estáticos con hash en el nombre1 año, Cache-Control: immutable

Un TTL corto con un volumen de tráfico alto sigue ahorrando mucho: 10 segundos de caché en una página que recibe 100 peticiones por segundo evitan 999 de cada 1.000 llamadas al backend.

Qué opción elegir

  • Ya usas Nginx y quieres cachear páginas públicas sin tocar código: caché de Nginx.
  • Mucho tráfico HTML, muchas cookies de terceros o necesitas purgar URLs al publicar: Varnish detrás de Nginx (Nginx para TLS, Varnish para caché).
  • La página es personalizada pero hay consultas caras que se repiten: Redis con cache-aside.
  • Tu framework ya trae soporte de Memcached y solo necesitas clave-valor: Memcached. En un proyecto nuevo, elige Redis.

Lo habitual en producción es combinar una caché HTTP para visitantes anónimos con Redis para los datos de la aplicación. Antes de añadir una capa, mide: si la tasa de aciertos está por debajo del 50 %, revisa las claves y las cabeceras antes de sumar más memoria.

Conclusión

Has visto las cuatro cachés más usadas en Linux, cada una configurada y verificada en Ubuntu 24.04. Como siguientes pasos, define cabeceras Cache-Control correctas en tu aplicación para que Nginx o Varnish sepan qué pueden guardar, monitoriza la tasa de aciertos y el uso de memoria de Redis con redis-cli INFO stats, y protege Redis con contraseña o ACL si otros servidores van a conectarse.