PRIVACIDAD

Tus preferencias.

Rechazar no limita el acceso a la web. Cerrar sin guardar conserva tu decisión anterior.

Técnicas · siempre activas

Guardamos únicamente tu elección durante un máximo de 12 meses en este navegador.

Consultar proveedores, duración y transferencias

Servidores

Redirecciones en Apache: configuración central y VirtualHost

Redirect, RedirectMatch, RewriteRule y RewriteMap en el VirtualHost de Apache, con cada ejemplo probado en un servidor real y cuatro fallos que reproduje.

¿Quieres aplicarlo a tu web?Hablemos
ResumenPor qué la configuración central y no .htaccess
Puntos clave
  • Si controlas la configuración central de Apache, las redirecciones van en el <VirtualHost>, no en .htaccess: la documentación oficial lo recomienda y se cargan una vez al arrancar, no en cada petición.
  • Para mover una URL o un grupo de URLs, Redirect y RedirectMatch (mod_alias). RewriteRule queda para lo que dependa de condiciones o de tablas.
  • El paso de HTTP a HTTPS y la unificación de www se resuelven con un VirtualHost por puerto y un Redirect permanent, sin una sola condición.
  • Con miles de URLs, RewriteMap en formato dbm evita un archivo de reglas inmanejable. Solo se puede declarar en la configuración central.
  • Probé cada ejemplo en un Apache 2.4.58 real y encontré cuatro fallos que no salen en los tutoriales: barra doble, index.html atrapado, cadena de dos saltos y vhost por defecto.
  • El orden de comprobación es apachectl configtest, graceful y curl -I; el log de rewrite:trace3 queda para cuando algo no cuadra.

Casi todos los tutoriales de redirecciones en Apache acaban en un bloque de .htaccess con RewriteRule, porque es lo que funciona en un hosting compartido. Si administras tu propio servidor, un VPS o la configuración de un cliente, ese es el sitio equivocado: la propia documentación de Apache pide mover esas reglas a la configuración central.

Este artículo trata solo esa configuración central: httpd.conf, sites-available y el VirtualHost. Cada ejemplo lo he ejecutado contra un Apache 2.4.58 propio y lo que ves como resultado es lo que devolvió curl. Si trabajas con .htaccess, tienes la guía de redirecciones en .htaccess.

Dos columnas: configuración central (httpd.conf y VirtualHost, se carga una vez) frente a .htaccess (se lee en cada petición y exige AllowOverride).
Dónde viven las reglas: en la configuración central se leen al arrancar; en .htaccess, en cada petición.

Por qué la configuración central y no .htaccess

La documentación oficial de Apache sobre .htaccess es tajante: si tienes acceso al archivo de configuración principal, pon ahí todo, incluidas las reglas de mod_rewrite. Da dos razones, rendimiento y seguridad. Sobre el rendimiento dice que, con AllowOverride activado, httpd busca archivos .htaccess en cada directorio «whether or not you actually even use them», y que el archivo se carga en cada petición. Las directivas de la configuración principal se cargan una vez al arrancar.

No he medido la diferencia de milisegundos y no voy a inventarla: depende del número de directorios, del sistema de archivos y de la caché del sistema operativo. Lo que sí he comprobado es el otro efecto, el que más sorprende en una auditoría. Con AllowOverride None, un .htaccess con un Redirect se ignora sin avisar; con AllowOverride FileInfo en ese directorio, funciona.

Prueba (mismo .htaccess)Resultado
AllowOverride None404: el Redirect del .htaccess no se lee
AllowOverride FileInfo en ese directorio301 a /nuevo/

Hay un motivo legítimo para seguir con .htaccess: no tener acceso a la configuración. La página «When not to use mod_rewrite» de Apache lo admite expresamente para quien solo dispone de ese archivo.

Redirect y RedirectMatch: mod_alias

La documentación de Apache dice que la redirección simple de una URL, o de una clase de URLs, debe hacerse con Redirect y RedirectMatch en lugar de RewriteRule. Redirect compara por prefijo de segmentos completos y añade al destino el resto de la ruta y la query string. RedirectMatch usa una expresión regular y permite reutilizar los grupos capturados.

<VirtualHost *:443>
    ServerName www.ejemplo.test

    Redirect permanent "/pagina-vieja" "/nuevo/"
    RedirectMatch permanent "^/blog/([0-9]{4})/([0-9]{2})/(.+)$" "/blog/$3"
</VirtualHost>
PeticiónRespuesta
/pagina-vieja301 → /nuevo/
/pagina-viejo y /pagina-vieja2404: no coinciden como segmento completo
/blog/2021/05/mi-post/301 → /blog/mi-post/
/blog/2021/05/mi-post/?utm=a301 → /blog/mi-post/?utm=a

La última fila importa: con RedirectMatch la query string también viajó al destino. La página de mod_alias lo documenta para Redirect y no dice nada explícito de RedirectMatch, así que es un comportamiento que verifiqué en 2.4.58 y que conviene repetir en tu versión.

Tabla con cuatro peticiones y su respuesta real en Apache: Redirect por prefijo de segmentos, 404 en segmentos parciales y RedirectMatch con y sin query.
Resultados reales de Redirect y RedirectMatch en Apache 2.4.58.

Qué código devuelve cada forma

Sin argumento de estado, Redirect devuelve un 302 temporal. Esta es la fuente habitual de redirecciones «permanentes» que Google trata como temporales: si escribes Redirect "/a" "/b" y olvidas permanent, el 302 es el que sale. Estas son las formas que probé.

DirectivaCódigo realCuándo
Redirect permanent301Cambio definitivo de URL
Redirect (sin estado)302Temporal; es el valor por defecto
Redirect gone "/retirado"410, sin destinoContenido eliminado a propósito
Redirect 308308Permanente que conserva el método (POST sigue siendo POST)

Google dice que 301 y 308 indican que la página se ha movido de forma permanente, y que se usen cuando estés seguro de que la redirección no se va a revertir. Cómo trata cada código y qué pasa con la señal que se transfiere lo explico en el artículo sobre tipos de redirección y su efecto en SEO, y el resto de la familia 3xx, 4xx y 5xx está en los códigos de respuesta del servidor. En Apache, una petición POST a la ruta con Redirect 308 devolvió 308, es decir, el servidor no degrada el método; lo que haga el cliente después no lo he probado.

mod_alias frente a mod_rewrite

La documentación de Apache describe mod_rewrite como «a last resort»: con alternativas más simples produce configuraciones confusas, frágiles y difíciles de mantener. Mi criterio, que coincide con esa página, es este.

Necesitas…Usa
Mover una URL o una carpeta a otraRedirect permanent
Mover por patrón (años, extensiones, ids)RedirectMatch
Quitar o fijar la query stringRewriteRule con QSD o QSA
Decidir según una cabecera, cookie o variableRewriteCond + RewriteRule, o un bloque <If>
Consultar una tabla de equivalenciasRewriteMap
Cinco necesidades en filas, cada una con la directiva de Apache recomendada: Redirect, RedirectMatch, RewriteRule con QSD o QSA, RewriteCond o If y RewriteMap.
Qué directiva para cada necesidad. Empieza por la más simple que la resuelva.

RewriteRule dentro del VirtualHost

En el contexto del servidor el patrón se compara con la ruta que va tras el host y antes de la query, y empieza por barra (^/productos/); en .htaccess esa barra no existe, y de ahí que las reglas no sean copiables tal cual entre los dos sitios. Estas tres reglas, probadas, muestran qué pasa con la query string.

RewriteEngine On
RewriteRule "^/productos/(.*)$"  "/tienda/$1" [R=301,L]
RewriteRule "^/sin-query/(.*)$"  "/tienda/$1" [R=301,L,QSD]
RewriteRule "^/con-query/(.*)$"  "/tienda/$1?origen=viejo" [R=301,L,QSA]
PeticiónLocation devuelto
/productos/camisa?c=rojo/tienda/camisa?c=rojo
/sin-query/camisa?c=rojo/tienda/camisa
/con-query/camisa?c=rojo/tienda/camisa?origen=viejo&c=rojo

Una regla con R=301 sin L sigue evaluando las siguientes; en configuración central END (desde 2.3.9) detiene todo el proceso de reescritura. Y un detalle de orden que la documentación indica y que probé: cuando Redirect y RewriteRule afectan a la misma URL, en contexto de servidor o VirtualHost corre primero mod_rewrite. Con Redirect permanent "/choque" y una RewriteRule al 302 sobre la misma ruta, ganó la segunda: respondió 302 al destino de la regla de reescritura.

Las reglas no se heredan entre vhosts

Otro fallo clásico al mover reglas desde .htaccess: las reescrituras del servidor principal no pasan a los VirtualHost. Puse una regla global y la probé en dos vhosts.

VirtualHostPetición a la URL de la regla global
Sin RewriteEngine404: la regla no existe para él
RewriteEngine On + RewriteOptions Inherit301 a /nuevo/

De HTTP a HTTPS con un VirtualHost por puerto

La documentación de Apache dice que, para llevar las URLs http a https, un Redirect en un virtual host HTTP dedicado es el enfoque más limpio. No hay RewriteCond %{HTTPS} ni lógica: todo lo que llega al puerto 80 va al destino, conservando ruta y query.

<VirtualHost *:80>
    ServerName www.ejemplo.test
    ServerAlias ejemplo.test
    Redirect permanent "/" "https://www.ejemplo.test/"
</VirtualHost>

<VirtualHost *:443>
    ServerName ejemplo.test
    Redirect permanent "/" "https://www.ejemplo.test/"
</VirtualHost>

<VirtualHost *:443>
    ServerName www.ejemplo.test
    # SSLEngine, certificados y contenido
</VirtualHost>

En mi laboratorio no tengo TLS, así que usé el puerto 8461 como «el 80» y el 8462 como «el 443» sin cifrado, con la misma estructura de VirtualHost. Esto es lo que devolvió cada Host.

PeticiónResultado
«:80», Host ejemplo.test, /guia.html?x=1301 → https://www.ejemplo.test/guia.html?x=1
«:80», Host www.ejemplo.test, /a/b?q=1301 → https://www.ejemplo.test/a/b?q=1
«:443», Host ejemplo.test, /servicios/?p=2301 → https://www.ejemplo.test/servicios/?p=2
«:443», Host www.ejemplo.test, /200
Host desconocido otro.test301 al canónico en los dos puertos

La última fila es una consecuencia de cómo funcionan los hosts virtuales por nombre: la primera <VirtualHost> definida para un puerto atiende cualquier Host que no coincida con ningún ServerName o ServerAlias. Por eso en mi ejemplo el vhost que redirige va primero: una petición con un dominio inesperado acaba en el canónico en lugar de servir el contenido bajo un host duplicado. Elige a conciencia cuál es tu vhost por defecto.

Flujo de tres VirtualHost: el del puerto 80 redirige a https www, el apex del 443 también, y solo el www del 443 sirve contenido con 200.
Tres VirtualHost, un único destino que responde 200.

Qué versión del dominio elegir (https, www o sin www) y por qué debe ser una sola es otra conversación, y la cuento en el artículo sobre www, sin www y http. Aquí solo importa que el servidor la imponga con un salto, no con dos.

Una alternativa: el bloque If

Desde Apache 2.4 puedes condicionar un Redirect con <If>, que es lo que muestra la propia documentación en «When not to use mod_rewrite» para redirigir al host canónico sin RewriteCond. Funcionó: una petición con Host distinto del canónico devolvió 301 con la ruta y la query intactas. Con el host correcto (el canónico), el bloque no actúa y la petición sigue su curso normal.

RewriteMap para tablas grandes

Con cientos de URLs antiguas, una regla por URL es ilegible. RewriteMap convierte la tabla en un archivo aparte y deja una sola regla. Solo se puede definir en la configuración del servidor o del VirtualHost, no en .htaccess ni en <Directory>, y es una razón de peso para tener acceso a la configuración central en una migración grande.

# maps/redir.txt: una pareja por línea, separada por espacio
guia-seo-2019        /blog/guia-seo/
producto-viejo-1     /tienda/producto-nuevo-1/

RewriteEngine On
RewriteMap tabla "txt:/etc/apache2/maps/redir.txt"
RewriteCond "${tabla:$1|NONE}" "!=NONE"
RewriteRule "^/txt/(.+)$" "${tabla:$1}" [R=301,L]

# Versión dbm, más rápida con tablas grandes
RewriteMap tabladbm "dbm:/etc/apache2/maps/redir.map"
RewriteRule "^/dbm/(.+)$" "${tabladbm:$1|/no-existe/}" [R=301,L]

El archivo dbm se genera con httxt2dbm -i redir.txt -o redir.map. Según la documentación, un dbm es mucho más rápido porque está indexado y un texto plano no. Los resultados que vi:

PruebaResultado
Clave que existe en el mapa de texto301 a /blog/guia-seo/
Clave que no existe, con RewriteCond de guarda404 (la regla no se aplica)
Clave que no existe, sin guarda ni valor por defecto301 a /: Apache trata un valor vacío como clave ausente y redirige a la raíz
Línea añadida al .txt, sin reiniciarFuncionó al momento: se recarga al cambiar su fecha de modificación
Línea añadida al .txt con el mapa dbmSin cambios hasta volver a ejecutar httxt2dbm

Esa tercera fila es el fallo de un mapa mal escrito: sin guarda ni valor por defecto, una clave desconocida se convierte en una redirección a la home, que es justo lo que se parece a un soft 404. Pon siempre un valor por defecto con | o una condición. En una migración SEO con miles de URLs, esa tabla es además el documento que cruzas con los rastreos antes y después.

Diagrama de una petición que pasa por una única RewriteRule, consulta un mapa txt o dbm y devuelve un 301 al destino, con la nota de regenerar el dbm con httxt2dbm.
Una regla, una tabla. El mapa dbm hay que regenerarlo al cambiar el texto.

Cuatro fallos que reproduje

Estos son los que no salen en la documentación de referencia y me encontré al probar. Los dos primeros se cuelan en revisiones de sitios reales.

Barra doble con un destino que termina en barra

Con Redirect permanent "/pagina-vieja" "/nuevo/", una petición a /pagina-vieja/sub/x.html?a=1 devolvió Location: /nuevo//sub/x.html?a=1. El prefijo /pagina-vieja se sustituye y el resto de la ruta, que empieza por barra, se añade detrás de la barra final del destino. Si el origen y el destino van sin barra final, el resultado es correcto: Redirect permanent "/old" "/nuevo" llevó /old/sub?x=1 a /nuevo/sub?x=1.

RedirectMatch que atrapa el index.html

Para quitar la extensión puse RedirectMatch permanent "^/(.+)\.html$" "/$1/". La home dejó de cargar y redirigió a /index/. El motivo: mod_dir reescribe / internamente a /index.html y esa subpetición también coincide con la regex. Con una exclusión funciona:

RedirectMatch permanent "^/(?!index\.html$)(.+)\.html$" "/$1/"

Resultado: / responde 200, /index.html responde 200 y /guia.html?v=2 devuelve 301 a /guia/?v=2.

Una redirección que acaba en dos saltos

Un Redirect permanent "/old" "/nuevo" hacia un directorio que existe produjo dos saltos: /old → /nuevo → /nuevo/, porque mod_dir añade la barra final con su propio 301. Lo vi siguiendo la cadena con curl -L. La solución es apuntar directamente a /nuevo/.

El vhost por defecto sirve lo que no esperabas

Ya lo vimos en la sección de HTTPS: sin un vhost de captura al principio, un Host que nadie configuró se sirve con el contenido del primer VirtualHost del puerto. Es un caso típico de contenido duplicado bajo dominios que no controlas.

Un efecto relacionado con la caché del navegador y de Googlebot: en mis pruebas la respuesta de un Redirect llegó sin Cache-Control ni Expires. No he medido cuánto tiempo guarda cada navegador un 301, así que si dudas de un destino, prueba primero con 302 y pasa a permanent cuando esté confirmado.

Cuatro tarjetas con los fallos reproducidos: barra doble, RedirectMatch que atrapa index.html, cadena de dos saltos y vhost por defecto.
Cuatro fallos reales de la configuración central, con su causa.

Aplicar los cambios sin tirar el servidor

Los cambios de la configuración central no se aplican solos. Antes de recargar, valida. apachectl configtest (equivalente a apachectl -t) analiza los archivos y responde Syntax OK o el error. Después, apachectl graceful reinicia el demonio sin abortar las conexiones abiertas; según la documentación, también ejecuta la comprobación de sintaxis antes de reiniciar.

apachectl configtest
apachectl graceful

Con graceful cargué vhosts nuevos en mi instancia sin parar el servicio. En Ubuntu y Debian, los sitios viven en sites-available y se activan con a2ensite y los módulos con a2enmod; en otras distribuciones las rutas cambian, y no he probado esas variantes.

Dos errores que el configtest detectó en mi laboratorio:

ErrorMensaje
Redirect permanent "/pagina-vieja" (sin destino)AH00526: Syntax error on line 19 … URL to redirect to is missing
RewriteEngine con el módulo sin cargarInvalid command 'RewriteEngine', perhaps misspelled or defined by a module not included in the server configuration

Cuidado: configtest valida la sintaxis, no la intención. Una regex demasiado amplia o un destino mal escrito pasan la prueba con Syntax OK.

Depurar una regla que no hace lo que debe

El orden que sigo: primero curl con la cabecera Host (así pruebas un vhost sin tocar DNS ni /etc/hosts), luego el log. Para ver la cadena entera sin resolver el dominio, --connect-to manda el nombre a tu servidor de pruebas.

curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' -H 'Host: www.ejemplo.test' http://127.0.0.1:8462/pagina-vieja
curl -sIL --connect-to www.ejemplo.test:80:127.0.0.1:8462 http://www.ejemplo.test/old

Si la respuesta no se explica, sube el log de mod_rewrite en el vhost afectado con LogLevel alert rewrite:trace3. Apache avisa de que un nivel por encima de trace2 ralentiza mucho el servidor y que debe usarse solo para depurar; quítalo al terminar. Para una petición a una regla global, el log mostró el patrón aplicado, el resultado y el código final:

applying pattern '^/global-viejo$' to uri '/global-viejo'
rewrite '/global-viejo' -> '/nuevo/'
redirect to http://www.ejemplo.test/nuevo/ [REDIRECT/301]

Para filtrar solo esas líneas del error_log, tail -f error_log | fgrep '[rewrite:' es lo que sugiere la documentación.

Cuatro pasos de depuración en Apache: configtest, graceful, curl con cabecera Host y log rewrite:trace3, con el aviso de quitar el trace al terminar.
Del más barato al más caro: validar, recargar, probar con curl y, solo si hace falta, el log.

Cómo lo tengo en mi web

cristofercruz.net usa un .htaccess en la raíz, así que lo que tengo hoy no es el escenario de este artículo y prefiero decirlo claro. Lo que sí puedo afirmar leyendo el código del sitio:

  • El .htaccess no contiene ninguna redirección Redirect ni RewriteRule con R=301. Sus RewriteRule son dos reglas con [F,L] que bloquean tools/ y .git/, más ErrorDocument 404 y 403 hacia /404.php.
  • No hay redirecciones de dominio en PHP: no hay lógica de cabecera Location sobre el host ni sobre el protocolo en el código de las páginas.
  • Las notas del propio proyecto marcan como «pendiente de verificar en el hosting» las redirecciones https y no-www, precisamente porque dependen del servidor.

Lo que no puedo saber desde el código: si el servidor es Apache con certeza, qué VirtualHost tiene, si hay una redirección central de HTTP a HTTPS o de apex a www, ni qué valor de AllowOverride aplica. Un hosting compartido suele gestionar eso por su cuenta, pero es una suposición mía y no un dato. La comprobación se hace desde fuera, con curl -I a las cuatro variantes del dominio, y es lo que corresponde hacer antes de tocar nada.

Dos columnas: lo que se ve en el código de cristofercruz.net (htaccess con dos reglas F, ErrorDocument y sin redirecciones 301) y lo que no se puede saber desde el código.
Lo que el código demuestra de mi web y lo que solo se verifica desde fuera.

Si tu hosting no te deja tocar la configuración central, la alternativa es el archivo por directorio que cubro en el artículo de redirecciones en .htaccess. Y si tu servidor es otro, el equivalente en nginx usa return 301 en lugar de Redirect.

Cómo auditar las redirecciones de Apache

  1. Lanza curl -I a las cuatro variantes (http:// y https://, con y sin www) y comprueba que tres devuelven un único 301 al canónico y una responde 200.
  2. Sigue cada cadena con curl -IL y cuenta los saltos. Cualquier cadena de más de un salto es un fallo a corregir.
  3. Prueba una URL con query string y comprueba que llega entera al destino.
  4. Pide un Host que no existe y mira qué responde el vhost por defecto.
  5. Revisa los códigos: 301 o 308 para lo definitivo, 302 solo si es temporal de verdad, 410 para lo eliminado.
  6. Rastrea la lista antigua de URLs con un crawler y cruza el resultado con el informe de páginas de Search Console tras unas semanas.
Auditoría SEO

Ver auditoría SEO

Preguntas frecuentes

¿Redirect o RewriteRule para una redirección 301?

Para una URL fija o un grupo con un patrón simple, Redirect o RedirectMatch. La documentación de Apache pide reservar RewriteRule para cuando no hay alternativa más simple, por ejemplo condiciones sobre cabeceras o consultas a un RewriteMap.

¿Un Redirect conserva la query string?

Sí. La documentación lo indica para Redirect y yo comprobé que RedirectMatch también la conserva en 2.4.58, aunque su página no lo dice explícitamente. Con RewriteRule la query se arrastra por defecto; usa QSD para descartarla.

¿Es peor .htaccess para el SEO?

No he encontrado ninguna documentación que ligue .htaccess con un peor posicionamiento. Lo que documenta Apache es un coste de rendimiento en el servidor y de seguridad. El efecto SEO llega si ese coste se convierte en un TTFB alto.

¿Hay que reiniciar Apache tras cambiar una redirección?

En la configuración central sí hace falta recargar: apachectl configtest y después apachectl graceful. En .htaccess no, porque se lee en cada petición. Un mapa txt de RewriteMap se recargó solo al modificar el archivo en mi prueba.

¿Qué pasa con las peticiones POST en un 301?

La documentación de mod_alias avisa de que los POST se descartan. Si necesitas que la redirección permanente conserve el método, Redirect 308 devolvió 308 en mi prueba. No he comprobado cómo reacciona cada cliente.

¿Cómo evito una cadena de redirecciones?

Apunta siempre al destino final, con su barra final y su protocolo definitivo. En mi laboratorio, un destino sin barra hacia un directorio añadió un segundo salto de mod_dir.

¿RewriteMap sirve en un hosting compartido?

No: solo se puede definir en la configuración del servidor o del VirtualHost, no en .htaccess. Si no tienes acceso, tendrás que repartir la tabla en reglas individuales.

Referencias

  1. Apache mod_alias: Redirect y RedirectMatch Apache HTTP Server 2.4
  2. Apache mod_rewrite: RewriteRule, RewriteMap, flags y LogLevel Apache HTTP Server 2.4
  3. When not to use mod_rewrite Apache HTTP Server 2.4
  4. Redirecting and Remapping with mod_rewrite Apache HTTP Server 2.4
  5. Using RewriteMap Apache HTTP Server 2.4
  6. Apache HTTP Server Tutorial: .htaccess files Apache HTTP Server 2.4
  7. apachectl: Apache HTTP Server Control Interface Apache HTTP Server 2.4
  8. Redirects and Google Search Google Search Central

Sigue leyendo

Servidores

· 15 min

Redirecciones .htaccess: guía con ejemplos probados en Apache

Redirect o RewriteRule, flags, https y www, bucles y rendimiento: ejemplos probados en Apache con el resultado de curl, y qué hay en el .htaccess de mi web.

Leer el artículo: Redirecciones .htaccess: guía con ejemplos probados en Apache
Servidores

· 14 min

Redirecciones en Nginx: return, rewrite y map con ejemplos

Cómo escribir redirecciones 301 en Nginx sin cadenas ni bucles: return frente a rewrite, bloques server, location, map, barra final y 410, con pruebas reales.

Leer el artículo: Redirecciones en Nginx: return, rewrite y map con ejemplos
Servidores

· 14 min

Redirecciones SEO: tipos 301, 302, 307 y 308 según Google

Tipos de redirecciones, qué documenta Google de cada una, cadenas, 410 frente a 301 y cómo las trato y audito en mi propia web.

Leer el artículo: Redirecciones SEO: tipos 301, 302, 307 y 308 según Google

Tu próximo paso empieza con una conversación.

Cuéntame qué necesitas conseguir con tu web.

Hablemos de tu proyecto