403 Forbidden es el tipo de “no” menos útil. Es tu stack diciéndote, sin más, que entendió la petición y la negó—a menudo sin explicar qué componente la rechazó.
En el ecosistema WordPress, ese rechazo puede venir de cualquier sitio: permisos del sistema de archivos, una regla del servidor web que olvidaste haber añadido, un WAF que cree que el cuerpo POST parece SQLi, un CDN que protege el origen “por tu bien”, o un plugin de seguridad que decidió ponerse agresivo. Esta guía explica cómo responsabilizar a la capa correcta y arreglarla sin convertir tu sitio en un bar abierto.
Qué significa realmente un 403 (y por qué no es una sola cosa)
HTTP 403 significa que el servidor (o algo que actúa como tal) se niega a autorizar la petición. La parte importante: “el servidor” puede ser un edge de CDN, un proxy inverso, tu servidor web de origen, un módulo WAF, o incluso WordPress mismo a través de un plugin que devuelve un 403.
Patrones comunes:
- 403 en el edge: CDN/WAF bloquea antes de que el tráfico llegue al origen. Los registros del origen parecen limpios porque la petición nunca llegó.
- 403 en el proxy/servidor web: reglas de Nginx/Apache, controles de acceso, denegaciones por ruta, control de listado de directorios, configuración de autenticación errónea o reglas de certificado cliente TLS.
- 403 desde el sistema de archivos: el usuario del servidor web no puede leer el archivo o atravesar directorios. Nginx suele registrar “permission denied” y devuelve 403/404 según la configuración.
- 403 por lógica de la app: el core de WordPress rara vez hace esto por sí solo en páginas públicas, pero los plugins de seguridad y los mu-plugins personalizados sí lo hacen.
Operativamente, 403 es menos un código de error y más un fallo de negociación entre “quién eres” y “a qué puedes acceder”. Arreglarlo consiste en probar qué actor vetó la petición y ajustar el permiso o la regla más limitada posible.
Una cita útil para tener a mano al triagear fallos de acceso: paráfrasis de la idea
de Werner Vogels: “Todo falla todo el tiempo; diseña y opera como si la falla fuera normal.” Esa mentalidad aplica también a permisos y reglas WAF—espera denegaciones accidentales y crea formas rápidas de localizarlas.
Broma #1: Un 403 es como un portero de discoteca que no te dice el código de vestimenta—solo que tu “request” no es bienvenido.
Guía de diagnóstico rápido (primero/segundo/tercero)
Este es el camino más corto a “quién lo bloqueó” sin pasearte por todos los archivos de configuración del planeta.
Primero: determina dónde se genera el 403
- Revisa los encabezados de respuesta desde un cliente: busca marcadores de CDN/WAF y firmas de servidor. Si ves un encabezado de CDN o “Server: cloudflare”, asume bloqueo en el edge hasta que se demuestre lo contrario.
- Comprueba si la petición llega al origen: sigue los logs de acceso del origen mientras reproduces el fallo. La ausencia de entrada suele significar bloqueo en el edge o DNS/ruta equivocada.
- Compara desde dos redes: tu IP de oficina puede estar bloqueada mientras el resto del mundo funciona (o al revés).
Segundo: clasifica la superficie bloqueada
- ¿Solo es wp-admin/wp-login.php? Piensa en reglas WAF, protección contra fuerza bruta, allowlists por geo/IP o bloqueo por autenticación.
- ¿Solo son uploads/archivos estáticos? Piensa en permisos del sistema de archivos, bloques de ubicación en Nginx, protección contra hotlink o denegación por extensión/MIME.
- ¿Solo peticiones POST? Piensa en ModSecurity/OWASP CRS, límites de tamaño de cuerpo, o plugins de seguridad que marcan payloads/nonces.
- ¿Es intermitente? Piensa en rate limiting, fail2ban, gestión de bots o un proxy con configuración inconsistente.
Tercero: corrige la capa más estrecha posible
- Si es edge/WAF: empieza por los registros de reglas y pruebas de “bypass para esta URI” antes de cambiar permisos en el origen.
- Si es servidor web: corrige reglas específicas de
location/<Directory>o ámbitos de auth—no apliques “permitir todo” a lo loco. - Si es sistema de archivos: corrige propiedad, bits de modo y ACLs en el subtree mínimo.
- Si es WordPress/plugin: desactiva el plugin problemático de forma segura o añade una regla de permitida para la ruta específica.
Hechos e historia: por qué el 403 sigue ocurriendo
Algo de contexto ayuda porque 403 no es solo cosa de WordPress—es un efecto colateral de cómo evolucionó la web.
- Hecho 1: El código de estado “403 Forbidden” está definido por estándares HTTP desde RFCs tempranos; se usa cuando la petición se entiende pero la autorización se niega, no para peticiones malformadas (eso es 400).
- Hecho 2: El modelo
.htaccessde Apache popularizó sobrescrituras por directorio; también popularizó 403 auto-infligidos cuando una directiva deny o rewrite cae en la carpeta equivocada. - Hecho 3: El estilo de configuración de Nginx (
locationy precedencia) lo hace rápido y predecible—hasta que un “deny all;” en el bloque equivocado se convierte en un portero sigiloso. - Hecho 4: ModSecurity surgió para proteger apps web sin reescribir la app; el OWASP Core Rule Set es útil, pero los falsos positivos son un impuesto recurrente que debes presupuestar.
- Hecho 5: La interfaz XML-RPC de WordPress existió para clientes de publicación remota; se volvió favorita de ataques de fuerza bruta y amplificación, por lo que muchas plantillas WAF la bloquean o limitan.
- Hecho 6: Los endpoints REST (
/wp-json/) se usan mucho hoy en día; bloquearlos rompe funciones modernas y setups headless de formas que parecen “403 de admin aleatorio”. - Hecho 7: La “protección hotlink” (deny por referer) fue común como defensa de ancho de banda; aún causa 403 en imágenes cuando los sitios se mueven atrás de CDNs o cambian de dominio.
- Hecho 8: Muchos paneles de hosting históricamente recomendaron permisos
777como “arreglo”. Ese consejo es herencia de épocas de hosting compartido inseguro y debe quedarse en el ático. - Hecho 9: Las prohibiciones estilo fail2ban surgieron como protección pragmática para SSH y luego se expandieron a logs HTTP; son efectivas, pero también pueden banear a tu propio monitoreo o NAT de oficina.
Depuración por capas: identifica quién dijo “forbidden”
Si tratas un 403 como “WordPress está roto”, cambiarás lo incorrecto y aumentarás el riesgo. El truco es recorrer la ruta de la petición:
- Cliente (navegador, bot, cliente API)
- DNS (¿estás golpeando el edge/origen correcto?)
- CDN/WAF (políticas de edge, gestión de bots, allowlist geo/IP)
- Load balancer / proxy inverso (ACLs, reglas de ruta, autenticación)
- Servidor web (reglas de acceso Nginx/Apache, rewrites, auth, ubicaciones internas)
- Sistema operativo + sistema de archivos (propiedad, bits de modo, ACLs, SELinux/AppArmor)
- PHP-FPM + WordPress (bloqueos por plugin, auth a nivel de aplicación, nonce/CSRF)
- Dependencias aguas abajo (almacenamiento de objetos para uploads, SSO, autenticación de terceros)
La mayoría de los 403 se vuelven evidentes una vez identificas la capa. Así que el primer trabajo es la atribución.
Tareas prácticas: comandos, salidas y decisiones (12+)
Estos son los movimientos reales. Cada tarea incluye: un comando, qué significa la salida y qué decisión tomar a continuación. Úsalas en orden hasta localizar al culpable.
Tarea 1: Reproducir y capturar encabezados (detectar edge vs origen)
cr0x@server:~$ curl -I https://example.com/wp-login.php
HTTP/2 403
date: Fri, 26 Dec 2025 10:12:33 GMT
content-type: text/html; charset=UTF-8
server: cloudflare
cf-ray: 85a1b2c3d4e5f678-LHR
Qué significa: La respuesta la sirve Cloudflare (o al menos el edge se identifica). El 403 probablemente esté generado en el edge.
Decisión: No toques los permisos de Nginx aún. Ve a los logs y reglas del WAF/CDN primero; también confirma si la petición llega al origen (Tarea 2).
Tarea 2: Confirmar si el origen ve la petición
cr0x@server:~$ sudo tail -f /var/log/nginx/access.log
192.0.2.10 - - [26/Dec/2025:10:12:33 +0000] "GET / HTTP/1.1" 200 15432 "-" "curl/8.5.0"
Qué significa: Ves peticiones a / pero no a /wp-login.php (mientras el cliente obtiene 403). Eso es consistente con bloqueo en el edge solo para esa ruta.
Decisión: Enfócate en políticas de path del edge/WAF. Si ves la petición bloqueada en los logs del origen, salta a las Tareas 4–9.
Tarea 3: Bypassear el CDN (golpear el origen directamente) para aislar capas
cr0x@server:~$ curl -I --resolve example.com:443:203.0.113.20 https://example.com/wp-login.php
HTTP/2 200
date: Fri, 26 Dec 2025 10:13:10 GMT
content-type: text/html; charset=UTF-8
server: nginx
Qué significa: El origen devuelve 200, pero la ruta pública devuelve 403. Es un problema de política en el edge/WAF, no de permisos de WordPress.
Decisión: Arregla reglas WAF o protecciones de bots; evita el pánico de “chmod a todo”.
Tarea 4: Inspeccionar el log de errores de Nginx por pistas de permiso/acceso
cr0x@server:~$ sudo tail -n 50 /var/log/nginx/error.log
2025/12/26 10:14:22 [error] 1123#1123: *884 open() "/var/www/example.com/wp-admin/admin.php" failed (13: Permission denied), client: 192.0.2.10, server: example.com, request: "GET /wp-admin/ HTTP/1.1", host: "example.com"
Qué significa: Denegación clásica de permisos en el sistema de archivos (errno 13). Nginx no pudo leer el archivo objetivo.
Decisión: Revisa propiedad/bits de modo/ACLs/SELinux. Empieza con las Tareas 6–8 y la Tarea 10.
Tarea 5: Verificar que la configuración del vhost no deniegue explícitamente rutas
cr0x@server:~$ sudo nginx -T 2>/dev/null | sed -n '/server_name example.com/,$p' | sed -n '1,160p'
server {
server_name example.com;
root /var/www/example.com;
location ~* \.(php|phtml)$ { include snippets/fastcgi-php.conf; }
location ~* /wp-admin/ { deny all; }
}
Qué significa: Alguien añadió un deny all para /wp-admin/. Nginx está haciendo exactamente lo que le indicaron.
Decisión: Quita o limita la regla deny (por ejemplo, permite solo IPs de admin), luego recarga Nginx (Tarea 9). No toques permisos del sistema de archivos; probablemente estén bien.
Tarea 6: Comprobar permisos de archivos y directorios en la ruta con fallo
cr0x@server:~$ namei -l /var/www/example.com/wp-admin/admin.php
f: /var/www/example.com/wp-admin/admin.php
drwxr-x--- root root /
drwxr-x--- root root var
drwxr-x--- root root www
drwxr-x--- root root example.com
drwxr-x--- root root wp-admin
-rw-r----- root root admin.php
Qué significa: Toda la ruta es propiedad de root con grupo root y permisos ejecutables restrictivos en directorios (750). Si Nginx se ejecuta como www-data, no puede atravesar.
Decisión: Corrige la propiedad al usuario/grupo web (Tarea 8) o ajusta membresía de grupo y permisos de forma segura. Evita permisos world-writable.
Tarea 7: Confirmar el usuario del servidor web
cr0x@server:~$ ps -o user,group,cmd -C nginx | head -n 5
USER GROUP CMD
root root nginx: master process /usr/sbin/nginx
www-data www-data nginx: worker process
www-data www-data nginx: worker process
Qué significa: Los workers corren como www-data. Esa es la identidad que debe leer y atravesar el árbol de WordPress.
Decisión: Asegura que los directorios tengan al menos permiso de ejecución para www-data (o su grupo) y que los archivos sean legibles, luego prueba de nuevo.
Tarea 8: Corregir propiedad y permisos básicos (valores por defecto seguros)
cr0x@server:~$ sudo chown -R www-data:www-data /var/www/example.com
cr0x@server:~$ sudo find /var/www/example.com -type d -exec chmod 755 {} \;
cr0x@server:~$ sudo find /var/www/example.com -type f -exec chmod 644 {} \;
Qué significa: Los directorios son ahora atravesables y los archivos legibles. WordPress podrá escribir donde sea necesario (luego podrás endurecerlo solo en directorios específicos como wp-content/uploads).
Decisión: Prueba de nuevo. Si el 403 persiste sin “permission denied”, pasa a revisar reglas del servidor web o SELinux/AppArmor.
Tarea 9: Validar y recargar Nginx/Apache tras cambios en la configuración
cr0x@server:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
cr0x@server:~$ sudo systemctl reload nginx
Qué significa: La configuración es válida y se recargó sin cerrar conexiones (reload es más amable que restart).
Decisión: Vuelve a probar la URL que fallaba. Si aún tienes 403, inspecciona los logs de errores de nuevo; no asumas que la causa es la misma.
Tarea 10: Comprobar el estado de SELinux y denegaciones de audit (el “muro invisible” específico de Linux)
cr0x@server:~$ getenforce
Enforcing
cr0x@server:~$ sudo ausearch -m avc -ts recent | tail -n 5
type=AVC msg=audit(1766744162.112:421): avc: denied { read } for pid=2314 comm="nginx" name="wp-config.php" dev="xvda1" ino=393231 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:default_t:s0 tclass=file permissive=0
Qué significa: SELinux niega a Nginx leer archivos etiquetados con default_t. Esto suele devolver 403/500 según el stack.
Decisión: Corrige los contextos (Tarea 11). No deshabilites SELinux en producción solo porque esté alertando.
Tarea 11: Restaurar el contexto SELinux correcto para la raíz web
cr0x@server:~$ sudo semanage fcontext -a -t httpd_sys_content_t "/var/www/example.com(/.*)?"
cr0x@server:~$ sudo restorecon -Rv /var/www/example.com
restorecon reset /var/www/example.com context unconfined_u:object_r:default_t:s0->unconfined_u:object_r:httpd_sys_content_t:s0
Qué significa: Los archivos ahora tienen etiquetas que permiten al dominio httpd leerlos.
Decisión: Prueba de nuevo. Si WordPress necesita escribir en uploads, aplica el contexto de escritura solo en ese directorio (no en todo el árbol).
Tarea 12: Detectar bloqueos por ModSecurity (403 con sensación de “Access denied”)
cr0x@server:~$ sudo tail -n 30 /var/log/apache2/modsec_audit.log
--d8a3c0f4-H--
Message: Access denied with code 403 (phase 2). Operator GE matched 5 at TX:anomaly_score.
[id "949110"] [msg "Inbound Anomaly Score Exceeded"] [tag "OWASP_CRS"]
Action: Intercepted (phase 2)
Qué significa: ModSecurity/OWASP CRS bloqueó la petición. Frecuentemente activado por ciertos payloads POST de plugins, llamadas REST API o query strings inusuales.
Decisión: Haz whitelist de forma estrecha: el ID de regla específico para la ruta específica, o ajusta umbrales de anomalía para ese vhost. No desactives globalmente el WAF salvo que disfrutes de manejar incidentes.
Tarea 13: Comprobar bans de fail2ban para tu IP (el escenario “¿por qué solo yo?”)
cr0x@server:~$ sudo fail2ban-client status
Status
|- Number of jail: 3
`- Jail list: sshd, nginx-http-auth, wordpress-login
cr0x@server:~$ sudo fail2ban-client status wordpress-login
Status for the jail: wordpress-login
|- Filter
| |- Currently failed: 1
| `- Total failed: 37
`- Actions
|- Currently banned: 1
`- Banned IP list: 192.0.2.10
Qué significa: Tu IP está baneada a nivel de host. Verás 403/444/drop de conexión según cómo esté configurada la jail.
Decisión: Desbane y ajusta umbrales o allowlist para la egress de tu oficina/VPN (Tarea 14). También revisa si tu monitoreo genera intentos de login falsos.
Tarea 14: Desbanear una IP y añadir una allowlist (con cuidado)
cr0x@server:~$ sudo fail2ban-client set wordpress-login unbanip 192.0.2.10
1
cr0x@server:~$ sudo grep -R "ignoreip" -n /etc/fail2ban/jail*.conf
/etc/fail2ban/jail.local:3:ignoreip = 127.0.0.1/8 203.0.113.0/24
Qué significa: La IP fue desbaneada; existe una allowlist en jail.local. El “1” indica éxito.
Decisión: Añade solo rangos de IPs confiables y estables. Si tu IP de oficina cambia a diario, usa una VPN con egress conocido en vez de esparcir direcciones por todos lados.
Tarea 15: Comprobar si WordPress está devolviendo el 403 (capa de aplicación)
cr0x@server:~$ curl -s -o /dev/null -w "%{http_code}\n" https://example.com/wp-json/wp/v2/users
403
Qué significa: El endpoint REST devuelve 403. Eso puede ser esperado (usuarios no autenticados no listan), o puede ser un plugin que bloquee todas las llamadas REST.
Decisión: Compara con un endpoint conocido como /wp-json/ y prueba con autenticación si hace falta. Si la UI de admin también falla, inspecciona plugins de seguridad y mu-plugins.
Tarea 16: Deshabilitar plugins temporalmente de forma segura (sin wp-admin)
cr0x@server:~$ cd /var/www/example.com
cr0x@server:~$ sudo mv wp-content/plugins wp-content/plugins.disabled
cr0x@server:~$ sudo mkdir wp-content/plugins
Qué significa: WordPress ve “no plugins” y vuelve al comportamiento core. Es la prueba A/B más limpia cuando wp-admin es inaccesible.
Decisión: Si el 403 desaparece, restaura los plugins y haz una bisect: habilita en lotes hasta encontrar el responsable. Si persiste, los plugins no son la causa.
Tarea 17: Buscar denies en .htaccess (Apache o Nginx+htaccess vía paneles)
cr0x@server:~$ sudo grep -nE "deny from|require all denied|RewriteRule" /var/www/example.com/.htaccess
12:Require all denied
Qué significa: Existe un deny duro. A veces queda por un “bloqueo de mantenimiento” o una herramienta de migración.
Decisión: Elimínalo o delimítalo (por ejemplo, protege solo un directorio de staging). Luego recarga Apache y vuelve a probar.
Tarea 18: Validar configuración de autorización de Apache y el vhost efectivo
cr0x@server:~$ sudo apachectl -S | sed -n '1,80p'
VirtualHost configuration:
*:80 example.com (/etc/apache2/sites-enabled/example.conf:1)
ServerRoot: "/etc/apache2"
Main DocumentRoot: "/var/www/html"
Qué significa: Confirma qué archivo de vhost está sirviendo el dominio. Los problemas de 403 suelen venir de editar el archivo equivocado o el vhost incorrecto.
Decisión: Abre el config referenciado y busca <Directory> con Require all denied o la falta de Require all granted.
Modos de fallo específicos de WordPress
WordPress atrae problemas de permisos porque abarca páginas públicas, áreas de admin, APIs REST, manejo de uploads y mucho código de terceros. Aquí los sospechosos habituales—y cómo se manifiestan.
wp-admin devuelve 403, la portada funciona
Esto suele ser un bloqueo deliberado por:
- Plantilla de reglas WAF que bloquea
/wp-admina IPs no allowlisteadas - Bloqueo “secure admin” en Nginx/Apache añadido durante un incidente y nunca quitado
- Jail de fail2ban activada por intentos repetidos de login
- Plugin de seguridad (Wordfence, iThemes, etc.) en modo “lockdown”
Qué hacer: Identifica si el origen ve la petición. Si la ve, busca en configs bloques para wp-admin y revisa WAF/fail2ban. No persigas permisos del sistema de archivos a menos que los logs muestren (13: Permission denied).
wp-login.php devuelve 403 o entra en bucle
Causas típicas:
- Protección de bots cree que tu login es un bot; bloquea por challenge JS/cookies
- WAF detecta patrones de credential stuffing; aplica rate limit o deniega
- Inspección del cuerpo POST marca cadenas de contraseña como “payload de ataque” (falsos positivos)
- Proxy inverso mal configurado con encabezados que rompen auth/cookies, que puede parecer 403 tras redirecciones
Qué hacer: Prueba desde otra IP y con perfil de navegador limpio. Revisa logs WAF por el request ID y rule ID exactos. Si un plugin de seguridad está implicado, desactívalo por sistema de archivos como en la Tarea 16.
Uploads (wp-content/uploads) devuelven 403
Esto suele ser aburrido. Aburrido es bueno.
- Permisos de directorio impiden traversal o lectura
- Bloques de location en Nginx niegan “archivos ocultos” pero tus reglas son demasiado amplias
- Protección hotlink rechaza Referer cambiado o ausente
- Uploads movidos a almacenamiento de objetos y el origen niega acceso directo
Qué hacer: Comprueba un archivo individual con curl headers y verifica que sea un fichero estático servido por el servidor web, no por PHP. Luego inspecciona permisos de sistema de archivos y precedencia de ubicaciones en Nginx. Si usas plugins de offload, revisa lógica de URL firmadas y políticas del bucket.
wp-json o admin-ajax devuelve 403
Esto rompe editores, customizers de tema, algunos plugins de caché y frontends headless. Culpables típicos:
- Regla WAF que bloquea
/wp-jsonpor considerarlo “endpoint API” - Plugin de seguridad que bloquea REST API para usuarios no autenticados (a veces demasiado agresivo)
- Contenido mixto o problemas con encabezados proxy que provocan fallos de verificación de nonce que pueden manifestarse como 403
Qué hacer: Prueba primero el index de /wp-json/; compara respuestas con y sin autenticación; decide si el bloqueo es política intencional o una ruptura accidental.
WAF/CDN/herramientas de seguridad: cómo se generan 403
Los WAF son necesarios. También son confiados. Tu trabajo es mantenerlos confiados y correctos.
403 en edge vs 403 en origen: por qué importa
Un 403 generado en el edge:
- No aparecerá en los logs de acceso del origen
- Suele incluir encabezados y ray/request IDs específicos del edge
- A veces presenta una página de bloqueo con marca
Un 403 generado en el origen:
- Aparecerá en logs de Nginx/Apache con status 403
- Normalmente tendrá contexto correlacionado en error log (“permission denied”, “access forbidden by rule”, fallos de auth)
- Se puede reproducir al evadir el edge (Tarea 3)
Falsos positivos de ModSecurity/CRS: trátalo como un problema de depuración, no como una guerra religiosa
Los falsos positivos suelen aumentar cuando:
- Despliegas un plugin nuevo que hace POST de JSON o formularios grandes
- Activas un page builder que envía payloads serializados complejos
- Añades endpoints headless o rutas REST personalizadas
Estrategia de arreglo:
- Identifica el ID de regla que bloquea (Tarea 12).
- Confirma que la petición es legítima (no hagas whitelist de un ataque real).
- Desactiva o ajusta la regla específica para la URI o parámetro concreto.
- Mantén un registro de auditoría. “Desactivamos el WAF” no es política; es una confesión.
Límites de tasa y gestión de bots
El rate limiting a menudo devuelve 403 (o 429, según el proveedor). Lo complicado es que puede activarse por:
- Ataques reales (bien)
- Tu propio monitoreo de uptime (vergonzoso)
- Pruebas de carga (predecible)
- NATs de operadores móviles (una IP representa miles de usuarios)
La mayoría de proveedores permiten un modo “challenge” en vez de bloqueo duro. Prefiere challenge para tráfico anónimo; usa bloqueos duros para paths conocidos como patrones abusivos de XML-RPC.
Plugins de seguridad y bloqueos “útiles”
Los plugins de seguridad de WordPress pueden devolver 403 desde PHP antes de que Nginx/Apache puedan ayudarte. Sus logs a veces están en wp-admin (al que no puedes acceder), lo cual es… una elección de diseño.
Broma #2: Lo único más persistente que un bot de fuerza bruta es un plugin de seguridad que te bloqueó cinco minutos antes de tu demo.
Enfoque operativo:
- Mantén configuraciones de plugins en control de versiones cuando sea posible (o al menos documentadas).
- Conoce el “kill switch” en el sistema de archivos (Tarea 16).
- Prefiere controles a nivel de infraestructura (WAF, rate limit) para amenazas genéricas y reserva los plugins para hardening específico de WP.
Errores comunes: síntoma → causa raíz → solución
Estos son los que consumen horas porque el síntoma parece otra cosa.
1) Solo una oficina/VPN obtiene 403; todos los demás bien
Síntoma: Marketing dice “el sitio está caído”, pero tu teléfono en LTE funciona.
Causa raíz: IP baneada (fail2ban, reputación WAF, score de bot) contra el NAT/VPN de tu oficina.
Solución: Revisa bans (Tarea 13), desbane (Tarea 14) y luego crea una estrategia de allowlist estable. Si debes allowlistear humanos, usa una VPN con egress predecible.
2) wp-admin 403 tras un cambio de “hardening”
Síntoma: Portada 200. Páginas de admin 403.
Causa raíz: Un location de Nginx o un <Directory> de Apache deniega demasiado ampliamente (Tarea 5, Tarea 18), o una regla WAF por path.
Solución: Quita el deny o delimítalo al rango de IPs y métodos correctos. Valida precedencia en Nginx (regex vs prefijo). Recarga y vuelve a probar.
3) Archivos estáticos 403 tras migración o restauración
Síntoma: Imágenes/CSS devuelven 403; páginas PHP pueden renderizar aún.
Causa raíz: Propiedad cambiada a root durante la restauración; directorios no atravesables por el usuario web (Tareas 6–8).
Solución: Corrige propiedad y permisos; considera usar tar preservando propiedad apropiadamente y verifica el usuario en tiempo de ejecución.
4) Acciones aleatorias de plugins 403, especialmente en POST
Síntoma: Guardados de formularios, actualizaciones del editor o llamadas API fallan con 403; GET funciona.
Causa raíz: Regla WAF/ModSecurity activada por contenido o tamaño del body POST (Tarea 12), o límites de tamaño de petición.
Solución: Encuentra el ID de regla y ajusta/whitelistea de forma estrecha. Si es por tamaño, ajusta límites en Nginx/Apache y PHP, pero mantén límites razonables.
5) 403 en directorios que deberían ser públicos
Síntoma: Visitar /wp-content/ u otros directorios devuelve 403.
Causa raíz: El listado de directorios está deshabilitado y devuelve 403 (esperado) y estás probando el directorio, no un archivo; o falta un archivo index.
Solución: Solicita un archivo real (/wp-content/uploads/...). Asegura que existan index donde sean necesarios. No habilites listado de directorios en producción salvo que te gusten las sorpresas.
6) 403 aparece tras activar SELinux
Síntoma: Los permisos parecen correctos, pero sigue habiendo 403 con “permission denied” en logs o bloqueos silenciosos.
Causa raíz: Contextos SELinux incorrectos (Tarea 10).
Solución: Restaura contextos correctos (Tarea 11). Mantén SELinux; arregla el etiquetado en vez de quitar las barreras.
Listas de verificación / plan paso a paso
Checklist A: Triage en 10 minutos
- Captura encabezados con
curl -Iy anota marcadores de servidor/WAF (Tarea 1). - Tail a los logs de acceso del origen mientras reproduces (Tarea 2).
- Evadir CDN con
--resolvesi aplica (Tarea 3). - Si el origen lo ve: inspecciona logs de error por “permission denied” o “access forbidden” (Tarea 4).
- Busca en la configuración del servidor reglas deny sobre la ruta (Tarea 5, Tarea 18).
- Comprueba traversal en el sistema de archivos con
namei -l(Tarea 6). - Si sigue raro: revisa SELinux (Tarea 10) y logs WAF/ModSecurity (Tarea 12).
Checklist B: Hardening sin autodañarse
- Restringe wp-admin por IP solo si tienes IPs estables; de lo contrario usa SSO/VPN en vez de allowlists frágiles.
- Throttlea login y XML-RPC en el edge. Prefiere rate limits a denegas totales salvo que realmente no necesites la función.
- Documenta cada regla deny con comentario y referencia de ticket en config. El futuro-tú es un extraño; trátalo con amabilidad.
- Mantén un método de emergencia: capacidad para evadir CDN hacia el origen y para desactivar plugins vía sistema de archivos.
- Monitorea falsos positivos del WAF: cuenta bloqueos por rule ID y URI para poder ajustar con evidencia.
Checklist C: Validación post-arreglo
- Reprueba la URL exacta que fallaba, método y encabezados (GET vs POST importa).
- Verifica que los logs ahora muestren la petición con 200/302 como corresponde.
- Confirma que no se introdujo una exposición amplia (no nuevo acceso público a
wp-config.php, no listado de directorios). - Prueba desde múltiples redes (oficina, LTE, externo) para captar políticas basadas en IP.
- Añade una comprobación sintética para el endpoint que rompió (wp-login, wp-json, archivo de uploads).
Tres micro-historias corporativas desde las trincheras
Micro-historia 1: El incidente causado por una suposición errónea
La empresa tenía un sitio marketing de WordPress detrás de un CDN/WAF. Una mañana, los editores informaron que /wp-admin/ devolvía 403. Ingeniería hizo lo que hace bajo presión: asumió “permisos” y empezó a cambiar propiedad y bits en el árbol de WordPress.
No ayudó. De hecho, empeoró: un apresurado chown -R cambió la propiedad de un directorio que un job de despliegue esperaba poseer, y ahora las releases empezaron a fallar. Dos equipos, un 403 y una sensación creciente de que Internet les estaba insultando personalmente.
El verdadero problema era visible en la primera captura de encabezados con curl: el 403 lo servía el edge y el origen nunca vio la petición. Una nueva regla gestionada del WAF había empezado a bloquear /wp-admin/ desde “países no confiables”, y los editores estaban en una VPN corporativa que egressaba en otro sitio. Desde la perspectiva del WAF, el tráfico admin había teletransportado.
Una vez que alguien evadió el CDN con una resolución directa al origen, wp-admin funcionó. La solución fue una excepción WAF estrecha para endpoints admin autenticados desde el rango de egress de la VPN, más una política: no cambiar egress de VPN sin avisar a quienes operan el acceso admin.
Lección: si no sabes qué capa produjo el 403, no estás haciendo troubleshooting aún—estás adivinando con privilegios de root.
Micro-historia 2: La optimización que salió mal
Un equipo distinto quería mejor rendimiento y menos hits al origen. Activaron caching agresivo y protección de bots en el edge, incluyendo reglas para “bloquear POST sospechosos” y “challengue a user agents desconocidos”. La portada voló. Los scores de Lighthouse mejoraron. Todos celebraron.
Luego, el plugin de formularios del sitio empezó a fallar. Los envíos retornaban 403 de forma intermitente. Solo sucedía para algunos usuarios y no se reproducía bien en la oficina. Clásico.
El efecto secundario fue sutil: el edge consideraba ciertas combinaciones de navegadores móviles como “baja reputación” y aplicaba mayor escrutinio. El payload del formulario incluía un blob de datos serializados que parecía una firma de ataque para el WAF. Algunas peticiones se retaban; otras se bloqueaban.
El equipo inicialmente intentó “optimizar” otra vez añadiendo más reglas de caching, lo que solo aumentó la variabilidad. Finalmente extrajeron eventos de auditoría del WAF por rule ID y los correlacionaron con el endpoint que fallaba. Añadieron una excepción limitada: permitir POST a ese endpoint de formulario con inspección normal pero sin el umbral agresivo. Las tasas de conversión se recuperaron.
Lección: las optimizaciones de rendimiento que cambian el manejo de peticiones (especialmente POST y flujos de auth) son cambios de fiabilidad. Trátalos como releases con monitoreo y rollback, no como un checkbox.
Micro-historia 3: La práctica aburrida pero correcta que salvó el día
Una gran empresa corría varias instancias de WordPress—algunas legacy, otras modernas, todas detrás de una granja de proxy inversos. Tenían el hábito poco sexy: cada vez que ocurría un 403, el ingeniero on-call capturaba tres artefactos y los adjuntaba al ticket: los encabezados de curl -I, la línea de log de acceso del origen (o la prueba de su ausencia) y el extracto relevante del log de errores.
Un fin de semana, una ola de 403 afectó varios sitios, mayormente en /wp-json/ y /wp-admin/admin-ajax.php. La sospecha inmediata fue “una actualización de WordPress rompió algo”. Pero sus tres artefactos contaron otra historia: el origen casi no veía nada y las páginas de 403 llevaban los mismos identificadores de edge.
Gracias a la evidencia consistente, escalaron al equipo de red/seguridad con datos específicos: timestamps, request IDs y rutas afectadas. Seguridad encontró un conjunto de reglas gestionadas recientemente desplegadas que era demasiado agresivo para endpoints tipo REST. Lo ajustaron con una excepción dirigida en vez de deshabilitar el WAF.
En una hora el incidente estaba contenido. No hubo cambios frenéticos de permisos, ni ruleta de plugins, ni héroes de medianoche deshabilitando seguridad.
Lección: la recolección de evidencia aburrida es un multiplicador de fuerza. Evita pelear con la capa equivocada y le da a otros equipos lo que necesitan para ayudar rápido.
Preguntas frecuentes
¿Por qué WordPress muestra 403 solo en wp-admin?
Porque wp-admin es un objetivo común para hardening. Plantillas de CDN/WAF, snippets de configuración de servidor y plugins de seguridad a menudo lo restrigen por IP, país, score de bot o rate limit. Confirma si el origen ve la petición; luego busca reglas deny explícitas y sistemas de baneo.
¿Un 403 siempre es un problema de permisos en disco?
No. La denegación por permisos en disco es solo una causa común. Políticas en el edge, reglas de Nginx/Apache, ModSecurity y plugins pueden devolver 403. Mira encabezados y logs para atribuir la capa primero.
¿Qué permisos de archivo debería tener WordPress?
Una base común es directorios 755 y archivos 644, propiedad del usuario/grupo con el que corre tu servidor web (o un usuario de despliegue con acceso de grupo). Directorios escribibles suelen ser wp-content/uploads y posiblemente caches. Evita 777.
¿Por qué veo 403 cuando solicito un directorio como /wp-content/?
Porque normalmente está deshabilitado el listado de directorios. Si no hay un index, el servidor puede devolver 403 para evitar listar contenidos. Prueba una ruta de archivo real en vez del directorio.
¿Cómo puedo saber si Cloudflare (u otro CDN) me está bloqueando?
Revisa encabezados (Tarea 1), luego tail a los logs de origen (Tarea 2). Si el origen nunca ve la petición y los encabezados muestran identificadores de edge, es un bloqueo en el edge. Confírmalo evadiendo el CDN con --resolve (Tarea 3).
¿Por qué el sitio funciona en mi navegador pero curl obtiene 403?
La gestión de bots y reglas WAF suelen tratar user agents desconocidos como sospechosos. Prueba curl -I -A "Mozilla/5.0" para comparar, pero no “arregles” permitiendo todo: ajusta la política bot/WAF adecuadamente.
¿Puede un plugin de WordPress devolver un 403 aunque Nginx/Apache esté bien?
Sí. Plugins de seguridad y código personalizado pueden rechazar peticiones por IP, encabezados, rutas o amenazas percibidas. Desactiva plugins vía sistema de archivos para hacer A/B (Tarea 16). Si eso lo soluciona, reactiva en lotes y encuentra el culpable.
¿Por qué fallan las peticiones POST con 403 pero las GET funcionan?
Los cuerpos POST son inspeccionados por WAFs y pueden activar reglas; también están sujetos a límites de tamaño y contenido. Revisa logs de auditoría de ModSecurity (Tarea 12) y límites de tamaño en el servidor web. Ajusta con precisión y valida que no estés enmascarando un ataque real.
¿Debería deshabilitar el WAF para “probar” que es el problema?
Prefiere pruebas de bypass (resolución directa al origen) o un bypass de regla acotado para una ruta/IP primero. La desactivación total es último recurso, temporal y idealmente hecha durante investigación controlada con monitoreo. Si no, “arreglarás” el 403 invitando a problemas peores.
Conclusión: siguientes pasos que perduran
Los 403 son un problema de coordinación disfrazado de error web. La solución rara vez es heroica; suele ser precisa.
- Atribuye el 403 a una capa usando encabezados, logs de origen y pruebas de bypass.
- Usa evidencia: logs de error por denegaciones de permiso, logs de auditoría WAF por rule IDs, listas de baneo para bloqueos por IP.
- Aplica el arreglo más estrecho posible: ajusta una regla, una ruta, una etiqueta de contexto o un subtree de directorio—luego prueba.
- Anota lo que cambiaste y añade una comprobación sintética para el endpoint que se rompió, de modo que el próximo 403 sea detectado y diagnosticado rápido.