Reglas de seguridad en el .htaccess de WordPress que sí sirven
Casi todo lo que se vende como seguridad del .htaccess de WordPress es relleno. Las reglas que realmente cambian tu superficie de ataque son pocas y cortas: aquí tienes cuál es cuál
Publicada
Casi todo lo que se vende como “seguridad del .htaccess de WordPress” es relleno. Las reglas que de verdad cambian tu superficie de ataque son pocas: bloquear la ejecución de PHP dentro de wp-content/uploads, denegar el acceso directo a wp-config.php y desactivar el listado de directorios. Las populares —bloquear xmlrpc.php “para frenar la fuerza bruta”, ocultar tu versión de WordPress, pegar una lista de 200 líneas de user-agents de bots maliciosos— van de lo marginal al puro teatro. Abajo te explico cuál es cuál y por qué.
Antes que nada, hay que dejar algo claro: el .htaccess solo hace algo en Apache (y en LiteSpeed, que también lo lee). En nginx se ignora por completo: el archivo se queda ahí mientras tú crees que estás protegido. Si tu hosting corre nginx, nada de esto aplica y necesitas bloques server/location en su lugar. Compruébalo con curl -I https://tusitio.com y mira la cabecera Server: antes de pasarte una hora editando un archivo que el servidor nunca lee.
La regla que de verdad importa: nada de PHP en /uploads
Esta es la que merece la pena. wp-content/uploads tiene permisos de escritura para todos por diseño: cada archivo multimedia que subes, cada plugin que guarda un archivo, escribe ahí. Si un atacante consigue colar un archivo .php en ese directorio (a través de un gestor de subidas vulnerable, un campo de imagen que no valida el tipo MIME, un plugin comprometido), la diferencia entre una molestia y un compromiso total con ejecución remota de código está en si el servidor ejecutará ese archivo cuando lo soliciten. Deniega la ejecución y el payload subido queda como un archivo inerte en el disco.
Mete esto en wp-content/uploads/.htaccess (crea el archivo si no existe):
<FilesMatch "\.php$">
Require all denied
</FilesMatch>
Esa es la sintaxis de Apache 2.4. En la versión más antigua, la 2.2, el equivalente es Order Deny,Allow / Deny from all. Mezclar los dos dialectos en un mismo archivo es la causa más común de un 500 Internal Server Error repentino después de “endurecer” la seguridad: si todo el sitio se cae en cuanto guardas, casi siempre es por eso. Comprueba tu versión con apachectl -v.
Protege wp-config.php
wp-config.php contiene las credenciales de tu base de datos y los salts de autenticación. Mientras PHP está funcionando, una petición directa al archivo devuelve una página en blanco: PHP ejecuta el archivo en vez de imprimirlo. El riesgo está en el caso de fallo: si PHP alguna vez se cae, se configura mal durante una migración o el handler se desactiva, Apache sirve el archivo como texto plano y suelta la contraseña de tu base de datos a cualquiera que la pida. Denegar el acceso es un seguro barato contra esos cinco minutos malos:
<Files wp-config.php>
Require all denied
</Files>
Desactiva el listado de directorios
Si alguien visita una carpeta sin index.php y Apache tiene Options +Indexes activado, muestra el contenido: cada archivo de copia de seguridad, cada volcado SQL suelto que se te olvidó. Desactívalo para todo el sitio:
Options -Indexes
Gravedad baja, pero real, y no cuesta nada.
Esa es la lista honesta y esencial. Puedes montar estas reglas —con la sintaxis correcta 2.4 frente a 2.2 para no tirar tu sitio con un 500— usando el generador de .htaccess para WordPress en vez de copiar y pegar de un post de foro escrito para la versión de Apache equivocada.
Lo que NO debes hacer
Bloquear xmlrpc.php “para protegerte de la fuerza bruta”. Esta es la grande que todo el mundo repite y está mal planteada. Sí, el método system.multicall de XML-RPC históricamente permitía a un atacante empaquetar muchos intentos de login en una sola petición: amplificación real. Pero el vector de fuerza bruta abrumadoramente más común son simples peticiones POST a wp-login.php, y bloquear xmlrpc.php no hace nada contra eso. La fuerza bruta se derrota con límites de tasa (rate limiting), contraseñas fuertes y 2FA, no matando un único endpoint. Sí hay una razón legítima para desactivar XML-RPC: su función de pingback se puede abusar para reflexión de DDoS, así que si no usas Jetpack, la app móvil ni los pingbacks, cerrarlo reduce tu superficie de ataque. Pero no te digas a ti mismo que es tu defensa contra la fuerza bruta, porque no lo es.
Ocultar tu versión de WordPress / quitar la etiqueta generator. Eliminar readme.html y la etiqueta <meta name="generator"> da la sensación de estar reforzando la seguridad. Un atacante identifica tu versión a partir de las query strings de los assets encolados, del marcado del editor de bloques y de una docena de pistas más en segundos. No estás ocultando nada; solo te estás dando la sensación de estar ocupado.
Listas gigantes de bloqueo por user-agent y referrer de bots maliciosos. Los user-agents son una única cabecera HTTP falsificable. Esas listas están obsoletas el mismo día que las pegas, no bloquean nada competente, y Apache evalúa cada regex en cada petición: estás pagando un peaje real de rendimiento a cambio de cero seguridad. Sáltatelas.
Bloquear wp-login.php por IP. Genial hasta que tu ISP rota tu dirección y te quedas fuera de tu propio panel de administración. Solo es viable con una IP realmente estática.
Redirecciones para bloquear la enumeración de autores (?author=1). El reescrito de .htaccess que la gente pega para esto está incompleto por sí solo: el endpoint REST /wp-json/wp/v2/users sigue listando los nombres de usuario. Bloquear una ruta mientras la otra sigue abierta es puro teatro.
¿Sigues atascado?
Si una regla tira el sitio con un 500, es la sintaxis: quita el último bloque que añadiste y recarga; eso lo aísla al instante. Si una regla parece no hacer nada, confirma que realmente estás en Apache y que AllowOverride está activado para el directorio (muchos hostings gestionados lo restringen). Construye el archivo a partir de una plantilla probada con el generador de .htaccess, quédate con las tres reglas que importan y descarta el resto.