Saltar al contenido
Servidor y .htaccess

Reglas de redirección en .htaccess de WordPress: 301 bien hechas

Mueve una URL sin perder posiciones: qué directiva de redirección usar en el .htaccess de WordPress, dónde deben ir las reglas y cómo probarlas sin quedarte fuera.

Publicada

¿Vas a mover una URL y quieres que la antigua conserve sus posiciones? Necesitas una 301 en .htaccess, colocada donde WordPress no la borre, escrita con la directiva correcta y apuntando a la URL exacta que WordPress sirve de verdad. Si fallas en cualquiera de esas tres cosas acabas con una cadena de redirecciones, un bucle o un 500 que te deja fuera de wp-admin.

La decisión, la regla de colocación y la prueba, en ese orden.

Qué directiva: Redirect, RedirectMatch o RewriteRule

Hay dos módulos de Apache en juego y no son intercambiables.

Redirect (mod_alias) — una ruta conocida a un destino. Lo más simple que funciona:

Redirect 301 /old-page/ https://example.com/new-page/

Dos comportamientos que conviene conocer. Coincide por prefijo de ruta, no por cadena exacta, así que /old-page/ también captura /old-page/anything/ y añade /anything/ al destino. Y arrastra la cadena de consulta automáticamente.

RedirectMatch (mod_alias) — una expresión regular que cubre muchas URL. Así es como se mueve una sección entera:

RedirectMatch 301 ^/blog/(.*)$ https://example.com/articles/$1

El grupo de captura (.*) pasa a ser $1 en el destino, así que /blog/hello-world/ aterriza en /articles/hello-world/. Fíjate en la barra inicial: los patrones de mod_alias coinciden con la ruta completa de la URL.

RewriteRule (mod_rewrite) — obligatoria cuando la redirección depende de una condición: el host, el protocolo, una cadena de consulta, un agente de usuario. Nada en mod_alias puede evaluar eso.

RewriteEngine On
RewriteCond %{HTTP_HOST} ^example\.com$ [NC]
RewriteRule ^(.*)$ https://www.example.com/$1 [R=301,L]

En un .htaccess por directorio, al patrón se le quita la barra inicial: por eso aquí es ^(.*)$ y en el ejemplo de mod_alias es ^/blog/. Confundir esto es el motivo más común de que una regla copiada no haga absolutamente nada.

Usa mod_alias por defecto. Recurre a RewriteRule solo cuando necesites una condición. Menos expresiones regulares, menos formas de equivocarse.

Dónde deben ir las reglas

Las redirecciones personalizadas van por encima de la línea # BEGIN WordPress. No dentro.

# --- custom redirects ---
Redirect 301 /old-page/ https://example.com/new-page/
RedirectMatch 301 ^/blog/(.*)$ https://example.com/articles/$1

# BEGIN WordPress
# The directives (lines) between "BEGIN WordPress" and "END WordPress" are
# dynamically generated, and should only be modified via WordPress filters.
<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

Dos razones independientes:

  1. WordPress reescribe su propio bloque. Guardar Ajustes → Enlaces permanentes llama a flush_rewrite_rules(), que descarta todo lo que hay entre los marcadores y lo vuelve a generar. Una regla que pongas dentro desaparece la próxima vez que alguien toque esa pantalla, quizá meses después y sin que nadie relacione ambos hechos.
  2. El orden decide quién gana. El bloque de WordPress termina con una regla comodín que manda toda petición que no sea un archivo ni un directorio a index.php. Una RewriteRule colocada después nunca llega a ejecutarse.

Una complicación que conviene admitir: mod_alias y mod_rewrite no se ejecutan realmente en el orden del archivo. Apache procesa mod_alias durante la traducción de la URL y el mod_rewrite por directorio más tarde, en la fase de fixup, así que una línea Redirect puede ganarle a una RewriteRule que aparece más arriba en el archivo. Si te ves necesitando los dos en rutas que se solapan, elige un módulo para esa ruta y quédate dentro de él. Depurar una pelea entre mod_alias y mod_rewrite no compensa la hora que cuesta.

Barras finales

La propia redirección canónica de WordPress añade una barra final a la mayoría de enlaces permanentes. Así que esto:

Redirect 301 /old-page/ https://example.com/new-page

produce dos saltos: tu 301 hacia la URL sin barra y luego la 301 del propio WordPress añadiéndola. Las cadenas siguen transmitiendo señales de posicionamiento, pero desperdician presupuesto de rastreo y añaden un viaje de ida y vuelta a cada visitante.

Carga primero el destino en un navegador, copia la URL tal cual se queda en la barra de direcciones y usa esa. Si tu estructura de enlaces permanentes termina en .html o no lleva barra final, haz coincidir eso en su lugar. Aquí no hay una respuesta universalmente correcta, solo «haz coincidir lo que sirve WordPress».

Probar sin dejarte fuera

.htaccess se lee en cada petición. Un error de sintaxis devuelve un 500 en todo el sitio, wp-admin incluido, así que la vía de recuperación tiene que existir antes de necesitarla.

Haz primero una copia del archivo. Por SSH:

cp /path/to/wordpress/.htaccess /path/to/wordpress/.htaccess.bak

Si el sitio da un 500, renombra el archivo roto encima del original y el sitio vuelve al instante. Ten una sesión SFTP ya abierta en otra ventana: entrar desde cero con el sitio caído es donde empieza el pánico. Ten en cuenta que apachectl configtest no analiza .htaccess, así que informará de una configuración sana mientras tu sitio está muerto.

Prueba con curl, no con un navegador. Los navegadores cachean las respuestas 301 con dureza y te enseñarán tan tranquilos el resultado de ayer:

curl -sI https://example.com/old-page/ | head -n 5

Fíjate en dos cosas: la línea de estado debe decir 301 y la cabecera Location: debe ser la URL final exacta. Para ver la cadena completa, síguela:

curl -sIL https://example.com/old-page/ | grep -E '^(HTTP|Location)'

Más de una línea HTTP/1.1 301 significa que has construido una cadena. Más de unas cinco significa que has construido un bucle, y curl se detendrá y te lo dirá.

Usa una 302 mientras aún no lo tengas claro. Una 302 no se cachea igual, así que el error es reversible en segundos. Pasa a 301 cuando curl te muestre el destino que pretendías. Esto no cuesta nada y ha salvado más sitios que cualquier otra costumbre de este artículo.

Si una regla parece no hacer nada en absoluto, confirma que Apache está leyendo siquiera el archivo. AllowOverride None en el directorio hace que Apache ignore .htaccess por completo, y nginx lo ignora del todo: comprueba la cabecera Server: con curl -I antes de ponerte a depurar expresiones regulares. Para montar el bloque con la sintaxis adecuada a tu versión de Apache y a tu ruta de instalación, usa el generador de .htaccess para WordPress.

Cuándo no conviene usar .htaccess

Para un puñado de redirecciones que los editores deban gestionar por su cuenta, un plugin de redirecciones que guarde las reglas en la base de datos es la mejor herramienta: sobrevive a las migraciones de alojamiento y no requiere SSH. La contrapartida es real: PHP tiene que arrancar para servir la redirección, lo que es más lento que Apache respondiendo directamente.

Usa .htaccess para movimientos estructurales y permanentes: un cambio de dominio, el renombrado de una sección, forzar https o www. Usa un plugin para redirecciones editoriales puntuales. Hacer ambas cosas está bien mientras sepas qué capa es dueña de cada URL, porque una redirección definida en dos sitios es un fallo esperando a una tarde mala.

FAQ

Preguntas

¿Dónde van las redirecciones personalizadas en el archivo .htaccess de WordPress?

Por encima de la línea # BEGIN WordPress, nunca entre los marcadores. WordPress regenera todo lo que hay dentro de esos marcadores cada vez que alguien guarda la pantalla de Enlaces permanentes, así que una regla colocada dentro se borra sin avisar. La colocación también importa por el orden: el bloque de WordPress termina con una regla comodín que manda las peticiones no coincidentes a index.php.

¿Debo usar Redirect, RedirectMatch o RewriteRule para una 301?

Usa Redirect para una única ruta conocida, RedirectMatch cuando un solo patrón cubre muchas URL y RewriteRule cuando la redirección depende de una condición como el host, el protocolo o la cadena de consulta. Redirect y RedirectMatch vienen de mod_alias y son más simples. RewriteRule viene de mod_rewrite y es la única que puede evaluar condiciones.

¿Por qué mi redirección en .htaccess provoca un bucle de redirección?

Normalmente el destino sigue coincidiendo con la regla que envió allí al visitante, así que la regla se dispara una y otra vez. La otra causa habitual es forzar https detrás de un balanceador de carga o un CDN, donde el servidor ve http plano en todas las peticiones aunque el visitante ya esté en https. Evalúa en su lugar la cabecera X-Forwarded-Proto.

¿Importa la barra final en una redirección de WordPress?

Sí. La redirección canónica de WordPress añade una barra final a la mayoría de enlaces permanentes, así que apuntar una 301 a una URL sin barra produce dos saltos en vez de uno. Las redirecciones encadenadas siguen transmitiendo señales de posicionamiento, pero desperdician presupuesto de rastreo y ralentizan al visitante. Haz coincidir la URL final exacta que sirve WordPress, incluida la barra.

¿Cómo pruebo una redirección de .htaccess sin romper mi sitio?

Ejecuta curl con la opción de solo cabeceras contra la URL antigua y lee el código de estado y la cabecera Location antes de fiarte del navegador. Los navegadores cachean las respuestas 301 de forma agresiva y te mostrarán un resultado obsoleto. Guarda antes una copia del archivo que funciona, porque un error de sintaxis en .htaccess devuelve un 500 en todas las páginas, wp-admin incluido.

¿Por qué todo mi sitio devolvió un error 500 tras añadir una redirección?

Una sola directiva mal formada en .htaccess tumba todas las URL bajo ese directorio, incluido el administrador. Las causas habituales son un envoltorio IfModule sin cerrar, una línea RewriteEngine On que falta o sintaxis de acceso de Apache 2.2 y 2.4 mezclada en el mismo archivo. Elimina el último bloque que añadiste y recarga para confirmarlo.