Modificaste .htaccess para “solo añadir una redirección” o “endurecer la seguridad”, actualizaste la página y ahora producción devuelve 500s, 403s o un bucle de redirección que consume CPU como un calefactor. El teléfono comienza a sonar. Marketing dice que “es solo un blog”. Mientras tanto, también es tu checkout, tu documentación, tu SEO y tu fin de semana.
Esta es la forma sensata de salir: restaurar una línea base conocida y buena, demostrar qué falló y poner salvaguardas para que la próxima edición no se convierta en un incidente. Lo haremos como un SRE que tuvo que explicar a la dirección por qué “un pequeño ajuste de configuración” derribó una vía de ingresos.
Qué hace realmente .htaccess (y por qué rompe tan dramáticamente)
.htaccess es configuración por directorio para Apache HTTP Server. Si la configuración de Apache lo permite (AllowOverride), Apache lee .htaccess en cada petición a ese directorio y sus subdirectorios. Ese es el punto: puedes hacer cambios sin tocar la configuración global ni recargar Apache. También es la trampa: puedes romper el enrutamiento, los permisos y la seguridad al instante, y Apache aplicará la lógica rota en cada petición.
WordPress se apoya en mod_rewrite para que los “permalinks bonitos” funcionen. El bloque de reescritura predeterminado de WordPress es pequeño y aburrido. Exactamente lo que quieres en producción. Cuando un plugin, una guía de “endurecimiento” o un compañero bienintencionado comienza a apilar directivas—redirecciones, reglas deny, flags de PHP, pistas de caché—puedes crear interacciones que no son obvias hasta que van a producción: bucles de reescritura, amplificación de peticiones, assets bloqueados, admin bloqueado y 500s súbitos.
Una cosa más: .htaccess se evalúa en el contexto de un directorio, no de todo el sitio. Eso significa que una regla que “parece correcta” en la raíz puede comportarse distinto en /wp-admin/ o /wp-content/. Las reglas de coincidencia de rutas son sutiles. Los incidentes en producción no son sutiles.
Broma corta #1: Editar .htaccess en vivo es como hacer cirugía con un cuchillo de mantequilla—técnicamente posible, pero vas a tener mucho que explicar después.
Datos y contexto interesantes (para que el comportamiento tenga sentido)
- .htaccess existe por el hosting compartido. En los primeros días del hosting barato, los usuarios necesitaban control limitado sin acceso root. Las anulaciones por directorio fueron el compromiso.
- Apache lee .htaccess en tiempo de petición. Esa comodidad cuesta rendimiento; el servidor debe comprobar el archivo (y directorios padres) repetidamente a menos que se configure lo contrario.
- Los permalinks de WordPress se volvieron “la expectativa por defecto.” A medida que los blogs evolucionaron a CMS, las URLs legibles dejaron de ser opcionales y pasaron a ser un requisito básico.
- El orden de reglas de mod_rewrite es una trampa común. La primera regla que coincide puede cortar el resto, y un pequeño cambio de flag (
L,R,QSA) puede invertir el comportamiento por completo. - 403 vs 404 suele ser política, no existencia. Un 403 de Apache puede ser causado por permisos de sistema de archivos, reglas de acceso o módulos de seguridad—no por contenido inexistente.
- Muchos “snippets de seguridad” están obsoletos. Publicaciones copiadas por años aún recomiendan directivas que entran en conflicto con el comportamiento moderno de WordPress o con setups de PHP-FPM.
- Nginx no usa .htaccess. Cuando los equipos migran de Apache a Nginx, a menudo siguen editando
.htaccessy se preguntan por qué nada cambia. - AllowOverride es una decisión del plano de control. Habilitarlo en todas partes es conveniente operativamente y hostil en términos de seguridad; deshabilitarlo por completo es seguro pero molesto. La mayoría de sistemas reales eligen un punto medio.
Guion de diagnóstico rápido
Este es el orden que encuentra el cuello de botella rápido, no el orden que parece educado.
1) Identificar la clase de falla: 500, 403, 404, bucle de redirección o página en blanco
- 500: Apache no pudo procesar la petición. Piensa en directiva inválida, módulo faltante, problema de permisos bajo módulo de seguridad o fallos del manejador PHP expuestos por reescritura.
- 403: Acceso denegado. Piensa en permisos de sistema de archivos, reglas
Require/Denyo WAF/módulo de seguridad. - 404: Enrutamiento. Piensa en reescritura que no se dispara,
RewriteBaseincorrecto, DocumentRoot equivocado o WordPress sin recibir la petición. - 301/302 bucle: Conflicto de lógica de reescritura o redirección. A menudo canonicalización de esquema/host + plugin + cabeceras de proxy.
- Página en blanco: Puede ser un fatal de PHP con display errors desactivado; aún puede provocarse por reescritura que enruta a PHP.
2) Mira primero el registro de errores del servidor web, no WordPress
Si .htaccess está roto, Apache a menudo te dirá exactamente qué directiva y por qué. Si empiezas en WordPress, estás depurando la capa equivocada.
3) Confirma que Apache siquiera está respetando .htaccess
Si AllowOverride None está establecido para esa ruta, tus cambios no se aplicarán y podrías perseguir fantasmas. Por el contrario, si las anulaciones están permitidas, una línea mala puede tumbar el vhost de inmediato.
4) Revertir a una línea base conocida y buena, luego reintroducir cambios
En producción, quieres una restauración rápida del servicio. La causa raíz puede seguir después. Pero hazlo limpiamente: respalda el archivo actual, restaura el predeterminado, valida los registros y luego añade cambios uno a la vez.
Restaurar un .htaccess predeterminado seguro de WordPress (correctamente)
Hay dos “valores predeterminados seguros” que vale la pena discutir:
- Bloque de reescritura predeterminado de WordPress (lo mínimo requerido para permalinks en Apache con
mod_rewrite). - Una línea base operativa segura: reescritura predeterminada más un pequeño conjunto de reglas de seguridad no controvertidas que no romperán admin, la REST API o las subidas.
Base #1: Bloque de reescritura predeterminado de WordPress
Esto es lo que WordPress escribe cuando “Guardar enlaces permanentes” en el admin. Es intencionalmente mínimo.
cr0x@server:~$ cat /var/www/example.com/public_html/.htaccess
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
Sí, esa línea HTTP_AUTHORIZATION importa para algunas configuraciones y plugins. Si la borras, puedes romper peticiones autenticadas a través de ciertos proxies o manejadores PHP. Mantenla a menos que tengas una razón para no hacerlo.
Base #2: Reescritura por defecto más endurecimiento operativo seguro
El endurecimiento requiere mesura. Si no puedes explicar exactamente qué peticiones bloquea y por qué, no pertenece a producción. Un conjunto conservador incluye: prevenir listado de directorios, bloquear acceso a archivos sensibles y añadir un par de cabeceras que no rompan WordPress.
Mantén el comportamiento de reescritura idéntico y añade solo reglas de bajo riesgo. Evita bloques regex avanzados en cadenas de consulta hasta haberlos probado con tus plugins y flujos de trabajo de admin.
cr0x@server:~$ cat /var/www/example.com/public_html/.htaccess
# Basic hygiene
Options -Indexes
<FilesMatch "^(\.env|composer\.(json|lock)|wp-config\.php|readme\.html|license\.txt)$">
Require all denied
</FilesMatch>
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
# Safe headers (avoid breaking admin)
<IfModule mod_headers.c>
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
</IfModule>
Fíjate en lo que falta: directivas de caché agresivas, bloquear xmlrpc.php a ciegas, reglas complicadas de hotlink y “deny all en wp-admin excepto mi IP.” Esos no son valores predeterminados. Son decisiones de proyecto.
Tareas prácticas: comandos, significado de la salida y decisiones
Quieres tareas repetibles que funcionen bajo presión. Aquí están las que yo realmente ejecuto. Cada una incluye (a) un comando, (b) qué significa la salida y (c) la decisión que tomas a partir de ella.
Task 1: Confirmar el servidor web y su configuración activa
cr0x@server:~$ ps -eo pid,comm,args | egrep 'apache2|httpd|nginx' | head
1123 apache2 /usr/sbin/apache2 -k start
1450 apache2 /usr/sbin/apache2 -k start
Significado: Apache está corriendo (apache2). Si solo ves nginx, deja de editar .htaccess; no está en la ruta de la petición.
Decisión: Si Apache no es la capa de servicio, localiza el ingreso real (load balancer, reverse proxy, Nginx) y corrige el enrutamiento ahí.
Task 2: Capturar el síntoma HTTP exacto desde el servidor mismo
cr0x@server:~$ curl -sS -D- -o /dev/null http://127.0.0.1/ | sed -n '1,12p'
HTTP/1.1 500 Internal Server Error
Date: Sat, 27 Dec 2025 12:10:13 GMT
Server: Apache/2.4.57 (Ubuntu)
Content-Type: text/html; charset=iso-8859-1
Significado: Es un 500 real en el servidor web, no un problema en el edge de CDN.
Decisión: Ve directamente a los registros de Apache y a la validación de la configuración antes de tocar WordPress.
Task 3: Seguir (tail) los registros de errores de Apache durante una petición
cr0x@server:~$ sudo tail -n 50 /var/log/apache2/error.log
[Sat Dec 27 12:10:13.492112 2025] [core:alert] [pid 1123] [client 127.0.0.1:39912] /var/www/example.com/public_html/.htaccess: Invalid command 'RewriteEngine', perhaps misspelled or defined by a module not included in the server configuration
Significado: mod_rewrite no está cargado, o Apache no reconoce las directivas de reescritura en este contexto.
Decisión: Habilitar mod_rewrite (en Debian/Ubuntu) y recargar Apache, o arreglar la imagen del contenedor/paquetes.
Task 4: Validar disponibilidad de módulos de Apache (mod_rewrite, mod_headers)
cr0x@server:~$ apache2ctl -M | egrep 'rewrite|headers'
headers_module (shared)
Significado: headers está presente; rewrite no. Eso coincide con el error del log.
Decisión: Habilitar rewrite, luego volver a probar. Sin él, los permalinks de WordPress no funcionarán como se espera.
Task 5: Habilitar mod_rewrite y recargar Apache (Debian/Ubuntu)
cr0x@server:~$ sudo a2enmod rewrite
Enabling module rewrite.
To activate the new configuration, you need to run:
systemctl restart apache2
Significado: El módulo ahora está habilitado en la configuración de Apache.
Decisión: Reinicia/recarga Apache de forma controlada. Si estás en HA, drena primero un nodo.
cr0x@server:~$ sudo systemctl reload apache2
Significado: La recarga tuvo éxito (es común no obtener salida). Si falla, systemctl status apache2 te dirá.
Decisión: Ejecuta inmediatamente curl y vuelve a seguir los logs para confirmar que la clase de error cambió.
Task 6: Comprobar si Apache está autorizado a respetar .htaccess (AllowOverride)
cr0x@server:~$ sudo apache2ctl -S 2>&1 | sed -n '1,20p'
VirtualHost configuration:
*:80 example.com (/etc/apache2/sites-enabled/example.com.conf:1)
ServerRoot: "/etc/apache2"
Main DocumentRoot: "/var/www/html"
Significado: Tienes la ruta del archivo vhost. A continuación, inspecciona el bloque de directorio para AllowOverride.
Decisión: Si AllowOverride None, entonces las reglas de .htaccess se ignoran; corrige en el nivel del vhost o cambia AllowOverride solo para este directorio.
cr0x@server:~$ sudo sed -n '1,200p' /etc/apache2/sites-enabled/example.com.conf
<VirtualHost *:80>
ServerName example.com
DocumentRoot /var/www/example.com/public_html
<Directory /var/www/example.com/public_html>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
Significado: Las anulaciones están permitidas. Así que .htaccess está en juego, para bien o para mal.
Decisión: Si el sitio está roto tras una edición, revertir .htaccess tendrá un impacto inmediato.
Task 7: Hacer una copia de seguridad en la máquina con sello temporal antes de cambiar nada
cr0x@server:~$ cd /var/www/example.com/public_html
cr0x@server:~$ sudo cp -a .htaccess .htaccess.bak.$(date +%F-%H%M%S)
cr0x@server:~$ sudo ls -la .htaccess*
-rw-r--r-- 1 www-data www-data 821 Dec 27 12:11 .htaccess
-rw-r--r-- 1 www-data www-data 821 Dec 27 12:11 .htaccess.bak.2025-12-27-121102
Significado: Ahora tienes una vía de escape. Si estás equivocado (pasa), puedes restaurar rápidamente.
Decisión: Nunca edites sin una copia de seguridad. En respuesta a incidentes, “recordaré cómo era” es una mentira que te cuentas a ti mismo.
Task 8: Validar propiedad y permisos del sistema de archivos (los 403s suelen empezar aquí)
cr0x@server:~$ namei -l /var/www/example.com/public_html/.htaccess
f: /var/www/example.com/public_html/.htaccess
drwxr-xr-x root root /
drwxr-xr-x root root var
drwxr-xr-x root root www
drwxr-x--- www-data www-data example.com
drwxr-x--- www-data www-data public_html
-rw-r----- www-data www-data .htaccess
Significado: Apache (a menudo www-data) puede leer el archivo si se ejecuta como www-data. Si cualquier directorio carece del bit de ejecución para el usuario de Apache, Apache no puede atravesarlo y obtendrás 403/500 raros.
Decisión: Si los permisos son demasiado restrictivos, arregla primero los bits de ejecución de los directorios antes de culpar a las reglas de reescritura.
Task 9: Buscar el clásico bucle de redirección con cabeceras
cr0x@server:~$ curl -sS -I http://example.com/ | sed -n '1,12p'
HTTP/1.1 301 Moved Permanently
Location: https://example.com/
Significado: HTTP redirige a HTTPS. Bien.
Decisión: Ahora prueba HTTPS y comprueba si hay una segunda redirección de vuelta a HTTP, o a un host diferente.
cr0x@server:~$ curl -sS -I https://example.com/ | egrep 'HTTP/|Location'
HTTP/2 301
location: http://example.com/
Significado: Eso es un bucle: HTTPS redirige de vuelta a HTTP. Usualmente causado por cabeceras de proxy mixtas y condiciones de reescritura que detectan mal el esquema.
Decisión: Arregla la canonicalización en un solo lugar (LB o Apache), y asegúrate de que X-Forwarded-Proto se respete correctamente. No apiles redirecciones conflictivas en .htaccess y plugins.
Task 10: Desactivar rápidamente .htaccess sin borrarlo (prueba segura)
cr0x@server:~$ cd /var/www/example.com/public_html
cr0x@server:~$ sudo mv .htaccess .htaccess.disabled
cr0x@server:~$ curl -sS -D- -o /dev/null http://127.0.0.1/ | sed -n '1,8p'
HTTP/1.1 200 OK
Date: Sat, 27 Dec 2025 12:12:44 GMT
Server: Apache/2.4.57 (Ubuntu)
Significado: Quitar .htaccess de la ruta de la petición restauró el servicio. Eso es confirmación, no solución.
Decisión: Vuelve a poner un .htaccess mínimo y conocido bueno (bloque predeterminado de WordPress), luego vuelve a habilitar el archivo.
Task 11: Restaurar el bloque de reescritura predeterminado de WordPress de forma segura
cr0x@server:~$ sudo tee /var/www/example.com/public_html/.htaccess >/dev/null <<'EOF'
# BEGIN WordPress
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
# END WordPress
EOF
cr0x@server:~$ curl -sS -D- -o /dev/null http://127.0.0.1/ | sed -n '1,8p'
HTTP/1.1 200 OK
Date: Sat, 27 Dec 2025 12:13:21 GMT
Server: Apache/2.4.57 (Ubuntu)
Significado: La ruta raíz responde de nuevo. Ahora confirma permalinks y admin.
Decisión: Si esto lo arregla, sabes que los cambios previos en .htaccess fueron el desencadenante. Comienza a reintroducir reglas necesarias con cuidado.
Task 12: Verificar que el enrutamiento de permalinks funciona (un archivo inexistente debería pasar por index.php)
cr0x@server:~$ curl -sS -D- -o /dev/null http://127.0.0.1/this-should-not-be-a-file | sed -n '1,10p'
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Significado: La petición está siendo enrutada a través de WordPress en lugar de devolver 404 desde Apache. WordPress aún puede producir una página 404, pero eso es a nivel de la aplicación.
Decisión: Si obtienes un 404 de Apache, la reescritura no está funcionando. Revisa mod_rewrite, AllowOverride y RewriteBase.
Task 13: Confirmar que WordPress ve el site URL y home URL correctos (los bucles de redirección a menudo viven aquí)
cr0x@server:~$ sudo -u www-data wp option get home --path=/var/www/example.com/public_html
https://example.com
cr0x@server:~$ sudo -u www-data wp option get siteurl --path=/var/www/example.com/public_html
https://example.com
Significado: WordPress coincide en esquema y host canónicos.
Decisión: Si estos difieren (http vs https, www vs apex), arréglalos antes de añadir redirecciones en .htaccess. Deja que una capa sea la responsable de la canonicalización.
Task 14: Comprobar que el manejador PHP está sano (los 500s pueden culpar injustamente a .htaccess)
cr0x@server:~$ curl -sS -D- -o /dev/null http://127.0.0.1/wp-login.php | sed -n '1,12p'
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Significado: La ejecución de PHP funciona al menos para este punto de entrada.
Decisión: Si esto devuelve 500 mientras los assets estáticos son 200, el problema probablemente sea PHP-FPM, permisos o errores de la aplicación más que la reescritura en sí.
Task 15: Validar la salud de la configuración de Apache después de cambios (captura el “no arranca” al recargar)
cr0x@server:~$ sudo apache2ctl configtest
Syntax OK
Significado: La configuración global de Apache se parsea correctamente. Esto no valida completamente cada caso límite de .htaccess en tiempo de ejecución, pero atrapa muchos desastres.
Decisión: Si no está OK, arregla antes de recargar. Una recarga rota en un setup de nodo único es una autoinfligida indisponibilidad.
Task 16: Encontrar exactamente qué directiva de .htaccess está rompiendo las peticiones
cr0x@server:~$ sudo grep -RIn --color=never "Invalid command\|RewriteCond\|RewriteRule\|Require\|Deny" /var/log/apache2/error.log | tail -n 5
/var/log/apache2/error.log:[Sat Dec 27 12:10:13.492112 2025] [core:alert] [pid 1123] [client 127.0.0.1:39912] /var/www/example.com/public_html/.htaccess: Invalid command 'RewriteEngine', perhaps misspelled or defined by a module not included in the server configuration
Significado: El log apunta al archivo y a la directiva que falla.
Decisión: Arregla la disparidad módulo/configuración en lugar de eliminar líneas al azar.
Tres mini-historias corporativas desde las trincheras
Mini-historia 1: El outage causado por una suposición equivocada
La compañía estaba en medio de un rebranding. Nuevo dominio, nuevos certificados TLS, nuevas páginas de campaña. Un desarrollador añadió lo que parecía una redirección inofensiva en .htaccess para forzar todo al nuevo hostname. Lo probó en su portátil. Funcionó.
En producción, el tráfico llegaba a través de un load balancer que terminaba TLS y enviaba HTTP plano a Apache. La lógica de la redirección se basaba en %{HTTPS} y asumía que si Apache veía HTTP, el cliente estaba en HTTP. Esa suposición era falsa. El cliente ya estaba en HTTPS; Apache simplemente no lo sabía.
La regla de redirección forzó HTTPS, el load balancer envió HTTP a Apache, Apache forzó HTTPS otra vez y el cliente rebotó entre endpoints hasta que el navegador se rindió. El monitoreo mostraba instancias “sanables” porque Apache respondía rápido—con redirecciones. Desde fuera, el sitio era inutilizable.
La solución fue aburrida: configurar Apache para confiar en X-Forwarded-Proto (o hacer la canonicalización exclusivamente en el load balancer), luego simplificar .htaccess a solo la reescritura de WordPress. La redirección se movió al edge donde el esquema es conocido. También añadieron una comprobación sintética que falla con redirecciones excesivas, porque “200 no es lo mismo que usable”.
Mini-historia 2: La optimización que salió mal
Un ingeniero orientado al rendimiento quiso reducir la carga de PHP. Añadió cabeceras de caché agresivas en .htaccess para todo bajo /wp-content/, además de un conjunto de reglas de reescritura para servir assets precomprimidos. En papel: menos peticiones, páginas más rápidas, usuarios más felices.
Luego vinieron las fallas silenciosas. Un plugin se actualizó y cambió nombres de archivos pero reutilizó rutas. Las cabeceras de caché se establecieron con valores de larga duración e inmutables. Los navegadores conservaron JavaScript obsoleto. Algunos usuarios no pudieron enviar formularios porque el código de validación del cliente era antiguo. Los tickets de soporte comenzaron: “el botón de checkout no funciona.” El incidente no fue un outage limpio; fue peor—una rotura parcial que evitó el monitoreo.
Al intentar “arreglar rápido” vaciaron las cachés del CDN, pero las cachés de navegador siguieron envenenadas. Tuvieron que revertir las cabeceras, incrementar versiones de assets y enviar instrucciones de cache-busting específicas. El coste real no fue CPU; fue la confianza del usuario y tiempo.
Lección aprendida: la caché es un cambio de producto. Si no controlas el versionado de assets de punta a punta, no pongas TTLs heroicos en el origen vía .htaccess. Usa la capa de caché de la plataforma (CDN) con valores sensatos y una estrategia explícita de invalidación, o mantén TTLs cortos.
Mini-historia 3: La práctica aburrida pero correcta que salvó el día
En otra empresa, el sitio WordPress estaba detrás de un proceso interno de cambio. No era sofisticado. Era consistente: cada edición de config, incluido .htaccess, pasaba por un pequeño repositorio y un job de despliegue que archivaba versiones anteriores en el servidor.
Un viernes por la tarde, un contratista aplicó un “paquete de endurecimiento de seguridad” que incluía bloquear acceso a ciertos archivos PHP y añadir reglas restrictivas bajo /wp-admin/. Inmediatamente bloqueó a los administradores—el mismo día que se habían programado actualizaciones de contenido. Predictiblemente, todos culparon a WordPress.
El on-call no debatió. Recuperaron el último artefacto conocido bueno de .htaccess de los logs de despliegue, lo restauraron y confirmaron la recuperación en minutos. Sin arqueología. Sin adivinanzas. Sin inmersiones SSH heroicas en múltiples instancias.
Después, con calma, construyeron un plan de pruebas: verificar login de admin, llamadas REST API usadas por el editor, subidas y cron. Los cambios del contratista se reintrodujeron detrás de un entorno de staging, se ajustaron y se desplegaron con plan de rollback. No fue glamuroso. Funcionó.
Errores comunes: síntoma → causa raíz → arreglo
Esta sección existe porque la mayoría de las roturas de .htaccess riman. Si reconoces el síntoma, puedes evitar mucha dramatización.
500 Internal Server Error inmediatamente después de editar .htaccess
- Síntoma: Todas las peticiones devuelven 500; el log de errores de Apache apunta a
.htaccess. - Causa raíz: Directiva inválida (typo), módulo no habilitado (comúnmente
mod_rewrite) o directiva no permitida en el contexto de.htaccess. - Arreglo: Revisa el log de errores para la directiva exacta, habilita el módulo (
a2enmod rewrite) o mueve la directiva a la configuración del vhost si no está permitida.
403 Forbidden en todo, incluida la página principal
- Síntoma: 403 para
/y assets; a veces solo después de añadir reglas “deny”. - Causa raíz:
Require all denieddemasiado amplio, estilo antiguoDeny from allmal aplicado o permisos de traversía del sistema de archivos rotos. - Arreglo: Elimina/limita bloques deny; verifica permisos de ejecución de directorio con
namei -l; asegura que el vhost tengaRequire all grantedpara DocumentRoot.
Permalinks bonitos devuelven 404 de Apache, pero /index.php funciona
- Síntoma:
/about/falla con 404 en el servidor web;/index.php?p=123funciona. - Causa raíz: Reescritura no ejecutándose:
mod_rewritedeshabilitado,AllowOverrideno permite reescritura oRewriteBaseincorrecto porque WordPress está en un subdirectorio. - Arreglo: Habilita rewrite; establece
AllowOverride Allo al menosAllowOverride FileInfo; configuraRewriteBase /subdir/cuando WordPress no está en la raíz del vhost.
Bucle infinito de redirección (el navegador dice “demasiadas redirecciones”)
- Síntoma: Bucle entre http/https o entre www y sin-www.
- Causa raíz: Canonicalización configurada en múltiples capas (plugin +
.htaccess+ load balancer), o detección de esquema incorrecta detrás de la terminación TLS. - Arreglo: Elige una capa para canonicalizar. Asegura que
home/siteurlde WordPress coincidan. Si estás detrás de un proxy, configura cabeceras reenviadas confiables y basa las redirecciones en ellas.
Admin funciona, pero subidas e imágenes devuelven 403
- Síntoma: El sitio carga, pero la biblioteca de medios muestra imágenes rotas; acceso directo a
/wp-content/uploads/...devuelve 403. - Causa raíz: Una regla deny destinada a archivos PHP o dotfiles coincide demasiado ampliamente, o permisos del sistema de archivos en uploads están mal tras una migración.
- Arreglo: Restringe el
FilesMatcha archivos sensibles específicos; verifica propiedad y permisos enwp-content/uploads.
Sitio “arriba” pero lento después de añadir reglas de seguridad/caché en .htaccess
- Síntoma: Aumento del TTFB; picos de CPU; los logs muestran muchas reescrituras internas.
- Causa raíz: Reglas de reescritura causando comprobaciones de sistema de archivos excesivas, o cadenas de redirección; también posible que
.htaccesstenga regex costosas evaluadas en cada petición. - Arreglo: Simplifica reglas; mueve lógica pesada a la config del vhost; prefiere bloques explícitos en el reverse proxy; mide con logs de acceso y tiempos de respuesta.
Endurecer sin romper: cabeceras, controles de acceso y límites
El endurecimiento no es copiar-pegar. Endurecer es modelar amenazas más pruebas de compatibilidad. WordPress tiene una UI de admin, REST API, endpoint cron, flujos de actualización de plugins, subidas de archivos y a veces un plugin de caché que espera ciertas cabeceras. Si lo cierras como un sitio estático, se comportará como una puerta cerrada: bloqueada.
Reglas que suelen ser seguras
- Deshabilitar listado de directorios con
Options -Indexes. Evita la navegación accidental de directorios sin un index. - Bloquear acceso a archivos sensibles explícitos como
wp-config.phpy.env. Usa coincidencias explícitas, no comodines amplios. - Añadir cabeceras de seguridad no invasivas (
X-Content-Type-Options,Referrer-Policy). Son de bajo riesgo y alto beneficio.
Reglas que son riesgosas y deben tratarse como cambios, no como valores predeterminados
- Bloquear
xmlrpc.phpa ciegas. Algunos sitios aún lo usan (apps móviles, integraciones). Si lo bloqueas, valida que nada dependa de él. - Allowlisting de IP para
/wp-admin/. Ideal para sitios internos; una pesadilla de soporte para equipos distribuidos y respuesta a incidentes. Si lo haces, incluye una vía de “romper el vidrio”. - Bloqueos complejos por user-agent o query-string. Tienden a bloquear peticiones legítimas y generan sesiones de depuración “funciona en mi máquina”.
- Cabeceras de caché demasiado agresivas en el origen. Útil cuando controlas el versionado; peligroso cuando no lo haces.
Dónde poner la lógica: .htaccess vs vhost vs load balancer
Si controlas la configuración del servidor, prefiere la configuración del vhost sobre .htaccess. Es más rápida (sin comprobaciones por petición en el sistema de archivos) y más auditable. Usa .htaccess como capa de compatibilidad, no como tu motor principal de políticas.
Si tienes un load balancer o CDN, las redirecciones canónicas (www/non-www, http/https) usualmente pertenecen allí. Esa capa ve el esquema real del cliente y puede aplicar un comportamiento consistente entre orígenes.
Una cita (idea parafraseada): Los sistemas fallan de maneras sorprendentes; la resiliencia viene de esperar fallos y diseñar para la recuperación.
— idea parafraseada asociada al pensamiento de confiabilidad operativa de John Allspaw.
Broma corta #2: La forma más rápida de aprender reglas de reescritura es causar un bucle de redirecciones y ver a tu navegador convertirse en un entusiasta del cardio.
Listas de verificación / plan paso a paso
Checklist A: Recuperar el sitio (modo incidente de 15 minutos)
- Confirma el síntoma localmente con
curla127.0.0.1para evitar ruido del CDN. - Sigue los registros de errores de Apache y reproduce la petición una vez.
- Haz copia de seguridad del actual
.htaccesscon sello temporal. - Desactiva temporalmente
.htaccessrenombrándolo. Si el servicio vuelve, has aislado la causa. - Restaura el bloque mínimo de reescritura de WordPress y vuelve a probar la página principal y un permalink.
- Confirma puntos de entrada de admin como
/wp-login.phpy/wp-admin/. - Detente allí. No sigas “mejorando” durante el incidente. Estabiliza primero.
Checklist B: Diagnosticar la causa raíz real (después de restaurar el servicio)
- Hacer diff del archivo roto contra el predeterminado. Identifica qué bloque introdujo el comportamiento.
- Comprueba módulos habilitados (
rewrite,headers) y compatibilidad de versión del servidor. - Revisa el comportamiento del proxy/LB: terminación TLS, cabeceras reenviadas, redirecciones canónicas.
- Prueba en staging con el mismo vhost y la misma configuración de cabeceras proxy. “Staging sin el load balancer” no es staging; es manualidades.
- Añade pruebas sintéticas: una para conteo de redirecciones, una para estado de la página de login, una para un permalink y una para un asset estático.
Checklist C: Reintroducir reglas necesarias de forma segura
- Añade solo un cambio lógico a la vez (un bloque de redirección, un bloque deny, un bloque de cabeceras).
- Después de cada cambio, ejecuta tres comprobaciones: página principal, un permalink y wp-login.
- Observa los logs mientras pruebas. Apache a menudo te dirá lo que el navegador no te muestra.
- Prefiere la configuración del vhost para reglas permanentes si la controlas; mantén
.htaccessmínimo. - Documenta por qué existe la regla en comentarios. El tú del futuro olvidará, y el tú del futuro estará de guardia.
Preguntas frecuentes (FAQ)
1) ¿Puedo simplemente borrar .htaccess?
Puedes, y es una buena prueba de aislamiento. Pero si dependes de permalinks bonitos, borrarlo suele degradar el enrutamiento a URLs “plain” o romper la resolución de páginas. Usa la eliminación/renombrado para confirmar causalidad y luego restaura el predeterminado mínimo.
2) ¿Por qué una sola línea en .htaccess tiró todo el sitio?
Porque Apache parsea .htaccess durante el manejo de la petición. Un error de sintaxis o una directiva desconocida puede hacer que Apache rechace la petición (a menudo con 500) antes de que WordPress se ejecute. Es fallo rápido, no degradación graciosa.
3) Estoy en Nginx. ¿Por qué mi cambio en .htaccess no hace nada?
Nginx no lee .htaccess. Si estás detrás de Nginx (o un host gestionado que usa Nginx), las reglas equivalentes deben implementarse en la configuración de Nginx, no en un archivo que WordPress espera que Apache lea.
4) ¿Cuál es el contenido predeterminado más seguro para .htaccess de WordPress?
El bloque de reescritura de WordPress mostrado arriba. Es el conjunto más pequeño que hace que los permalinks funcionen. Añade solo reglas extra que puedas justificar y probar.
5) ¿Debería poner cabeceras de seguridad en .htaccess?
Es aceptable si no puedes editar la config del vhost, pero mantenlo mínimo. Algunas cabeceras (especialmente Content Security Policy) pueden romper plugins, editores y contenido embebido. Empieza con cabeceras de bajo riesgo y expande solo tras pruebas.
6) ¿Por qué tengo un bucle de redirección tras forzar HTTPS?
Lo más común: TLS termina en un load balancer, Apache ve HTTP de backend y tu lógica de redirección usa %{HTTPS} en lugar de cabeceras reenviadas confiables. Arregla la canonicalización en el edge o enseña a Apache cuál es el esquema real.
7) ¿Puede un plugin reescribir mi .htaccess automáticamente?
Sí. Muchos plugins de caché y seguridad modifican .htaccess. Es conveniente hasta que deja de serlo. Si lo permites, trátalo como código: rastrea cambios, guarda copias y entiende qué escribe el plugin.
8) ¿Qué hago si no puedo acceder a wp-admin para “Guardar enlaces permanentes” y regenerar .htaccess?
Restaura el bloque predeterminado manualmente (como se muestra), luego recupera el acceso al admin. Alternativamente, usa WP-CLI para ajustar ajustes de permalinks, pero el rewrite del servidor web debe estar correcto o WordPress no verá las rutas esperadas.
9) ¿Es mala idea AllowOverride All?
Operativamente conveniente y sensible en seguridad. Si puedes, delimítalo: habilita solo lo que necesitas (AllowOverride FileInfo para reescritura, quizá Options si hace falta). Cuantas menos directivas permitas, menor será el radio de daño de una edición mala.
10) ¿Cómo evito que esto vuelva a ocurrir?
Deja de tratar .htaccess como un bloc de notas. Ponlo bajo control de versiones, desplégalo como configuración y añade un mecanismo de rollback. Además, añade monitoreo sintético que detecte bucles de redirección y picos de 500 rápidamente.
Conclusión: pasos siguientes para evitar que se repita
Si tu sitio WordPress cayó tras un cambio en .htaccess, la solución no es mística. Es disciplinada:
- Restaura el servicio rápido respaldando y revirtiendo al bloque de reescritura predeterminado de WordPress.
- Usa los logs como fuente de verdad. Apache te dice cuando no puede parsear o aplicar directivas.
- Elige una capa para redirecciones (edge u origen) y deja de apilar reglas de canonicalización en tres sitios.
- Endurece con prudencia. Bloquea archivos sensibles explícitos, desactiva índices, añade un par de cabeceras seguras. Deja las heroicidades para staging.
- Operationaliza el archivo: control de versiones, despliegue automatizado y un artefacto de rollback que puedas aplicar medio dormido.
El objetivo no es nunca romper .htaccess. El objetivo es hacer que romperlo no sea catastrófico. Los sistemas de producción recompensan la corrección aburrida. Castigan la ingeniosidad con intereses.