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 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.

¿Quieres aplicarlo a tu web?Hablemos
Resumenreturn o rewrite: cuál usar y qué devuelve cada uno
Puntos clave
  • Para una redirección fija, return 301 basta: una directiva, sin regex. rewrite ... permanent queda para cuando necesitas capturar parte de la ruta.
  • Un return con URL fija pierde la query string. Para conservarla, añade $is_args$args; rewrite la añade solo.
  • Un bloque server por versión de la web y un único salto desde cualquier entrada.
  • En location, el exacto gana, después el prefijo más largo y después la primera regex que encaje. Una regex puede anular un prefijo que creías protegido.
  • Para cientos o miles de redirecciones, un map sobre $uri en un archivo aparte; con 30.000 reglas tuve que subir los límites de hash.
  • Se depura con nginx -t, curl -sIL y, si hace falta, rewrite_log. Todos los ejemplos están probados con un Nginx real.

La redirección de Nginx más habitual cabe en una línea, y casi todos los fallos que he visto vienen de lo que esa línea no dice: qué pasa con la query string, qué hace el servidor con la barra final o cuántos saltos da una URL antes de llegar a su destino.

Esta guía cubre return, rewrite, location, map, dominios antiguos, 410, el uso de if, los errores que se repiten y cómo depurar. Probé cada ejemplo con Nginx 1.24.0 (Ubuntu) en puertos de prueba y curl. En los listados escribo listen 80 y listen 443; en mi entorno los cambié por puertos altos, desactivé port_in_redirect para que el Location no muestre ese puerto y no probé IPv6.

Dos columnas: a la izquierda una cadena de tres saltos 301 desde http://www.example.com/old-page/ que acaba en /new-page/ y pierde el parámetro utm; a la derecha un único salto 301 que conserva el parámetro.
Misma URL de entrada, dos configuraciones. A la izquierda, cada capa redirige por su cuenta (3 saltos y la query string perdida); a la derecha, un solo salto. Resultados reales de mi Nginx de prueba.

return o rewrite: cuál usar y qué devuelve cada uno

Qué código elegir (301, 302, 307, 308) por razones de SEO lo explico en la guía de tipos de redirecciones SEO. Aquí me quedo con cómo se escribe en Nginx. Según la documentación de Google, 301 y 308 son una señal fuerte de que el destino debe ser la URL canónica; 302 y 307, una señal débil.

return detiene el procesamiento y devuelve un código. Para 301, 302, 303, 307 y 308 acepta una URL de destino con variables; si das una ruta local (/destino), Nginx forma la URL completa con el esquema de la petición. rewrite aplica una regex sobre la URI, ejecuta sus directivas en orden y, si el reemplazo empieza por http://, https:// o $scheme, devuelve la redirección. Con el flag permanent da 301 y con redirect da 302.

SituaciónDirectivaMotivo
Una URL concreta a otra, o un dominio enteroreturn 301 URLSin regex que evaluar ni capturas que gestionar
Cambiar un patrón de ruta (/blog/(.*) a /articulos/$1)rewrite ... permanentCaptura grupos y añade la query string sola
Muchas reglas independientesmap + returnUna tabla en archivo aparte
Código 307 o 308returnrewrite solo emite 301 o 302
Tabla con cinco reglas de Nginx y el Location que producen para una petición con parámetros: return con URL fija pierde la query string, return con is_args y args la conserva, rewrite permanent la conserva, rewrite con interrogación final la descarta y un prefijo con return pierde la subruta.
Cinco reglas, cinco resultados con una petición que lleva parámetros. Solo dos conservan la query string sin que lo pidas.

Query string: lo que conserva cada forma

location = /fija      { return 301 /destino; }
location = /conserva  { return 301 /destino$is_args$args; }
location ~ ^/viejo/(.*)$ { return 301 /nuevo/$1$is_args$args; }

rewrite ^/antiguo/(.*)$ /nuevo/$1 permanent;
rewrite ^/limpia/(.*)$  /nuevo/$1? permanent;

location /seccion/ { return 301 /nueva-seccion/; }

Lo que devolvió mi Nginx con curl -sI:

/fija?a=1               -> 301 Location: http://example.com/destino
/conserva?a=1&b=2       -> 301 Location: http://example.com/destino?a=1&b=2
/viejo/pagina?a=1       -> 301 Location: http://example.com/nuevo/pagina?a=1
/antiguo/pagina?a=1     -> 301 Location: http://example.com/nuevo/pagina?a=1
/limpia/pagina?a=1      -> 301 Location: http://example.com/nuevo/pagina
/seccion/sub/pagina     -> 301 Location: http://example.com/nueva-seccion/

La documentación dice que, si el reemplazo de rewrite lleva argumentos nuevos, los antiguos se añaden detrás, y que un ? final lo evita. Un location de prefijo con return no arrastra el resto de la ruta: /seccion/sub/pagina acaba en /nueva-seccion/. Si quieres conservar el sufijo, necesitas captura (rewrite o un location ~).

Un bloque server por cada versión de la web

La documentación de Nginx descarta el if ($host = ...) para redirigir entre www y no-www («wrong, cumbersome, and ineffective») y propone un server separado con return 301. Para las cuatro versiones de un dominio (http y https, con y sin www) bastan tres bloques:

server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://example.com$request_uri;
}

server {
    listen 443 ssl;
    server_name www.example.com;
    ssl_certificate     /etc/ssl/example.com.pem;
    ssl_certificate_key /etc/ssl/example.com.key;
    return 301 https://example.com$request_uri;
}

server {
    listen 443 ssl;
    server_name example.com;
    ssl_certificate     /etc/ssl/example.com.pem;
    ssl_certificate_key /etc/ssl/example.com.key;
    root /var/www/example.com;
}

El primero recoge todo el tráfico HTTP, con o sin www, y lo manda a la versión canónica en un solo salto. El segundo cubre HTTPS con www, que necesita un certificado válido para que el navegador vea la redirección. $request_uri conserva ruta y query string tal como llegaron.

Tres bloques server: listen 80 para example.com y www con return 301, listen 443 para www con return 301 y listen 443 para example.com que sirve el contenido; debajo, cuatro peticiones de entrada que llegan a https://example.com en un solo salto.
Tres bloques cubren las cuatro versiones. Cualquier entrada llega a la canónica con un único 301.
http://example.com/blog/post.html?utm=1   -> 301 https://example.com/blog/post.html?utm=1
https://www.example.com/blog/?a=1&b=2     -> 301 https://example.com/blog/?a=1&b=2
https://example.com/blog/                 -> 200

Nginx compara el Host con server_name en este orden: nombre exacto, comodín inicial, comodín final y primera regex. Si nada encaja, responde el servidor por defecto del puerto, una propiedad de listen y no del nombre. En mi prueba, un Host desconocido cayó en el primer bloque y se fue a https://example.com/. Si en ese bloque pones return 301 https://$host$request_uri, Nginx devuelve el Host que le manden: con Host: sitio-ajeno.test obtuve Location: https://sitio-ajeno.test/algo?q=1. Escribe el dominio canónico a mano. Para elegir cuál debe ser la versión canónica, tienes la guía de versiones www, no-www, http y https y la de etiquetas canonical.

location: exactos, prefijos y regex, y su orden

Muchas redirecciones «que no funcionan» son un location que nunca se ejecuta. El orden de la documentación: Nginx busca primero entre los prefijos y recuerda el más largo; si ese prefijo lleva ^~, se detiene ahí. Si no, evalúa las regex en el orden en que aparecen y se queda con la primera que encaje; si ninguna encaja, usa el prefijo recordado. Un = exacto termina la búsqueda al instante. Todos los tipos comparan solo la ruta, sin la query string.

location = /inicio       { return 200 "exacta (= /inicio)\n"; }
location /promo/         { return 200 "prefijo /promo/\n"; }
location ^~ /imagenes/   { return 200 "prefijo ^~ /imagenes/\n"; }
location ~ \.html$       { return 200 "regex ~ .html\n"; }
location ~* \.(jpg|png)$ { return 200 "regex ~* jpg|png\n"; }
location /               { return 200 "prefijo /\n"; }
PeticiónRespondePor qué
/inicio y /inicio?x=1exactaLa query string no cuenta
/inicio/ y /Inicioprefijo /La barra y las mayúsculas cambian la URL
/promo/oferta.htmlregex ~ .htmlLa regex gana al prefijo sin ^~
/imagenes/foto.jpgprefijo ^~^~ impide evaluar las regex
/otra/foto.JPGregex ~*~* ignora mayúsculas
Cuatro pasos numerados del orden de evaluación de location: exacto con igual, prefijo más largo que se recuerda, regex en orden de aparición y, si ninguna encaja, el prefijo recordado; con el ejemplo de /promo/oferta.html que lo resuelve una regex.
El orden de evaluación de location y un caso real: /promo/oferta.html no llega al prefijo /promo/.

Si redirijo una sección con location /promo/ y existe una regex por extensión, las URL con .html de esa sección se saltan mi redirección. Con ^~ en el prefijo se arregla.

Barra final: añadirla o quitarla sin bucles

Elige una convención y aplícala a todo el sitio. Para añadir la barra a las rutas sin extensión, una regla basta:

rewrite ^([^.?]*[^/])$ $1/ permanent;

En mi prueba, /blog pasó a /blog/, /blog?a=1 a /blog/?a=1 y /blog/post.html o /logo.jpg respondieron 200 sin tocarse.

Quitarla es donde aparece el problema. Con rewrite ^/(.+)/$ /$1 permanent; y un directorio real en disco, las URL /blog/ y /blog se mandaron la una a la otra sin parar: curl -L llegó al límite de diez saltos. El motivo es que Nginx, por su cuenta, redirige un directorio sin barra a su versión con barra. Lo observé en la práctica; en la documentación aparece de forma explícita solo para location de prefijo con proxy_pass, fastcgi_pass y similares. La solución que probé es servir el directorio como archivo con try_files:

rewrite ^/(.+)/$ /$1 permanent;

location / {
    try_files $uri $uri/index.html =404;
}

Con ella, /blog/ saltó a /blog y este respondió 200. Un apunte sobre mi entorno: esa redirección automática incluye el puerto si no es el estándar (me salió https://example.com:8362/blog/); en producción, en 80 o 443, no aparece.

Dos flujos: quitar la barra con rewrite sobre un directorio real genera un bucle entre /blog/ y /blog que curl corta a los diez saltos; con try_files dollar uri y dollar uri barra index.html hay un solo salto y respuesta 200.
Quitar la barra sobre un directorio real choca con la redirección automática de Nginx. try_files evita el bucle.

map para tablas de redirecciones grandes

Con decenas de redirecciones, un location por regla se vuelve inmanejable. map crea una variable que depende de otra y se evalúa solo cuando se usa. Mi diseño usa dos map, un único salto y los tres bloques de antes:

map $uri $nueva_ruta {
    default "";
    include /etc/nginx/redirects.map;
}

map $nueva_ruta $destino {
    ""      $request_uri;
    default $nueva_ruta$is_args$args;
}

server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://example.com$destino;
}
# (el server de https://www... igual, con su ssl_certificate)

server {
    listen 443 ssl;
    server_name example.com;
    # ssl_certificate ...
    root /var/www/example.com;

    if ($nueva_ruta) {
        return 301 https://example.com$destino;
    }
}

Y el archivo redirects.map, con claves exactas y una regex con captura con nombre:

/old-page/                   /new-page/;
/blog/viejo-post/            /blog/nuevo-post/;
/producto-retirado/          /tienda/;
~^/categoria/(?<c>[^/]+)/$   /blog/$c/;

map busca por este orden: cadena exacta, máscara de prefijo, máscara de sufijo, primera regex y default. Resultados reales, siempre a un salto:

http://www.example.com/old-page/?utm=1     -> https://example.com/new-page/?utm=1
https://www.example.com/categoria/seo/     -> https://example.com/blog/seo/
http://www.example.com/caf%C3%A9/?q=a%20b  -> https://example.com/caf%C3%A9/?q=a%20b  (sin regla)
http://example.com/producto-retirado       -> https://example.com/producto-retirado    (sin barra: sin regla)
Flujo de cuatro pasos: la URI /old-page/ entra al primer map que da nueva_ruta, el segundo map construye destino con la query string y return 301 lo envía; debajo, dos avisos: usar uri y no request_uri como clave, y subir los límites de hash con 30.000 reglas.
Dos map encadenados: el primero decide si hay regla; el segundo construye el destino con su query string.

Clave en $uri, no en $request_uri

Con $request_uri como clave, /old-page/ daba 301 pero /old-page/?utm=1 respondía 200 sin redirigir: incluye la query string y la clave exacta no encaja. $uri es la ruta normalizada y sin argumentos, aunque decodificada; por eso el destino de las URL sin regla sale de $request_uri y conserva los %C3%A9 tal cual.

Límites de hash con tablas largas

Generé un archivo de 30.000 reglas con claves de hasta 70 caracteres. Con los valores por defecto, nginx -t falló:

[emerg] could not build map_hash, you should increase map_hash_bucket_size: 64

Subir solo map_hash_bucket_size a 128 convirtió el error en un aviso (could not build optimal map_hash). Con estos valores, el aviso desapareció:

map_hash_bucket_size 512;
map_hash_max_size    65536;

Con esa tabla, validar y arrancar tardó algo más de medio segundo y la regla de prueba redirigió bien. No he medido latencia por petición. Antes de tocar producción, comprueba cada regla con el script de la sección de depuración.

Dominio antiguo y páginas retiradas

Dominio antiguo a dominio nuevo

El nombre .viejo.com (con punto inicial) cubre viejo.com y todos sus subdominios. Si la estructura cambió, pon el rewrite específico antes del return general (en mi prueba, /blog/post-uno/?utm=1 acabó en /articulos/post-uno/?utm=1 y el resto conservó ruta y query):

server {
    listen 80;
    listen 443 ssl;
    server_name .viejo.com;
    ssl_certificate     /etc/ssl/example.com.pem;
    ssl_certificate_key /etc/ssl/example.com.key;

    rewrite ^/blog/(.*)$ https://nuevo.com/articulos/$1 permanent;
    return 301 https://nuevo.com$request_uri;
}

El dominio antiguo necesita un certificado válido para su HTTPS mientras dure la redirección; en mi prueba usé curl -k con uno autofirmado, así que eso no lo validé. En una migración real la correspondencia debe ser URL a URL (mi servicio de migraciones SEO parte de ahí), y mandar todo a la home no lo es.

410 con return

Para una URL retirada para siempre, return 410 es una línea. Para una sección entera, un prefijo con ^~; para una lista, un map con if a nivel de server:

location = /producto-retirado/      { return 410; }
location ^~ /tienda/descatalogado/  { return 410; }

error_page 410 /410.html;
location = /410.html { internal; }
map $uri $retirada {
    default 0;
    /producto-retirado/      1;
    ~^/tienda/descatalogado/ 1;
}
if ($retirada) { return 410; }  # dentro del server

Ambas devolvieron 410 Gone; la primera con 410.html como cuerpo y con /410.html inaccesible desde fuera (404). La documentación de Google trata todos los 4xx igual, salvo el 429, y no distingue 404 de 410. Elijo 410 porque comunica la intención, no porque prometa una desindexación más rápida. El repaso completo de códigos está en la guía de códigos de respuesta del servidor.

if: cuándo sí y cuándo no

La wiki de Nginx tiene un texto, «If is Evil», sobre if dentro de location: en algunos casos hace algo distinto de lo esperado y puede provocar un fallo del proceso; las únicas acciones 100 % seguras ahí son return ... y rewrite ... last; y recomienda evitarlo cuando haya alternativa (try_files, o moverlo a nivel de server). La documentación del módulo da la razón de fondo: dentro de un if, la petición pasa a tener la configuración de ese bloque.

Ese efecto se ve con facilidad. Esta configuración añade una cabecera en el location y otra dentro de un if:

location /con-if {
    add_header X-Capa "location";
    if ($arg_debug) {
        add_header X-Debug "si";
    }
    return 200 "ok\n";
}
/con-if            -> X-Capa: location
/con-if?debug=1    -> X-Debug: si            (X-Capa ha desaparecido)

Mi regla: uso if solo con return dentro y, a ser posible, a nivel de server, como en el if ($nueva_ruta) del apartado de map. No lo uso para decidir el host (para eso están los server_name) ni para encadenar condiciones.

Errores típicos

Seis tarjetas con errores de redirecciones en Nginx: bucle detrás de un terminador TLS, return de server que se ejecuta antes que location, server_name duplicado que solo avisa, captura sobre uri que decodifica, regex que gana al prefijo y if que pierde cabeceras.
Seis fallos que he reproducido en la prueba. Los dos primeros son los que más daño hacen en producción.

Bucle detrás de un proxy o CDN que termina el TLS

Si un balanceador o un CDN que termina el TLS habla HTTP plano con tu Nginx, $scheme vale siempre http en el backend, y un if ($scheme = http) redirige también lo que ya venía por HTTPS. Lo reproduje con dos servidores:

$ curl -sSL -o /dev/null --max-redirs 5 https://example.com/blog/
curl: (47) Maximum (5) redirects followed

La solución es mirar la cabecera que añade el proxy:

map $http_x_forwarded_proto $esquema_publico {
    default $scheme;
    https   https;
}
# en el backend:
if ($esquema_publico = http) { return 301 https://example.com$request_uri; }

Así, la petición por HTTPS dio 200 sin saltos y la HTTP directa dio 301. Sin probar: el backend solo debería fiarse de esa cabecera si viene del proxy.

El return del server se ejecuta antes que cualquier location

Las directivas de nivel server se ejecutan antes de buscar el location. Con un return 301 de server y un location para /.well-known/acme-challenge/, el desafío respondía 301 en lugar de 200. Moviendo el return a un location /, el desafío dio 200 y el resto 301. Qué hace certbot ante ese 301 no lo he probado.

El 301 que no se puede deshacer

Nginx no añade cabeceras de caché a un return 301 (lo comprobé con curl -sI). Qué hace después cada navegador no lo he medido. Mi práctica: un 302, o un 301 de vida corta mientras compruebo la regla, y el definitivo cuando todo es correcto:

location = /b {
    add_header Cache-Control "max-age=300";
    return 301 /destino;
}

La cabecera aparece en el 301. Si hay caché en CDN delante, purga tras cambiar una regla.

server_name duplicado: solo un aviso

Dos server con el mismo server_name y puerto no hacen fallar nginx -t: emite [warn] conflicting server name "example.com" on 0.0.0.0:8361, ignored y las reglas del segundo bloque no se aplican (lo comprobé). Lee los avisos, no solo el «test is successful».

Capturas sobre $uri y caracteres codificados

Con una captura sobre $uri dentro de un return, /a/dos%20palabras produjo Location: https://ejemplo.com/nuevo/dos palabras (espacio sin codificar) y /a/a%2Fb produjo .../a/b, que cambia el significado. Con rewrite a una URL absoluta, Nginx escapó %20 y %C3%A9, pero %2F acabó como /. Con una captura sobre $request_uri ("^/c/(?<resto>[^?]*)") las tres salieron intactas.

Depurar y auditar una redirección

Cuatro pasos de depuración: nginx -t para validar la sintaxis, curl -sIL para ver cada salto, rewrite_log on para seguir las reglas rewrite y el access_log para ver los 301 que reciben los rastreadores.
Cuatro comprobaciones, de la más barata a la más lenta.

Primero nginx -t antes de recargar. Mensajes reales de mis errores de prueba:

unexpected "}"                           (falta el ; antes de la llave)
unknown directive "retrun"               (errata)
"map" directive is not allowed here      (map va en http, no en server)
duplicate location "/a"

Después, la cadena completa con curl. Con -w sacas el número de saltos y el destino final:

$ curl -sIL -o /dev/null -w 'saltos=%{num_redirects} final=%{http_code} %{url_effective}\n' http://www.example.com/old-page/?utm=1
saltos=1 final=200 https://example.com/new-page/?utm=1

Para una tabla de map, un bucle que comprueba cada regla exacta (un solo salto, Location esperada, destino con 200). Al añadir una regla que apuntaba a una URL inexistente, la marcó:

grep -E '^/' redirects.map | tr -d ';' | while read -r antigua nueva; do
  r=$(curl -s -o /dev/null -w '%{http_code} %{redirect_url}' "https://example.com$antigua")
  f=$(curl -s -o /dev/null -w '%{http_code}' "https://example.com$nueva")
  [ "$r" = "301 https://example.com$nueva" ] && [ "$f" = "200" ] && e=OK || e=FALLO
  echo "$e  $antigua -> $r  (destino: $f)"
done
OK     /old-page/ -> 301 https://example.com/new-page/  (destino: 200)
FALLO  /roto/ -> 301 https://example.com/no-existe/  (destino: 404)

Si el comportamiento sigue sin ser el esperado, activa rewrite_log on; con error_log a nivel notice. Registra las reglas rewrite, no los return:

"^/antiguo/(.*)$" matches "/antiguo/x"
rewritten redirect: "/nuevo/x?a=1"
"^/antiguo/(.*)$" does not match "/fijo"

Para lo que ven los rastreadores, la fuente es el access_log: filtra los 301 y 404 (método en la guía de análisis de logs SEO).

Nginx y Apache: diferencias que cambian el resultado

La sintaxis de Apache está en la guía de redirecciones en Apache; aquí solo la diferencia que más sorprende. Probé Redirect 301 /seccion/ /nueva-seccion/ en Apache 2.4 y /seccion/a/b?x=1 acabó en /nueva-seccion/a/b?x=1: conserva el resto de la ruta y la query string. El equivalente de Nginx, location /seccion/ { return 301 /nueva-seccion/; }, los pierde, como vimos arriba. Además, Apache admite .htaccess por directorio; en Nginx todo vive en la configuración central y se aplica con nginx -s reload.

Qué hago yo en mi web

Mi web corre sobre Apache, no sobre Nginx. Su .htaccess tiene 33 líneas y ninguna de ellas es una redirección: solo dos RewriteRule que devuelven 403 para tools/ y .git/, más ErrorDocument hacia una página 404 que responde 404 con noindex. No tengo configuración de Nginx propia que enseñarte. Escribo esta guía porque me llegan clientes con VPS y WordPress sobre Nginx.

Lo que sí muestra el código:

  • El dominio canónico es https://cristofercruz.net, sin www, definido en SITE_URL. Cada página emite su rel="canonical" con él.
  • Las 24 URL de sitemap.xml usan ese dominio y terminan en barra; el sitemap del blog se genera con SITE_URL y robots.txt declara ambos con el mismo dominio.
  • No hay http:// escrito a mano en los PHP, XML ni JSON del sitio, salvo los espacios de nombres XML.
  • El README anota las redirecciones https y no-www como «pendiente de verificar en el hosting»: dependen del servidor y no están en el repositorio. No puedo afirmar cómo responde hoy http://www a mi dominio.
  • Para retirar contenido uso PHP, no el servidor. El antiguo /blog/google-io-2025/ responde 410 con X-Robots-Tag: noindex y una página de «Artículo retirado».

Además tengo un generador de redirecciones para Apache, Nginx e IIS. Ejecuté su código y probé la salida de Nginx contra el servidor real, y encontré el problema de las capturas sobre $uri: /viejo/dos%20palabras salía con un espacio y /viejo/a%2Fb con una barra. Ya lo he corregido. Para las reglas de directorio con sufijo en rutas ASCII, el generador escribe ahora if ($request_uri ~ "^/viejo/(?<redirect_tail_0>[^?]*)"), con la captura cortada antes de la query string, que se añade aparte con $is_args$args. Las páginas exactas siguen usando $uri, y también las rutas de origen con caracteres Unicode, porque $request_uri llega codificado y no coincidiría. Con la salida nueva, curl contra Nginx real devolvió estas cabeceras Location:

/viejo/dos%20palabras  -> 301 /nuevo/dos%20palabras
/viejo/a%2Fb?x=1       -> 301 /nuevo/a%2Fb?x=1
/viejo/caf%C3%A9/      -> 301 /nuevo/caf%C3%A9/
/pagina-vieja?utm=1    -> 301 /nuevo/?utm=1

Queda una limitación: en una regla cuyo origen tiene Unicode, la captura sigue siendo sobre $uri. En mi prueba con /vieja-ñ/, la URL /vieja-%C3%B1/a%20b acabó en /nuevo/a b, con el espacio sin codificar. El generador ya avisa de que hay que revisar espacios y Unicode antes de publicar, y para esos casos sigue siendo mejor un map con destinos escritos a mano.

Auditoría SEO

Ver auditoría SEO

Preguntas frecuentes

¿Cómo evito cadenas http a https a no-www a ruta nueva?

Haz que cada bloque no canónico redirija directo al destino final. Con el map, cualquier entrada llegó en un salto; con una redirección por capa, mi prueba dio tres.

¿Puedo usar if para redirigir?

Sí, si lo único que contiene es return y, mejor, a nivel de server. Dentro de un location, la wiki de Nginx solo da por 100 % seguros return y rewrite ... last.

¿Cuántas reglas aguanta un map?

Probé 30.000 con claves de hasta 70 caracteres; necesité map_hash_bucket_size 512 y map_hash_max_size 65536. No he medido latencia, así que no te doy un límite.

¿301 o 308?

Google trata el 308 como equivalente al 301 al rastrear. La diferencia es que el 308 prohíbe cambiar el método de la petición. Nginx emite 308 con return desde la versión 1.13.0; rewrite solo emite 301 o 302.

Mi redirección no se aplica, ¿qué reviso?

Avisos de nginx -t (¿server_name duplicado?), si recargaste, si una regex gana al prefijo, si un return de server se ejecuta antes que tu location y qué URL llega realmente, con curl -sIL.

Referencias y fuentes

  1. Module ngx_http_rewrite_module nginx.org
  2. Module ngx_http_core_module nginx.org
  3. Module ngx_http_map_module nginx.org
  4. Converting rewrite rules nginx.org
  5. If is Evil... when used in location context Nginx Wiki
  6. Redirects and Google Search Google Search Central
  7. HTTP status codes, and network and DNS errors Google Search Central
  8. 301 Moved Permanently MDN Web Docs

Sigue leyendo

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
Servidores

· 13 min

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.

Leer el artículo: Redirecciones en Apache: configuración central y VirtualHost
Servidores

· 15 min

Versiones de una web: www, sin www, http y https en SEO

Por qué http, https, www y sin www son URLs distintas para Google, cómo elegir una y redirigir el resto con un 301 probado en Nginx y Apache.

Leer el artículo: Versiones de una web: www, sin www, http y https en SEO

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

Cuéntame qué necesitas conseguir con tu web.

Hablemos de tu proyecto