Configuración de Caché FastCGI en Nginx para PHP
La caché FastCGI de Nginx almacena las respuestas generadas por PHP-FPM directamente en disco, sirviendo páginas dinámicas como si fueran estáticas y reduciendo la carga del servidor de manera drástica. Esta configuración es especialmente efectiva para aplicaciones WordPress y PHP en general, donde un servidor modesto puede manejar miles de peticiones concurrentes sin invocar el intérprete PHP en cada solicitud.
Requisitos Previos
- Ubuntu 20.04/22.04 o CentOS/Rocky Linux 8+
- Nginx 1.18+ instalado y funcionando
- PHP-FPM 7.4+ configurado como backend
- Al menos 1 GB de espacio en disco para la caché
- Acceso root o usuario con privilegios sudo
Configurar la Zona de Caché
La zona de caché se define en el contexto http de Nginx, generalmente en el archivo principal:
# Editar la configuración principal de Nginx
sudo nano /etc/nginx/nginx.conf
# Añadir dentro del bloque http {} (antes de include de sites):
# Definición de la zona de caché FastCGI
# levels=1:2 - estructura de directorios en dos niveles
# keys_zone - nombre y tamaño del almacén de claves en memoria (10MB ~ 80.000 entradas)
# max_size - tamaño máximo total de la caché en disco
# inactive - tiempo para eliminar entradas no usadas
# use_temp_path=off - escribir directamente al destino (evita copia innecesaria)
fastcgi_cache_path /var/cache/nginx/fastcgi_cache
levels=1:2
keys_zone=mi_cache_php:10m
max_size=2g
inactive=60m
use_temp_path=off;
# Crear el directorio de caché y asignar permisos
sudo mkdir -p /var/cache/nginx/fastcgi_cache
sudo chown -R www-data:www-data /var/cache/nginx/fastcgi_cache
# En CentOS/Rocky:
# sudo chown -R nginx:nginx /var/cache/nginx/fastcgi_cache
# Verificar la configuración
sudo nginx -t
Configuración del Virtual Host
Configurar la caché FastCGI en el bloque del servidor PHP:
sudo cat > /etc/nginx/sites-available/mi-app-php << 'EOF'
# Variable para controlar el bypass de caché (0 = usar caché, 1 = saltar caché)
# Se usa en las reglas condicionales de bypass
map $request_method $no_cache_method {
default 0;
POST 1; # Nunca cachear peticiones POST
}
server {
listen 80;
listen 443 ssl http2;
server_name mi-app.com www.mi-app.com;
root /var/www/mi-app;
index index.php index.html;
# Redirigir HTTP a HTTPS
if ($scheme = http) {
return 301 https://$host$request_uri;
}
ssl_certificate /etc/letsencrypt/live/mi-app.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mi-app.com/privkey.pem;
# Archivos estáticos directamente (sin pasar por PHP)
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff|woff2|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# Bloque principal PHP con caché FastCGI
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# ---- CONFIGURACIÓN DE CACHÉ FASTCGI ----
# Zona de caché a usar (definida en nginx.conf)
fastcgi_cache mi_cache_php;
# Clave única para cada entrada de caché
fastcgi_cache_key "$scheme$request_method$host$request_uri";
# Códigos de respuesta HTTP que se cachean
fastcgi_cache_valid 200 301 302 60m;
fastcgi_cache_valid 404 1m;
# Número de peticiones antes de almacenar en caché
fastcgi_cache_min_uses 1;
# Bypass: no usar caché si $skip_cache es 1
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
# Servir caché obsoleta si el backend falla
fastcgi_cache_use_stale error timeout updating
http_500 http_503;
# Bloquear peticiones simultáneas para la misma clave
fastcgi_cache_lock on;
fastcgi_cache_lock_timeout 5s;
# Añadir cabecera informativa del estado de la caché
add_header X-FastCGI-Cache $upstream_cache_status;
}
location / {
try_files $uri $uri/ /index.php?$args;
}
}
EOF
sudo ln -s /etc/nginx/sites-available/mi-app-php /etc/nginx/sites-enabled/
Diseño de Claves de Caché
La clave de caché determina cuándo se reutilizan las entradas almacenadas:
# Clave básica: esquema + método + host + URI
fastcgi_cache_key "$scheme$request_method$host$request_uri";
# Clave con soporte para múltiples idiomas
fastcgi_cache_key "$scheme$request_method$host$request_uri$http_accept_language";
# Clave con soporte para dispositivos móviles
fastcgi_cache_key "$scheme$request_method$host$request_uri$is_mobile";
# Clave para aplicaciones con autenticación por cookie de sesión
# ATENCIÓN: Esto puede crear muchas entradas únicas
# fastcgi_cache_key "$scheme$request_method$host$request_uri$cookie_sessionid";
# Definir variable para detección de móviles
map $http_user_agent $is_mobile {
default 0;
~*mobile 1;
~*android 1;
~*iphone 1;
}
Reglas de Bypass
Definir qué peticiones saltarán la caché:
# En el bloque http o en un archivo de configuración incluido
# Crear variable $skip_cache basada en condiciones
geo $skip_cache_by_ip {
default 0;
# No cachear para IPs de administradores
192.168.1.0/24 1;
}
map $http_cookie $skip_cache_cookies {
default 0;
# Saltar caché para usuarios logueados (WordPress)
~*wordpress_logged_in 1;
# Saltar para usuarios con cookie de carrito WooCommerce
~*woocommerce_items_in_cart 1;
# Saltar para usuarios con sesión PHP activa
~*PHPSESSID 1;
}
map $request_uri $skip_cache_uri {
default 0;
# Nunca cachear el panel de administración
~*/wp-admin/ 1;
~*/wp-login.php 1;
# Nunca cachear el proceso de pago
~*/checkout/ 1;
~*/cart/ 1;
# Nunca cachear rutas de API
~*/api/ 1;
~*/wp-json/ 1;
}
# Variable final que combina todas las reglas de bypass
# Si cualquiera de las variables es "1", se salta la caché
map $request_method $skip_post {
default 0;
POST 1;
}
En el bloque server:
# Combinar condiciones de bypass
set $skip_cache 0;
if ($skip_cache_cookies) { set $skip_cache 1; }
if ($skip_cache_uri) { set $skip_cache 1; }
if ($skip_post) { set $skip_cache 1; }
if ($skip_cache_by_ip) { set $skip_cache 1; }
if ($query_string != "") { set $skip_cache 0; } # Permitir query strings cacheados
Cabeceras de Estado de Caché
Diagnosticar el comportamiento de la caché con cabeceras HTTP:
# Añadir al bloque location ~ \.php$
add_header X-FastCGI-Cache $upstream_cache_status always;
# Valores posibles de $upstream_cache_status:
# HIT - Servido desde caché
# MISS - No estaba en caché, generado por PHP
# BYPASS - Saltado por regla de bypass ($skip_cache = 1)
# EXPIRED - Estaba en caché pero expirado, regenerado
# STALE - Servido desde caché obsoleta (backend falló)
# UPDATING - En caché pero actualizándose
# Verificar estado de caché con curl
curl -I https://mi-app.com/una-pagina | grep X-FastCGI-Cache
# Primera petición: MISS (se genera y almacena)
# Segunda petición: HIT (servida desde caché)
# Script para verificar la tasa de aciertos
cat > /usr/local/bin/check-cache-rate.sh << 'EOF'
#!/bin/bash
# Analizar la tasa de aciertos de la caché FastCGI
LOG_FILE="/var/log/nginx/access.log"
TOTAL=$(wc -l < "$LOG_FILE")
HITS=$(grep '"X-FastCGI-Cache: HIT"' "$LOG_FILE" | wc -l)
MISSES=$(grep '"X-FastCGI-Cache: MISS"' "$LOG_FILE" | wc -l)
BYPASSES=$(grep '"X-FastCGI-Cache: BYPASS"' "$LOG_FILE" | wc -l)
echo "Total peticiones PHP: $TOTAL"
echo "Cache HIT: $HITS ($(( HITS * 100 / (TOTAL + 1) ))%)"
echo "Cache MISS: $MISSES"
echo "Cache BYPASS: $BYPASSES"
EOF
chmod +x /usr/local/bin/check-cache-rate.sh
Purga de Caché
Eliminar entradas específicas de la caché:
# Método 1: Purga simple eliminando archivos de caché
# El nombre del archivo de caché es el MD5 de la clave
# Calcular la ruta de un archivo de caché específico
CACHE_KEY="httpGETmi-app.com/pagina-especifica"
CACHE_FILE=$(echo -n "$CACHE_KEY" | md5sum | awk '{print $1}')
LEVEL1=${CACHE_FILE: -1}
LEVEL2=${CACHE_FILE: -3:2}
echo "/var/cache/nginx/fastcgi_cache/$LEVEL1/$LEVEL2/$CACHE_FILE"
# Borrar toda la caché
sudo find /var/cache/nginx/fastcgi_cache/ -type f -delete
echo "Caché FastCGI eliminada completamente"
# Método 2: Módulo ngx_cache_purge (requiere compilación con el módulo)
# location ~ /purge(/.*) {
# fastcgi_cache_purge mi_cache_php "$scheme$request_method$host$1";
# }
# Uso: curl -X PURGE https://mi-app.com/pagina-a-purgar
# Script de purga selectiva
cat > /usr/local/bin/nginx-cache-purge.sh << 'EOF'
#!/bin/bash
# Purgar caché de una URL específica de Nginx
URL=$1
if [ -z "$URL" ]; then
echo "Uso: $0 <URL>"
echo "Ejemplo: $0 https://mi-app.com/pagina"
exit 1
fi
# Calcular la clave de caché (debe coincidir con fastcgi_cache_key)
CACHE_KEY="httpGET${URL#https://}"
HASH=$(echo -n "$CACHE_KEY" | md5sum | awk '{print $1}')
LEVEL1=${HASH: -1}
LEVEL2=${HASH: -3:2}
CACHE_FILE="/var/cache/nginx/fastcgi_cache/$LEVEL1/$LEVEL2/$HASH"
if [ -f "$CACHE_FILE" ]; then
rm -f "$CACHE_FILE"
echo "Caché eliminada: $URL"
else
echo "No encontrada en caché: $URL"
fi
EOF
chmod +x /usr/local/bin/nginx-cache-purge.sh
Optimización para WordPress
Configuración específica para WordPress con WooCommerce:
# Archivo de configuración FastCGI para WordPress
sudo cat > /etc/nginx/snippets/wordpress-fastcgi-cache.conf << 'EOF'
# Reglas de bypass específicas para WordPress
set $skip_cache 0;
# No cachear si hay cookies de WordPress activas
if ($http_cookie ~* "comment_author_|wordpress_(?!test_cookie)|wp-postpass_") {
set $skip_cache 1;
}
# No cachear el proceso de login, admin y checkout
if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|/wp-(app|cron|login|register|mail).php|wp-.*.php|/feed/|index.php|wp-comments-popup.php|wp-links-opml.php|wp-locations.php|sitemap(_index)?.xml|[a-z0-9_-]+-sitemap([0-9]+)?.xml)") {
set $skip_cache 1;
}
# No cachear peticiones con query strings (excepto paginación simple)
if ($query_string != "") {
set $skip_cache 1;
}
# WooCommerce: no cachear carrito ni proceso de pago
if ($http_cookie ~* "woocommerce_items_in_cart|woocommerce_cart_hash") {
set $skip_cache 1;
}
EOF
# Incluir en el virtual host de WordPress
sudo cat > /etc/nginx/sites-available/wordpress << 'EOF'
server {
listen 443 ssl http2;
server_name mi-wordpress.com;
root /var/www/wordpress;
index index.php;
ssl_certificate /etc/letsencrypt/live/mi-wordpress.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mi-wordpress.com/privkey.pem;
# Incluir reglas de bypass para WordPress
include snippets/wordpress-fastcgi-cache.conf;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_cache mi_cache_php;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_valid 200 1h;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
fastcgi_cache_use_stale error timeout updating http_500 http_503;
add_header X-FastCGI-Cache $upstream_cache_status;
}
}
EOF
sudo nginx -t && sudo systemctl reload nginx
Solución de Problemas
La caché siempre muestra BYPASS:
# Verificar qué condición activa el bypass
# Añadir temporalmente esta línea al bloque PHP:
add_header X-Skip-Cache-Value $skip_cache;
# Revisar las cookies enviadas en la petición
curl -I -b "wordpress_logged_in=test" https://mi-app.com/
La caché no reduce la carga del servidor:
# Verificar que los archivos se están creando
ls -la /var/cache/nginx/fastcgi_cache/
# Verificar permisos del directorio de caché
sudo ls -la /var/cache/nginx/
sudo chown -R www-data:www-data /var/cache/nginx/fastcgi_cache/
Contenido obsoleto en caché:
# Reducir el tiempo de validez o purgar manualmente
sudo find /var/cache/nginx/fastcgi_cache/ -type f -delete
sudo systemctl reload nginx
Conclusión
La caché FastCGI de Nginx es una de las optimizaciones más impactantes para aplicaciones PHP, capaz de reducir la carga del servidor en un 90% o más para sitios con contenido dinámico que no cambia en cada petición. El diseño correcto de las claves de caché y las reglas de bypass es fundamental para garantizar que los usuarios autenticados y las transacciones reciben siempre contenido fresco, mientras que el resto de visitantes se benefician de tiempos de respuesta de milisegundos servidos directamente desde memoria.


