Optimizar WordPress en Kubernetes: Nginx + PHP-FPM + FastCGI Cache + Redis

WordPress en Kubernetes puede ser rápido. Muy rápido. Pero la configuración por defecto no está optimizada para rendimiento. Te cuento cómo he tuneado mi instalación de WordPress en K3s para conseguir un TTFB bajo y tiempos de carga mínimos.
La arquitectura: Nginx + PHP-FPM en sidecar
El primer gran cambio: olvídate de Apache. La imagen oficial wordpress:latest usa Apache con mod_php, que consume más recursos de los necesarios. Nosotros vamos a usar dos contenedores en el mismo pod:
-
nginx:1.27-alpine — Servidor web ultrarrápido y ligero
-
wordpress:php8.3-fpm-alpine — PHP-FPM en Alpine, mínimo consumo
Esta separación nos permite tunear cada componente por separado y añadir capas de caché que Apache no ofrece tan fácilmente.
FastCGI Cache: el secreto del TTFB bajo
La clave para un TTFB bajísimo es el FastCGI cache de Nginx. En lugar de pasar cada petición a PHP, Nginx cachea las respuestas HTML y las sirve directamente:
fastcgi_cache_path /tmp/nginx-cache levels=1:2 keys_zone=WORDPRESS:32m max_size=256m;
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_cache WORDPRESS;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_valid 200 301 302 10m;
fastcgi_cache_use_stale updating error timeout;
add_header X-Cache $upstream_cache_status;
}
Con esto, las páginas se sirven desde la caché de Nginx en milisegundos, sin tocar PHP ni MySQL. El header X-Cache te dice si es HIT (cacheado) o MISS (no cacheado).
PHP-FPM con OPcache JIT
PHP 8.x trae OPcache con JIT (Just-In-Time compilation). Actívalo:
opcache.enable=1
opcache.memory_consumption=128
opcache.max_accelerated_files=10000
opcache.revalidate_freq=60
opcache.jit_buffer_size=32M
opcache.jit=1255
El JIT compila el bytecode de PHP a código máquina, acelerando la ejecución notablemente. Además, añadimos realpath_cache_size=4096K y zlib.output_compression=On para comprimir las respuestas.
Redis para sesiones y caché de objetos
Redis es el compañero perfecto de WordPress. Lo usamos para dos cosas:
-
Sesiones PHP: redirigimos las sesiones a Redis en vez de disco
-
Caché de objetos: con el plugin Redis Object Cache, las consultas a la base de datos se reducen drásticamente
session.save_handler=redis
session.save_path="tcp://redis:6379/0"
# Y en wp-config.php:
define("WP_REDIS_HOST", "redis");
define("WP_REDIS_PORT", 6379);
define("WP_CACHE", true);
define("WP_CACHE_KEY_SALT", "innercys_");
Redis configurado con maxmemory 256mb y política allkeys-lru es suficiente para un blog de tamaño medio.
MySQL tuneado
La base de datos suele ser el cuello de botella. Ajustes clave en mi my.cnf:
innodb_buffer_pool_size=256M
innodb_log_file_size=64M
innodb_flush_method=O_DIRECT
innodb_flush_log_at_trx_commit=2
max_connections=50
table_open_cache=400
Nota importante: En MySQL 8.0 eliminaron query_cache_type. Si vienes de versiones anteriores, asegúrate de quitarlo de tu configuración o MySQL no arrancará.
Cabeceras de caché para assets
Los archivos estáticos (CSS, JS, imágenes) deben cachearse en el navegador del usuario:
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|webp|avif)$ {
expires 365d;
add_header Cache-Control "public, immutable, max-age=31536000";
}
location ~* /wp-content/uploads/ {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
}
Con immutable le decimos al navegador que nunca tiene que volver a pedir ese archivo. Los usuarios que ya han visitado el blog cargarán las páginas casi instantáneamente.
Compresión Gzip
Nginx comprime las respuestas antes de enviarlas. Probé Brotli pero la imagen nginx:alpine no lo incluye por defecto, así que mejor usar Gzip:
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5;
gzip_min_length 256;
gzip_types text/plain text/css text/javascript
application/json image/svg+xml ...
Seguridad adicional
Aprovechamos para blindar WordPress:
-
✅ XML-RPC bloqueado (
return 403) — evita ataques de fuerza bruta -
✅ PHP denegado en uploads, wp-includes y wp-admin
-
✅ Headers de seguridad: X-Frame-Options, XSS-Protection, Referrer-Policy
-
✅ Archivos sensibles (.git, .env, .sql, .htaccess) devuelven 404
-
✅
DISALLOW_FILE_EDITactivado en wp-config -
✅ Solo 5 revisiones por post (
WP_POST_REVISIONS=5)
Resultados
Con esta configuración, WordPress responde en milisegundos. Las páginas cacheadas se sirven directamente desde Nginx sin tocar PHP ni MySQL. Y las no cacheadas se benefician del OPcache JIT y Redis.
El consumo de recursos también es menor: Nginx usa ~64MB RAM, PHP-FPM ~512MB, Redis ~256MB y MySQL ~512MB. Todo cabe perfectamente en un clúster de Raspberry Pis.