Apache para WordPress: módulos y reglas que rompen sitios (y cómo solucionarlo)

¿Te fue útil?

La mayoría de las caídas de WordPress en Apache no empiezan con WordPress. Empiezan con “un pequeño cambio en Apache” que parecía inocuo:
una regla de seguridad nueva, un ajuste de compresión, una “limpieza” de redirecciones o un encabezado de caché ingenioso copiado de un blog de 2014.
Entonces tu página principal se redirige a sí misma, el área de administración se convierte en un museo de 403, o las llamadas a la API REST fallan misteriosamente solo los martes.

Esta es una guía de campo para sistemas de producción: qué módulos de Apache y patrones de configuración suelen romper WordPress, cómo demostrar que eso es lo que ocurre,
y cómo arreglarlo sin enloquecer. Si operas WordPress a escala, esto es menos “conceptos básicos de servidor web” y más “detener la hemorragia, y luego prevenirla”.

Guía rápida de diagnóstico

Cuando WordPress “se rompe” en Apache, el camino más rápido es clasificar la falla por síntoma HTTP,
luego confirmar la capa responsable (Apache vs PHP-FPM vs WordPress vs red/CDN).
No adivines. Haz que el servidor te diga la verdad.

Primero: identifica la clase de fallo en 90 segundos

  1. Obtén una respuesta limpia desde el origen (omitiendo el CDN si está presente). Usa curl con verbose y sigue redirecciones.
    Buscas patrones de códigos de estado y cadenas de redirección.
  2. Revisa los registros de errores de Apache para la misma marca de tiempo. Una sola línea suele nombrar el módulo culpable (security2, rewrite, proxy_fcgi).
  3. Revisa los campos del registro de accesos: estado, bytes, tiempo de petición, tiempo upstream (si se registra), user agent.
    Si no registras el tiempo de petición, deberías hacerlo—hoy mismo.

Segundo: decide en cuál de estos cuatro grupos estás

  • Bucle de redirección / esquema incorrecto / host incorrecto: casi siempre reglas de reescritura, cabeceras del proxy o desajuste siteurl/home de WordPress.
  • 403 Forbidden: mod_security, permisos del sistema de archivos o reglas de autorización de Apache (Require, AllowOverride, Options).
  • 500/502/503: manejo de PHP mal configurado (proxy_fcgi), desajuste MPM/PHP, timeouts o límites de memoria.
  • Lento o inestable: KeepAlive, casos límite de HTTP/2, compresión, cabeceras de caché, saturación del backend, I/O de disco o problemas DNS/proxy.

Tercero: confirma con una prueba dirigida

Elige la prueba que colapse la incertidumbre:
deshabilitar un módulo en un vhost de staging, omitir la reescritura para una ruta, ejecutar una sola petición con instrumentación de nivel RewriteLog,
o reproducir una petición contra el backend directamente.

Una cita operativa que vale la pena tatuar en tu runbook, porque se mantiene verdadera bajo presión:
“La esperanza no es una estrategia.” — Gene Kranz

Hechos interesantes y contexto histórico (útil, no trivia)

  • .htaccess existe en gran parte por el hosting compartido: permitió a los usuarios controlar la configuración por directorio sin acceso root. Es conveniente y costoso en tiempo de ejecución.
  • mod_php empujó a Apache hacia prefork: durante años, ejecutar PHP dentro de Apache significó que prefork MPM era la opción segura. Muchos hosts de WordPress aún cargan con ese legado.
  • Los “pretty permalinks” de WordPress son básicamente reglas de reescritura: las reglas canónicas se escribieron primero para mod_rewrite y luego se adaptaron a otros entornos. Apache es la forma de referencia.
  • mod_security se volvió mainstream en los 2000s: es potente, y sus falsos positivos contra wp-admin y XML-RPC en WordPress son legendarios.
  • HTTP/2 en Apache maduró con el tiempo: las implementaciones iniciales encontraron incompatibilidades con ciertos clientes e intermediarios. Hoy está mayormente estable, pero las malas configuraciones aún golpean.
  • El soporte de Brotli llegó después que gzip: mod_brotli es más nuevo, y proxies/caches antiguos pueden comportarse mal si los encabezados Vary no son correctos.
  • La sintaxis de authz de Apache cambió: la era “Order allow,deny” dio paso a “Require all granted.” Mezclar versiones o copiar snippets sin entenderlos causa 403 accidentales.
  • “AllowOverride None” fue el PSA de rendimiento: deshabilitar la búsqueda de .htaccess mejora el rendimiento, pero romperá los permalinks de WordPress a menos que muevas las reglas al vhost.

Los habituales que rompen sitios: módulos y reglas que perjudican a WordPress

1) mod_rewrite: el autor silencioso de tus bucles de redirección

WordPress necesita reglas de reescritura para los pretty permalinks. El problema no es mod_rewrite en sí; son los humanos.
Específicamente: humanos que mezclan redirecciones (301/302), aplicación de host canónico, HTTP→HTTPS y limpieza de slash final
en múltiples capas (Apache + WordPress + CDN + balanceador).

Patrones típicos de rotura:

  • Redirección infinita a la misma URL: dos reglas discrepan sobre esquema o host, o WordPress piensa que corre en HTTP mientras los clientes llegan por HTTPS.
  • El admin redirige a la página principal: WordPress ve un host/esquema distinto que el navegador debido a cabeceras de proxy faltantes.
  • REST API 404: las reglas de reescritura no se aplican a /wp-json porque AllowOverride bloquea .htaccess, o porque una regla lo interrumpe.

Opinión práctica: si tienes acceso root, mantén las reglas de reescritura de WordPress fuera de .htaccess y en el vhost. Menos estadísticas de sistema de archivos, menos sorpresas.
Pero si haces eso, trátalo como código: versiona, prueba y documenta las invariantes (hostnames, esquemas y rutas canónicas).

2) AllowOverride y Options: “funciona en staging” no es una configuración

La causa única más común de “los permalinks de WordPress se rompieron” en Apache no es WordPress. Es Apache ignorando las reglas.
Eso ocurre cuando configuras:

  • AllowOverride None (por lo que .htaccess se ignora)
  • o sobreescribes Options / Require de manera que bloquea el acceso.

La solución “buena” es permitir solo los tipos de override que WordPress necesita (normalmente AllowOverride FileInfo como mínimo),
o mover las reglas de reescritura al vhost y mantener AllowOverride None por rendimiento.

3) mod_security (security2): protección que con frecuencia bloquea el admin

mod_security es un firewall de aplicaciones web. Inspecciona las peticiones y bloquea patrones que parecen maliciosos.
El tráfico de administración de WordPress parece malicioso en un buen día: muchos parámetros, datos serializados, blobs JSON y plugins con query strings “creativos”.

Modos clásicos de fallo:

  • 403 en wp-admin después de instalar un plugin o habilitar un page builder.
  • Solicitudes de REST API bloqueadas, causando fallos del editor por bloques, embeds rotos o errores AJAX.
  • Cierres de sesión aleatorios porque ciertas cookies/cabeceras disparan reglas.

Guía con criterio: no deshabilites mod_security globalmente. Ajústalo por vhost y por ID de regla, y mantén un registro de auditoría que realmente leas.
“Desactivamos el WAF” es una solución a corto plazo que se convierte en un informe de incidente a largo plazo.

4) mod_proxy_fcgi y PHP-FPM: 502s que parecen WordPress pero no lo son

Apache moderno + WordPress a menudo significa que Apache sirve activos estáticos y proxya PHP a PHP-FPM vía proxy_fcgi.
Las malas configuraciones aquí producen:

  • 502 Bad Gateway (FPM caído, permisos del socket, ruta incorrecta, timeout)
  • 503 Service Unavailable (backend saturado, max children alcanzado)
  • 500 intermitentes (crashes, agotamiento de memoria, peticiones lentas que alcanzan timeouts)

5) Elección de MPM (event/worker/prefork): tuning de rendimiento que falla bajo carga

El Módulo de Multiprocesamiento (MPM) de Apache decide cómo maneja conexiones.
WordPress en sí no se preocupa, pero tu integración de PHP y la forma del tráfico sí.

  • prefork: modelo antiguo proceso-por-conexión; funciona con mod_php; consume mucha memoria.
  • worker: hilos; mejor concurrencia; requiere módulos con comportamiento thread-safe.
  • event: mejor propósito general para tráfico con keep-alive; va bien con PHP-FPM.

La rotura viene por desajuste: habilitar event MPM pero seguir cargando mod_php, o fijar MaxRequestWorkers agresivo sin margen de memoria.
WordPress se convierte en chivo expiatorio mientras Apache silenciosamente mata procesos por OOM.

6) HTTP/2 (mod_http2): genial hasta que proxies y encabezados están mal

HTTP/2 suele ayudar a frontends de WordPress multiplexando peticiones. Pero las malas configuraciones causan fallos extraños:

  • Algunos clientes se quedan colgados debido a proxies intermedios o ajustes TLS defectuosos.
  • Aumento súbito de CPU con ciertos suites de cifrado y ajustes de compresión.
  • Push (si se usa) puede poner caches nerviosos y malgastar ancho de banda. La mayoría de los sitios ya no debería usar HTTP/2 server push.

7) Módulos de compresión: mod_deflate vs mod_brotli y la trampa del encabezado Vary

La compresión ahorra ancho de banda. También puede crear cache poison si tu capa de caché no respeta la negociación de contenido.
Si habilitas brotli y gzip, debes tener los encabezados de respuesta correctos, especialmente Vary: Accept-Encoding.

Un error común: habilitar brotli para todo, y luego descubrir que un cache upstream sirve CSS comprimido con brotli a un cliente que no lo soporta.
La página se convierte en una exposición de arte moderno con estilos rotos.

8) Cabeceras de caché y mod_expires: “optimización” que congela tu tema en el tiempo

Quieres tiempos de caché largos para activos estáticos. No quieres tiempos largos para HTML ni para endpoints dinámicos.
Los temas y plugins de WordPress incluyen activos que cambian; si configuras caché agresiva sin nombres versionados, los usuarios conservarán JS antiguo para siempre.

9) mod_headers: cabeceras de seguridad que rompen flujos de login

Las cabeceras de seguridad importan. Pero puedes romper la autenticación de WordPress y los embeds si te pasas:

  • SameSite cookies con configuraciones extrañas pueden romper integraciones de terceros.
  • Content-Security-Policy demasiado estricta rompe el editor por bloques, los embeds y la analítica.
  • X-Frame-Options bloquea usos legítimos de iframe (previsualizaciones, algunos flujos SSO) si no planificas excepciones.

10) DirectoryIndex, MultiViews y otras funcionalidades “pequeñas” que causan rarezas grandes

Características de Apache que parecen no relacionadas con WordPress frecuentemente colisionan con él:

  • MultiViews (negociación de contenido): puede romper permalinks al mapear URLs a archivos inesperados. Si ejecutas WordPress, usualmente quieres desactivarlo.
  • DirectoryIndex mal configurado: puede causar 403/404 donde esperabas el comportamiento del front controller de WordPress.
  • Alias y ProxyPass en colisión: una sola ruta en conflicto puede hacer desaparecer /wp-admin o /wp-json.

Broma #1: Apache puede hacer casi cualquier cosa. Eso también es el problema—tus compañeros de trabajo también pueden.

Tareas prácticas: comandos, salidas y decisiones

A continuación hay tareas concretas que puedes ejecutar en un host Apache. Cada una incluye lo que significa la salida y la decisión que debes tomar.
Estas no son “agradables de tener.” Son el camino más corto de los síntomas a la causa.

Task 1: Confirma el modo real de fallo desde el origen con curl

cr0x@server:~$ curl -svL -o /dev/null https://example.com/ 2>&1 | sed -n '1,25p'
*   Trying 203.0.113.10:443...
* Connected to example.com (203.0.113.10) port 443 (#0)
> GET / HTTP/2
> host: example.com
> user-agent: curl/7.81.0
> accept: */*
< HTTP/2 301
< location: http://example.com/
< server: Apache
* Issue another request to this URL: 'http://example.com/'
> GET / HTTP/1.1
> Host: example.com
< HTTP/1.1 301 Moved Permanently
< Location: https://example.com/

Qué significa: Tienes un bucle de cambio de esquema: HTTPS redirige a HTTP, luego HTTP redirige de nuevo a HTTPS.
Eso suele ser dos fuentes de redirección diferentes peleándose (reescritura de Apache más canonicalización a nivel de aplicación).

Decisión: Encuentra y elimina un lado. Preferible: Apache aplica HTTPS; WordPress debe estar configurado para creer que está detrás de HTTPS (cabeceras del proxy).

Task 2: Ver qué MPM de Apache está activo

cr0x@server:~$ apachectl -V | egrep -i 'server mpm|httpd_root|server_config_file'
Server MPM:     event
 -D HTTPD_ROOT="/etc/apache2"
 -D SERVER_CONFIG_FILE="apache2.conf"

Qué significa: Estás en event MPM. Es bueno para concurrencia, pero no debes usar mod_php.

Decisión: Asegura que PHP se ejecute vía PHP-FPM (proxy_fcgi). Si ves prefork, verifica margen de memoria y conteos de workers.

Task 3: Lista módulos cargados (busca los sospechosos habituales)

cr0x@server:~$ apachectl -M | egrep -i 'rewrite|security2|http2|deflate|brotli|headers|proxy_fcgi|mpm'
 headers_module (shared)
 http2_module (shared)
 proxy_fcgi_module (shared)
 rewrite_module (shared)
 security2_module (shared)
 deflate_module (shared)
 mpm_event_module (shared)

Qué significa: rewrite y security2 están activos, además de HTTP/2 y compresión. Cualquiera de estos puede explicar una regresión.

Decisión: Si tienes 403s: enfócate en security2 y authz. Si tienes redirecciones: enfócate en rewrite y cabeceras proxy. Si tienes colgamientos de clientes: inspecciona http2/TLS.

Task 4: Confirma si .htaccess está siendo respetado

cr0x@server:~$ apachectl -S 2>/dev/null | sed -n '1,40p'
VirtualHost configuration:
*:80                   example.com (/etc/apache2/sites-enabled/example.conf:1)
*:443                  example.com (/etc/apache2/sites-enabled/example-ssl.conf:1)
ServerRoot: "/etc/apache2"
Main DocumentRoot: "/var/www/html"

Esto no muestra AllowOverride directamente, así que revisa el archivo del vhost.

cr0x@server:~$ sudo apachectl -t -D DUMP_INCLUDES | sed -n '1,120p'
Included configuration files:
  (/etc/apache2/apache2.conf:215)
  (/etc/apache2/sites-enabled/example.conf:1)
  (/etc/apache2/sites-enabled/example-ssl.conf:1)

Ahora inspecciona el bloque de directorio:

cr0x@server:~$ sudo sed -n '1,140p' /etc/apache2/sites-enabled/example.conf
<VirtualHost *:80>
  ServerName example.com
  DocumentRoot /var/www/example.com/public

  <Directory /var/www/example.com/public>
    Options FollowSymLinks
    AllowOverride None
    Require all granted
  </Directory>
</VirtualHost>

Qué significa: .htaccess es ignorado. Las reglas de reescritura de WordPress en .htaccess no se aplicarán. Los permalinks mostrarán 404 o se comportarán de forma extraña.

Decisión: O bien establece AllowOverride FileInfo (o All, si te gustan las auditorías), o mueve las reglas de reescritura de WordPress al vhost.

Task 5: Valida que las reglas de reescritura de WordPress existan donde crees

cr0x@server:~$ sudo grep -n "BEGIN WordPress" -n /var/www/example.com/public/.htaccess
2:# BEGIN WordPress

Qué significa: Las reglas existen en .htaccess, pero la Tarea 4 muestra que Apache las ignora.

Decisión: No “re-guardar permalinks” en wp-admin y fingir que se arregló. Arregla Apache primero.

Task 6: Revisa MultiViews (rompe las “URLs bonitas” de maneras divertidas)

cr0x@server:~$ sudo apachectl -t -D DUMP_RUN_CFG | egrep -i 'multiviews|options'

Qué significa: Este volcado no siempre es verboso respecto a Options a nivel de directorio.

Decisión: Busca en tu configuración MultiViews y desactívala para docroots de WordPress.

cr0x@server:~$ sudo grep -RIn "MultiViews" /etc/apache2 | head
/etc/apache2/conf-enabled/negotiation.conf:12:Options +MultiViews

Decisión: Cámbialo a Options -MultiViews en el vhost o bloque de Directory de WordPress.

Task 7: Confirma el cableado del handler de PHP (socket FPM y permisos)

cr0x@server:~$ sudo apachectl -t -D DUMP_MODULES | grep -E 'proxy_fcgi|php'
 proxy_fcgi_module (shared)
cr0x@server:~$ sudo grep -RIn "SetHandler.*proxy:unix" /etc/apache2/sites-enabled/example-ssl.conf
38:  <FilesMatch \.php$>
39:    SetHandler "proxy:unix:/run/php/php8.2-fpm.sock|fcgi://localhost/"
40:  </FilesMatch>
cr0x@server:~$ ls -l /run/php/php8.2-fpm.sock
srw-rw---- 1 www-data www-data 0 Dec 27 09:05 /run/php/php8.2-fpm.sock

Qué significa: El socket existe y es propiedad de www-data. Apache debe ejecutarse como www-data (o estar en ese grupo) para conectar.

Decisión: Si ves “permission denied” en los logs de Apache, arregla la propiedad/grupo del socket o el usuario de Apache, no WordPress.

Task 8: Lee los errores de Apache alrededor de la hora del fallo (no pases por encima)

cr0x@server:~$ sudo tail -n 60 /var/log/apache2/error.log
[Wed Dec 27 09:11:02.331221 2025] [security2:error] [pid 22190] [client 198.51.100.24:51422] [id "949110"] [msg "Inbound Anomaly Score Exceeded"] [uri "/wp-admin/admin-ajax.php"]
[Wed Dec 27 09:11:02.331339 2025] [security2:error] [pid 22190] [client 198.51.100.24:51422] ModSecurity: Access denied with code 403 (phase 2). Matched "Operator `Ge' with parameter `5'"...

Qué significa: mod_security bloqueó un endpoint AJAX de WordPress. El ID de regla es visible (949110 aquí).

Decisión: Crea una excepción dirigida (elimina o modifica esa regla para este vhost/ruta), y verifica que no estés enmascarando un ataque real. Usa los logs de auditoría para validar patrones.

Task 9: Inspecciona el log de auditoría de ModSecurity para contexto completo

cr0x@server:~$ sudo grep -n "949110" /var/log/apache2/modsec_audit.log | tail -n 5
245817:Message: Warning. Pattern match "..." at ARGS:action. [file "/usr/share/modsecurity-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "78"] [id "949110"]

Qué significa: Tienes el archivo de reglas CRS y el número de línea. Eso es accionable.

Decisión: Prefiere exclusiones por ID de regla + localización estrecha (por ejemplo, solo wp-admin/admin-ajax.php) en lugar de deshabilitar globalmente el CRS.

Task 10: Prueba el comportamiento de reescritura con trazas tipo RewriteLog (método Apache 2.4)

Apache 2.4 no usa la antigua directiva RewriteLog. Habilitas trazas por módulo:

cr0x@server:~$ sudo apachectl -t -D DUMP_VHOSTS | sed -n '1,40p'
VirtualHost configuration:
*:80                   example.com (/etc/apache2/sites-enabled/example.conf:1)
*:443                  example.com (/etc/apache2/sites-enabled/example-ssl.conf:1)

Añade temporalmente (para un solo vhost) algo como:
LogLevel warn rewrite:trace3
luego recarga y reproduce. Después, inspecciona los logs:

cr0x@server:~$ sudo tail -n 40 /var/log/apache2/error.log
[rewrite:trace3] [pid 22501] mod_rewrite.c(477): [client 198.51.100.24:52011]  applying pattern '^/(.*)$' to uri '/'
[rewrite:trace3] [pid 22501] mod_rewrite.c(477): [client 198.51.100.24:52011]  rewrite '/' -> '/index.php'

Qué significa: Puedes ver qué patrones coincidieron y cómo se reescribió la URI.

Decisión: Si una regla reescribe inesperadamente a una redirección, elimínala o constriñela. Si las reglas de WordPress nunca se ejecutan, tu contexto de directorio/AllowOverride está mal.

Task 11: Detecta “esquema incorrecto detrás de proxy” comprobando cabeceras de la petición y env de Apache

cr0x@server:~$ curl -s -D - -o /dev/null https://example.com/wp-login.php | egrep -i 'location:|server:|set-cookie:'
server: Apache
location: http://example.com/wp-login.php?redirect_to=...

Qué significa: WordPress está generando redirecciones HTTP mientras el navegador usa HTTPS. Normalmente falta el manejo de X-Forwarded-Proto o la URL del sitio de WordPress está mal.

Decisión: Arregla la propagación de las cabeceras del proxy y dile a Apache/WordPress el esquema original. Revisa también los ajustes siteurl/home de WordPress.

Task 12: Verifica KeepAlive y timeouts (sitios lentos o “fallos aleatorios”)

cr0x@server:~$ sudo apachectl -t -D DUMP_RUN_CFG | egrep -i 'KeepAlive|Timeout|MaxKeepAliveRequests|KeepAliveTimeout'
KeepAlive: On
MaxKeepAliveRequests: 100
KeepAliveTimeout: 5
Timeout: 60

Qué significa: Valores por defecto razonables. Si KeepAliveTimeout es enorme, puedes agotar workers bajo carga.

Decisión: Para sitios WordPress con alta carga, mantén KeepAliveTimeout bajo (usualmente 2–5 segundos) y deja que HTTP/2 haga el trabajo de multiplexado.

Task 13: Revisa la saturación de workers en server-status de Apache (si está habilitado)

cr0x@server:~$ curl -s http://127.0.0.1/server-status?auto | egrep 'BusyWorkers|IdleWorkers|ReqPerSec|CPULoad'
CPULoad: .322
ReqPerSec: 18.2
BusyWorkers: 245
IdleWorkers: 0

Qué significa: No tienes workers inactivos. Las nuevas conexiones se encolarán o fallarán. Esto es un problema de capacidad de Apache, no que WordPress “sea lento”.

Decisión: Aumenta MaxRequestWorkers solo si tienes margen de memoria. Si no, reduce KeepAliveTimeout, arregla llamadas lentas al backend y escala horizontalmente.

Task 14: Revisa la saturación de PHP-FPM (max children alcanzado)

cr0x@server:~$ sudo tail -n 40 /var/log/php8.2-fpm.log
[27-Dec-2025 09:22:14] WARNING: [pool www] server reached pm.max_children setting (40), consider raising it
[27-Dec-2025 09:22:20] NOTICE: [pool www] child 17732 started

Qué significa: FPM está saturado. Apache puede estar bien; los workers PHP no.

Decisión: No subas pm.max_children a ciegas. Mide memoria por proceso PHP, confirma latencia de DB y ajusta según RAM y carga.

Task 15: Confirma que los activos estáticos están cacheados correctamente (y que no lo está el HTML)

cr0x@server:~$ curl -sI https://example.com/wp-content/themes/site/style.css | egrep -i 'cache-control|expires|etag|vary|content-encoding'
cache-control: public, max-age=31536000
etag: "2c1b-5f3c1a4b"
vary: Accept-Encoding
content-encoding: br

Qué significa: Genial para assets versionados. Si tu tema usa nombres de archivo no versionados, esto puede congelar CSS antiguo para los usuarios.

Decisión: Usa URLs de assets versionadas (query string o nombres con hash) si pones max-age largo. No apliques esta caché al HTML.

Task 16: Detecta rápidamente un error de “cache todo” en HTML

cr0x@server:~$ curl -sI https://example.com/ | egrep -i 'cache-control|set-cookie|vary'
cache-control: public, max-age=31536000
set-cookie: wordpress_logged_in=...

Qué significa: Estás cacheando HTML públicamente y además se envían cookies de autenticación. Así se termina mezclando la sesión de un usuario en la experiencia de otro.

Decisión: Arregla la política de caché: el HTML debería ser generalmente private o no-store para rutas con sesión, y controlado cuidadosamente para tráfico anónimo.

Tres micro-historias del mundo corporativo desde la trinchera

Micro-historia 1: El incidente causado por una suposición equivocada

Una empresa mediana migró un sitio de marketing en WordPress detrás de un balanceador de carga que terminaba TLS.
El origen Apache solo hablaba HTTP internamente, lo cual es normal y está bien—si le enseñes a la app cómo es la realidad.
Alguien dijo: “WordPress lo averiguará.” Esa frase debería estar prohibida en el chat de producción.

En minutos tras el corte, los usuarios móviles quedaron atrapados en un bucle de redirecciones. Los de escritorio a veces conseguían entrar.
Las trazas HTTP mostraron un patrón: las peticiones llegaban al balanceador por HTTPS, se reenviaban a Apache como HTTP,
y WordPress generaba redirecciones de vuelta a HTTP porque creía estar ejecutándose en HTTP plano.

El equipo inicialmente persiguió reglas de reescritura. Luego persiguieron HSTS. Luego el CDN.
El avance vino de hacer lo aburrido: capturar una cadena completa de petición/respuesta desde el origen con cabeceras,
y luego comprobar qué cabeceras realmente establecía el balanceador.

El origen no recibía X-Forwarded-Proto: https de forma consistente porque a un listener le faltaba la regla de inyección de cabeceras.
WordPress vio señales mixtas e intentó “arreglar” URLs en ambas direcciones. El bucle no era misterioso; era determinista.

Solución: hacer consistentes las cabeceras proxy, configurar Apache para confiar en el proxy y configurar WordPress para tratar el proto reenviado como autoritativo.
Luego simplificar las reescrituras de Apache: una redirección canónica, una sola. El incidente terminó. El postmortem incluyó una nueva regla:
no migraciones de infraestructura sin una transcripción curl en el ticket.

Micro-historia 2: La optimización que salió mal

Otra organización decidió “mejorar el rendimiento” deshabilitando la búsqueda de .htaccess.
La idea estaba bien: AllowOverride None reduce búsquedas de sistema de archivos y puede eliminar una clase de deriva de configuración.
La ejecución fue el problema: hicieron el cambio un viernes por la tarde sin mover las reglas de reescritura al vhost.

El sitio no se cayó por completo. Hizo algo peor: en su mayoría funcionó, excepto por todo lo que hace que sea un sitio web.
La página principal cargó, pero las entradas del blog daban 404. Las páginas de categoría daban 404. Los endpoints JSON usados por el editor por bloques fallaron.
Los tickets de soporte lo describieron como “aleatorio”. Nada es aleatorio; era enrutamiento por ruta que se estaba desmoronando.

Recuperaron el cambio y lo llamaron “problema de Apache”. No del todo. Fue un problema de gestión de cambios.
Deshabilitar AllowOverride es una migración. Trátala como tal: replica las reglas, prueba permalinks, prueba wp-admin, prueba wp-json,
y solo entonces elimina el soporte .htaccess.

El resultado a largo plazo fue positivo: finalmente movieron reglas al vhost, añadieron tests de integración que hacen curl a un conjunto representativo de URLs,
y eliminaron fragmentos de reescritura de plugins obsoletos en .htaccess. El rendimiento mejoró y la configuración quedó más sensata.
Pero la lección fue simple: las optimizaciones no cuentan si eliminan funcionalidad.

Micro-historia 3: La práctica aburrida pero correcta que salvó el día

Una gran empresa tenía una flota WordPress con una base Apache común: módulos estándar, cabeceras estándar, política TLS estándar.
Cada sitio además tenía sus propios plugins y personal editorial, que es una forma amable de decir “cargas de petición impredecibles.”
Ejecutaban mod_security con CRS, porque cumplimiento lo exigía.

Cada vez que una actualización de plugin disparaba falsos positivos, otros equipos querían deshabilitar el WAF “temporalmente.”
El equipo de plataforma se negó. En cambio tenían un procedimiento: recoger el evento de auditoría de mod_security, identificar el ID de regla,
confirmar que la carga era legítima, escribir una exclusión estrecha para el sitio y endpoint, y adjuntarla a una solicitud de cambio.
Era tedioso. También repetible.

Una mañana de lunes una oleada de peticiones empezó a golpear /wp-login.php con patrones de parámetros extraños.
mod_security bloqueó la mayoría. Un puñado de acciones administrativas legítimas también fueron bloqueadas, y los editores se quejaron rápido.
El equipo usó el flujo establecido: ajustaron una regla para una llamada admin-ajax específica manteniendo las protecciones más amplias.

El sitio se mantuvo en línea, la autenticación quedó protegida y el único “daño” real fue una reunión de 20 minutos para aprobar la excepción de regla.
Nadie escribió una historia heroica sobre ello. Ese es el punto. La práctica aburrida gana a las caídas emocionantes.

Errores comunes: síntoma → causa raíz → solución

Bucle de redirección (ERR_TOO_MANY_REDIRECTS)

Síntoma: El navegador entra en un bucle entre HTTP y HTTPS, o entre www y sin www.

Causa raíz: Canonicalización en conflicto entre reescrituras de Apache, ajustes de WordPress y comportamiento de proxy/CDN.

Solución: Elige una capa para imponer host/esquema canónico (normalmente edge o Apache). Asegura que X-Forwarded-Proto y X-Forwarded-Host sean consistentes, y que siteurl/home de WordPress coincidan con la URL pública.

Permalinks 404, pero la página principal funciona

Síntoma: / carga, /2025/… 404, /wp-json falla.

Causa raíz: .htaccess ignorado por AllowOverride None, o reglas de reescritura faltantes en la configuración del vhost.

Solución: Habilita AllowOverride FileInfo para el directorio docroot o migra las reglas de reescritura de WordPress al vhost.

403 Forbidden en wp-admin o admin-ajax

Síntoma: Las páginas de admin cargan parcialmente; llamadas AJAX fallan; el editor muestra errores; los logs muestran 403.

Causa raíz: mod_security falso positivo o mala configuración de authz de Apache (reglas Require, métodos bloqueados, etc.).

Solución: Revisa el log de errores de Apache por IDs de regla; afina excepciones de mod_security de forma estrecha. Si es authz, corrige permisos de directorio y pon Require all granted donde corresponda.

502 Bad Gateway tras una “pequeña” actualización de PHP

Síntoma: Apache devuelve 502 para todas las páginas PHP; los archivos estáticos siguen sirviéndose.

Causa raíz: proxy_fcgi de Apache sigue apuntando al path antiguo del socket de PHP-FPM, o el servicio FPM no está en ejecución.

Solución: Valida la ruta del socket en SetHandler y el estado del servicio; recarga Apache tras actualizar. Confirma permisos del socket.

Sitio lento, CPU bien, pero usuarios se quejan

Síntoma: Alta latencia, timeouts ocasionales, sin pico evidente de CPU.

Causa raíz: Agotamiento de workers por KeepAliveTimeout largo, saturación del backend (FPM max children), o esperas de I/O de disco por sesiones/subidas de PHP.

Solución: Inspecciona BusyWorkers/IdleWorkers, logs de FPM por max_children, y reduce KeepAliveTimeout. Luego perfila endpoints lentos.

Los cambios de tema/JS no se ven para los usuarios

Síntoma: “He limpiado mi caché” se convierte en la rutina cardio del help desk.

Causa raíz: Cache-Control/Expires agresivos para assets no versionados.

Solución: Versiona assets (nombres con hash o query string). Mantén cache largo solo para assets versionados; cache más corto para archivos dinámicos o que se actualizan con frecuencia.

Errores de REST API, editor por bloques roto, pero el front-end parece bien

Síntoma: El editor no puede guardar, muestra “Publishing failed”, o los embeds fallan.

Causa raíz: mod_security bloquea payloads JSON, las reescrituras no enrutan /wp-json, o encabezados/CORS mal configurados.

Solución: Confirma que /wp-json/ devuelve 200 desde el origen, ajusta mod_security y asegúrate de que las reglas de reescritura/front controller se apliquen a esa ruta.

Broma #2: La frase más peligrosa en ops es “es solo un cambio de cabecera.”

Listas de verificación / plan paso a paso

Lista A: Estabilizar un sitio WordPress roto en Apache (30–60 minutos)

  1. Captura una petición fallida con curl (-svL) y guarda la salida en el ticket de incidente.
  2. Correlaciona la marca de tiempo con el log de errores de Apache y el log de PHP-FPM.
  3. Clasifica como redirección/403/5xx/lento y elige el conjunto de módulos a inspeccionar.
  4. Confirma si .htaccess se respeta (AllowOverride) y si las reglas de reescritura existen donde se espera.
  5. Revisa impactos de mod_security por ID de regla; no adivines.
  6. Valida la ruta del socket de PHP-FPM y permisos; confirma que el servicio esté sano.
  7. Mide saturación de workers (BusyWorkers/IdleWorkers de Apache; advertencias de max_children en FPM).
  8. Realiza un cambio que elimine el modo de fallo primario (por ejemplo, eliminar la redirección en conflicto, añadir una excepción WAF estrecha, corregir la ruta del socket).
  9. Plan de rollback: asegúrate de poder revertir la configuración rápidamente (archivo vhost previo, estado de módulos conocido).
  10. Re-prueba: homepage, un permalink, wp-admin login, admin-ajax, wp-json.

Lista B: Prevenir el próximo outage (la parte que nadie programa)

  1. Estandariza la canonicalización: decide dónde viven las redirecciones (edge vs Apache vs app). Documenta la decisión.
  2. Deja de duplicar reglas entre Apache y WordPress. Debe existir una cadena de redirección canónica, no tres.
  3. Mueve la configuración de reescritura al vhost si puedes, y mantén AllowOverride None para rendimiento y determinismo.
  4. Registra lo que necesitas: tiempo de petición, tiempo upstream, status, user agent, host, X-Forwarded-Proto.
  5. Mantén mod_security, pero gánalo: retención de logs de auditoría, excepciones por ID de regla y un proceso de aprobación sencillo.
  6. Haz pruebas de carga con realismo: incluye llamadas a wp-admin y wp-json, no solo hits anónimos a la página principal.
  7. Limita el radio de blast: configuraciones por vhost, despliegues escalonados y feature flags para módulos riesgosos (HTTP/2, brotli) cuando sea posible.

Lista C: Proceso seguro de cambios en Apache para WordPress

  1. Haz diff de la configuración en un PR (o al menos una solicitud de cambio) y exige una segunda revisión.
  2. Ejecuta apachectl -t antes del reload.
  3. Despliega a un vhost/host canario y ejecuta un suite de pruebas curl scriptado (front page, permalinks, wp-login, wp-admin, wp-json).
  4. Observa logs de errores y tasas 4xx/5xx durante 15–30 minutos.
  5. Despliega gradualmente. Si tu tooling no soporta despliegues graduales, tu tooling es el riesgo.

Preguntas frecuentes

1) ¿Debo usar .htaccess para WordPress en Apache?

Si estás en hosting compartido, probablemente sí. Si operas el servidor, prefiere la configuración en vhost.
.htaccess cuesta rendimiento y crea “config invisible” que tu proceso de despliegue no versiona bien.

2) ¿Cuál es la forma más rápida de saber si las reglas de reescritura son el problema?

Haz curl a un permalink conocido y observa el código de estado. Luego revisa si Apache respeta .htaccess (AllowOverride).
Si los permalinks devuelven 404 y AllowOverride es None, ya está: Apache está ignorando las reglas.

3) ¿Por qué wp-admin funciona pero el editor por bloques falla?

El editor por bloques depende fuertemente de endpoints REST API bajo /wp-json y llamadas AJAX.
mod_security, reescrituras rotas o métodos HTTP bloqueados pueden romper selectivamente esos endpoints mientras las páginas HTML se mantienen intactas.

4) ¿Mod_security vale la pena para WordPress?

Sí, si lo gestionas como un adulto: logs de auditoría, excepciones dirigidas y revisión periódica.
No, si tu modo operativo es “desactivarlo cuando grita.”

5) prefork vs event MPM: ¿qué debo elegir para WordPress?

Si usas PHP-FPM, utiliza event MPM en la mayoría de los casos. Si usas mod_php (no recomendado para setups modernos), estarás atado a prefork.
La respuesta correcta suele ser “event + PHP-FPM”, luego ajusta workers según memoria y tráfico.

6) ¿HTTP/2 puede romper mi sitio WordPress?

Normalmente no, pero puede exponer malas configuraciones: rarezas TLS, proxies defectuosos o comportamiento de caché incorrecto.
Si habilitar HTTP/2 se correlaciona con colgamientos de clientes o cargas parciales, prueba deshabilitar HTTP/2 temporalmente en un vhost y compara.

7) ¿Por qué recibo 403 solo en admin-ajax.php?

admin-ajax suele llevar payloads que parecen ataques a reglas WAF genéricas.
Confirma en los logs de Apache; a menudo verás un ID de regla de mod_security. Excluye de forma estrecha para ese endpoint e ID de regla.

8) Mi CSS/JS se cachea para siempre después de habilitar mod_expires. ¿Cómo lo arreglo sin desactivar la caché?

Mantén tiempos largos de caché solo para assets versionados. Si los nombres de archivo no están versionados, añade versiones (hashes o query strings) en el build/despliegue del tema.
Acorta la caché para assets no versionados. La caché no es enemiga; los assets no versionados sí lo son.

9) WordPress sigue redirigiendo a HTTP aun cuando mi sitio es HTTPS. ¿Es Apache o WordPress?

Casi siempre es “desajuste de la realidad del proxy.” Apache ve HTTP desde el balanceador y pasa esa impresión a PHP.
Arregla el manejo de forwarded proto/host y los ajustes de URL de WordPress, luego elimina las redirecciones duplicadas.

10) ¿Cuál es el conjunto mínimo seguro de módulos de Apache para WordPress?

Típicamente: rewrite, headers, módulos TLS, un handler de PHP (proxy_fcgi), y opcionalmente deflate o brotli.
Añade http2 si tu entorno lo soporta limpiamente. Añade mod_security si puedes operarlo apropiadamente.

Conclusión: qué hacer después (y qué dejar de hacer)

WordPress se lleva la culpa de muchos problemas de Apache porque es lo que ven los usuarios.
En la práctica, los reincidentes son capas de configuración: reglas de reescritura que se pelean entre sí, .htaccess ignorado,
reglas WAF que bloquean tráfico legítimo y “mejoras de rendimiento” no probadas contra endpoints reales.

Pasos siguientes que dan resultado inmediato:

  • Establece por escrito una política de canonicalización (host + esquema) y hazla cumplir en un solo lugar.
  • Decide si eres un equipo que usa .htaccess o uno que usa vhost-config. Elige uno. Úsalo de forma coherente.
  • Habilita los logs que necesitas para depurar rápido (incluyendo tiempo de petición) y haz que la “transcripción curl” sea parte de la higiene de incidentes.
  • Trata las excepciones de mod_security como código: estrechas, revisadas y justificadas.
  • Cuando ajustes Apache, hazlo con mediciones de capacidad (BusyWorkers/Saturación FPM), no con corazonadas.

Deja de hacer esto:

  • Deja de apilar redirecciones en tres capas y luego sorprenderte cuando forman una cinta de Möbius.
  • Deja de desactivar controles de seguridad globalmente porque una actualización de plugin fue conflictiva.
  • Deja de aplicar las mismas cabeceras de caché al HTML que a las imágenes.

El objetivo no es una configuración de Apache ingeniosa. El objetivo es un sitio WordPress que se mantenga en línea, rápido y que falle de formas que puedas diagnosticar antes del almuerzo.

← Anterior
MySQL vs PostgreSQL: límites de memoria en Docker — cómo evitar la estrangulación silenciosa
Siguiente →
Pruebas de CPU en el mundo real: un método sencillo para tu propia carga de trabajo

Deja un comentario