Caché de Proxy Nginx para Reverse Proxy
La caché de proxy de Nginx almacena las respuestas de los servidores upstream (backends) para servirlas directamente sin reenviar cada petición, reduciendo la latencia y la carga en los servidores de aplicación. A diferencia de la caché FastCGI, esta configuración funciona con cualquier backend HTTP: Node.js, Python, Java, Go o cualquier servicio web, convirtiéndola en una herramienta universal de aceleración para arquitecturas de microservicios y APIs en Linux.
Requisitos Previos
- Ubuntu 20.04/22.04 o CentOS/Rocky Linux 8+
- Nginx 1.18+ instalado como reverse proxy
- Espacio en disco para la caché (recomendado SSD)
- Backend HTTP funcionando (Node.js, Python, etc.)
- Acceso root o usuario con privilegios sudo
Configurar la Zona de Caché
La zona de caché se define en el contexto http de Nginx:
sudo nano /etc/nginx/nginx.conf
Añadir dentro del bloque http {}:
# ================================================================
# Configuración de caché de proxy Nginx
# ================================================================
# Zona de caché principal para el reverse proxy
# levels=1:2 → estructura de directorios en dos niveles (mejor rendimiento)
# keys_zone=nombre:tamaño → 1MB almacena ~8000 claves de caché
# max_size → límite total en disco
# inactive → eliminar si no se accede en este tiempo
# use_temp_path=off → escribir directamente, sin archivo temporal intermedio
proxy_cache_path /var/cache/nginx/proxy_cache
levels=1:2
keys_zone=cache_backend:20m
max_size=5g
inactive=1h
use_temp_path=off;
# Zona separada para APIs (con configuración diferente)
proxy_cache_path /var/cache/nginx/api_cache
levels=1:2
keys_zone=cache_api:5m
max_size=500m
inactive=10m
use_temp_path=off;
# Variable global para controlar el bypass
map $http_cache_control $bypass_proxy_cache {
default 0;
no-cache 1;
no-store 1;
}
# Crear directorios de caché con permisos correctos
sudo mkdir -p /var/cache/nginx/proxy_cache
sudo mkdir -p /var/cache/nginx/api_cache
sudo chown -R www-data:www-data /var/cache/nginx/
# En CentOS/Rocky: sudo chown -R nginx:nginx /var/cache/nginx/
sudo nginx -t
Configuración del Reverse Proxy
Configurar el virtual host con caché habilitada:
sudo cat > /etc/nginx/sites-available/mi-app-cached << 'EOF'
# Definición del grupo de backends
upstream app_backend {
# Servidor de aplicación principal
server 127.0.0.1:3000;
# Servidor adicional para balanceo de carga
server 127.0.0.1:3001 backup;
# Mantener conexiones persistentes al backend
keepalive 32;
}
server {
listen 443 ssl http2;
server_name mi-app.com www.mi-app.com;
ssl_certificate /etc/letsencrypt/live/mi-app.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mi-app.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
# ---- CONFIGURACIÓN GLOBAL DE CACHÉ ----
# Activar la zona de caché a usar
proxy_cache cache_backend;
# Clave de caché (define qué peticiones se consideran iguales)
proxy_cache_key "$scheme$proxy_host$request_uri";
# Cuánto tiempo cachear respuestas con código de estado específico
proxy_cache_valid 200 301 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_valid any 1m;
# Añadir cabecera para diagnosticar el estado de la caché
add_header X-Proxy-Cache $upstream_cache_status always;
# Número mínimo de peticiones antes de cachear
proxy_cache_min_uses 2;
# Métodos que se pueden cachear (GET y HEAD por defecto)
# proxy_cache_methods GET HEAD;
# ---- PETICIONES AL BACKEND ----
location / {
proxy_pass http://app_backend;
# Cabeceras hacia el backend
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Timeouts del proxy
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Usar conexiones keepalive al backend
proxy_http_version 1.1;
proxy_set_header Connection "";
}
# ---- ARCHIVOS ESTÁTICOS (sin proxy al backend) ----
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2|svg)$ {
root /var/www/mi-app/public;
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# ---- API: caché con TTL corto ----
location /api/ {
proxy_pass http://app_backend;
proxy_set_header Host $host;
# Usar zona de caché dedicada para APIs
proxy_cache cache_api;
proxy_cache_valid 200 30s; # Solo 30 segundos para datos dinámicos
# Solo cachear GET de la API
proxy_cache_methods GET;
# No cachear si el cliente envía Authorization
proxy_cache_bypass $http_authorization;
proxy_no_cache $http_authorization;
add_header X-Proxy-Cache $upstream_cache_status always;
}
}
server {
listen 80;
server_name mi-app.com www.mi-app.com;
return 301 https://$server_name$request_uri;
}
EOF
sudo ln -s /etc/nginx/sites-available/mi-app-cached /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Diseño de Claves de Caché
La clave determina qué peticiones reutilizan la misma entrada:
# Clave básica: esquema + host + URI completa
proxy_cache_key "$scheme$proxy_host$request_uri";
# Clave con soporte para versiones de API
proxy_cache_key "$scheme$proxy_host$request_uri$http_x_api_version";
# Clave para sitios multilingüe
proxy_cache_key "$scheme$proxy_host$request_uri$http_accept_language";
# Clave separada para usuarios móviles (contenido diferente)
map $http_user_agent $device_type {
default "desktop";
~*mobile "mobile";
~*tablet "tablet";
}
# Incluir tipo de dispositivo en la clave
proxy_cache_key "$scheme$proxy_host$request_uri$device_type";
# Clave con query string (cuidado: puede generar muchas entradas)
# proxy_cache_key "$scheme$proxy_host$uri?$args";
# Mejor: normalizar la query string antes de cachear
Cabeceras Cache-Control
Respetar y enviar cabeceras de control de caché:
# En el bloque location o server:
# Respetar las cabeceras Cache-Control del backend
proxy_cache_bypass $http_pragma $http_authorization;
# No ignorar las cabeceras del backend (comportamiento por defecto)
# proxy_ignore_headers Cache-Control Expires; # Solo activar si es necesario
# Configurar Vary para respuestas que dependen de cabeceras del cliente
proxy_cache_key "$scheme$proxy_host$request_uri$http_accept_encoding";
# Convertir cabeceras X-Accel-Expires del backend a control de caché
# El backend puede enviar: X-Accel-Expires: 300 (segundos)
# Nginx respetará ese valor automáticamente
Ejemplo de respuesta correcta desde el backend:
# Verificar las cabeceras de caché de una respuesta
curl -I https://mi-app.com/pagina-cacheada
# Ejemplo de respuesta con estado de caché:
# HTTP/2 200
# X-Proxy-Cache: HIT ← Servido desde caché
# Cache-Control: max-age=600 ← El backend indicó 10 minutos
# Age: 245 ← Lleva 245 segundos en caché
# Segunda petición inmediata:
# X-Proxy-Cache: HIT
# Primera petición (caché fría):
# X-Proxy-Cache: MISS
Servir Contenido Obsoleto
Mejorar la resiliencia sirviendo caché cuando el backend falla:
# Servir caché obsoleta cuando el backend tiene problemas
# Muy útil para mantenimiento o fallos temporales del backend
proxy_cache_use_stale
error
timeout
updating
http_500
http_502
http_503
http_504;
# Tiempo máximo que se puede servir caché obsoleta
proxy_cache_background_update on;
proxy_cache_revalidate on;
# En caso de fallo del backend, servir caché aunque esté expirada
proxy_cache_lock on;
proxy_cache_lock_timeout 5s;
proxy_cache_lock_age 200ms;
Configuración completa con stale para producción:
sudo cat > /etc/nginx/snippets/proxy-cache-resilient.conf << 'EOF'
# Configuración de caché resiliente para reverse proxy en producción
proxy_cache cache_backend;
proxy_cache_key "$scheme$proxy_host$request_uri";
proxy_cache_valid 200 301 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_min_uses 1;
# Servir contenido obsoleto hasta 1 hora si el backend falla
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
proxy_cache_revalidate on;
# Evitar que múltiples peticiones simultáneas lleguen al backend para la misma clave
proxy_cache_lock on;
proxy_cache_lock_timeout 5s;
# Cabecera de estado de caché para diagnóstico
add_header X-Proxy-Cache $upstream_cache_status always;
add_header X-Cache-Date $upstream_http_date always;
EOF
Purga de Entradas
Eliminar entradas específicas de la caché:
# Método 1: Limpiar toda la caché
sudo find /var/cache/nginx/proxy_cache/ -type f -delete
sudo nginx -s reload
# Método 2: Purga selectiva usando el módulo cache_purge
# Requiere Nginx compilado con ngx_cache_purge
# Añadir en el bloque server para activar la purga:
# location ~ /purge(/.*) {
# # Solo permitir purgas desde redes internas
# allow 127.0.0.1;
# allow 10.0.0.0/8;
# deny all;
# proxy_cache_purge cache_backend "$scheme$proxy_host$1";
# }
# Uso de purga:
# curl -X PURGE https://mi-app.com/pagina-a-limpiar
# Método 3: Script de purga por patrón
cat > /usr/local/bin/nginx-purge-cache.sh << 'EOF'
#!/bin/bash
# Purgar caché de Nginx por patrón de URL
CACHE_DIR="/var/cache/nginx/proxy_cache"
PATTERN=$1
if [ -z "$PATTERN" ]; then
echo "Uso: $0 <patron>"
echo "Ejemplo: $0 'api/productos'"
exit 1
fi
# Buscar y eliminar archivos que contengan el patrón en su contenido
sudo find "$CACHE_DIR" -type f -exec grep -l "$PATTERN" {} \; -delete
echo "Caché purgada para el patrón: $PATTERN"
EOF
chmod +x /usr/local/bin/nginx-purge-cache.sh
Optimización de Cache Lock
Prevenir el "thundering herd" en caché vacía:
# En el bloque server o location:
# Solo una petición al backend cuando la caché está vacía/expirada
proxy_cache_lock on;
# Tiempo máximo para esperar el lock
proxy_cache_lock_timeout 5s;
# Tiempo después del cual se permite una segunda petición si la primera no responde
proxy_cache_lock_age 200ms;
# Configuración de buffer para respuestas del backend
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;
# No almacenar en caché si la respuesta es muy pequeña (posible error)
# proxy_cache_min_uses también ayuda: esperar N peticiones antes de cachear
proxy_cache_min_uses 1;
Monitorizar la caché con Nginx status:
# Habilitar el módulo stub_status para monitorización
sudo cat > /etc/nginx/sites-available/nginx-status << 'EOF'
server {
listen 127.0.0.1:8080;
server_name localhost;
# Estado básico de Nginx
location /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
}
EOF
sudo ln -s /etc/nginx/sites-available/nginx-status /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
# Ver estadísticas
curl http://127.0.0.1:8080/nginx_status
# Script de monitorización de la tasa de aciertos
cat > /usr/local/bin/nginx-cache-stats.sh << 'EOF'
#!/bin/bash
# Estadísticas de la caché de proxy Nginx
LOG_FILE="/var/log/nginx/access.log"
# Contar por estado de caché
echo "=== Estadísticas de Caché Nginx ==="
echo ""
for STATUS in HIT MISS BYPASS EXPIRED STALE; do
COUNT=$(grep "X-Proxy-Cache: $STATUS" "$LOG_FILE" 2>/dev/null | wc -l)
echo "$STATUS: $COUNT"
done
TOTAL=$(wc -l < "$LOG_FILE")
HITS=$(grep "X-Proxy-Cache: HIT" "$LOG_FILE" 2>/dev/null | wc -l)
if [ "$TOTAL" -gt 0 ]; then
HIT_RATE=$((HITS * 100 / TOTAL))
echo ""
echo "Tasa de aciertos: ${HIT_RATE}%"
fi
EOF
chmod +x /usr/local/bin/nginx-cache-stats.sh
Solución de Problemas
La caché siempre devuelve MISS:
# Verificar que el directorio de caché existe y tiene permisos
ls -la /var/cache/nginx/proxy_cache/
sudo chown -R www-data:www-data /var/cache/nginx/
# Verificar que la zona de caché está definida en nginx.conf
sudo nginx -T | grep proxy_cache_path
# El backend puede estar enviando cabeceras que previenen el cacheo
curl -I https://mi-app.com/ | grep -E "Cache-Control|Set-Cookie|Pragma"
# Set-Cookie y "Cache-Control: no-store" previenen el cacheo
La caché no se invalida cuando el contenido cambia:
# Reducir el TTL de la caché para contenido que cambia frecuentemente
proxy_cache_valid 200 30s;
# O implementar purga activa desde la aplicación
# Cuando se publica contenido nuevo, purgar la URL correspondiente
Alto uso de disco por la caché:
# Ver tamaño actual de la caché
du -sh /var/cache/nginx/proxy_cache/
# Reducir max_size en la definición de la zona
# O reducir el tiempo inactive para eliminar entradas no usadas más rápido
# Ver qué archivos ocupan más espacio
find /var/cache/nginx/proxy_cache/ -type f -exec ls -s {} \; | sort -rn | head -20
Conclusión
La caché de proxy de Nginx es una solución versátil que puede acelerar cualquier backend HTTP independientemente del lenguaje de programación, reduciendo dramáticamente la carga en los servidores de aplicación y mejorando los tiempos de respuesta para los usuarios finales. La combinación de claves de caché bien diseñadas, políticas de stale para resiliencia ante fallos del backend, y purga activa cuando el contenido cambia, proporciona una capa de caché robusta y eficiente para arquitecturas de reverse proxy en producción.


