HomelabKubernetes

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:

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:

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:

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.