SEO técnico desde terminal: comandos útiles para validar problemas reales

Hay comprobaciones SEO que no necesitan una herramienta online, una auditoría completa ni abrir cinco pestañas. A veces basta con lanzar una petición limpia desde terminal y ver qué responde la web.

No hablo de sustituir herramientas como Screaming Frog, Search Console, DevTools o Lighthouse. Hablo de tener recursos rápidos para validar sospechas concretas en el día a día: diferencias entre lo que recibe Googlebot y un usuario normal, cambios entre producción y staging, caché sirviendo HTML antiguo o señales SEO modificadas después de ejecutar JavaScript.

La terminal no hace magia. Pero cuando sabes qué estás buscando, te permite comprobarlo rápido y sin depender siempre de una interfaz.

1. Comprobar si una web responde distinto a Googlebot y a un usuario normal

Uno de los usos más útiles de curl es comprobar si una URL entrega una respuesta diferente según el user-agent que hace la petición. No hace falta pensar únicamente en cloaking intencionado. A veces la diferencia viene de una capa de caché, una regla de seguridad, un CDN, un plugin, una configuración de servidor o una protección antibots mal ajustada.

La sospecha es sencilla: Googlebot podría estar recibiendo una versión distinta a la que recibe un usuario normal.

Esta comprobación no verifica cómo accede Googlebot real ni sustituye una inspección en Search Console. Solo comprueba si el servidor cambia la respuesta cuando la petición declara un user-agent de Googlebot.

Primero hacemos la petición como Googlebot:

curl -sI -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" https://tudominio.com/

Después hacemos la misma petición como navegador normal:

curl -sI -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" https://tudominio.com/

En las cabeceras hay que mirar especialmente el código HTTP, location, x-robots-tag, content-type, cache-control y cualquier cabecera que pueda cambiar el comportamiento de rastreo o indexación.

Para compararlo mejor, guardo ambas respuestas y hago un diff:

curl -sI -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" https://tudominio.com/ > /tmp/googlebot-headers.txt

curl -sI -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" https://tudominio.com/ > /tmp/usuario-headers.txt

diff -u /tmp/usuario-headers.txt /tmp/googlebot-headers.txt

Si los dos devuelven un 200, mismo tipo de contenido y mismas señales críticas, en principio no hay nada llamativo en cabeceras. Si Googlebot recibe una redirección distinta, un noindex por cabecera, un tipo de contenido diferente o una política de caché distinta, ya hay algo que investigar.

También conviene comparar el tamaño del HTML entregado:

curl -sL -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" https://tudominio.com/ | wc -c

curl -sL -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" https://tudominio.com/ | wc -c

Si el número de bytes cambia mucho, no significa automáticamente que haya cloaking. Puede haber tokens, variaciones de caché, elementos dinámicos o pequeños cambios de sesión. Pero sí indica que el HTML no es idéntico y merece una comparación más fina.

curl -sL -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" https://tudominio.com/ > /tmp/googlebot.html

curl -sL -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" https://tudominio.com/ > /tmp/usuario.html

diff -u /tmp/usuario.html /tmp/googlebot.html | grep -Ei "canonical|robots|title|ld\+json|href=|h1|content"

El escenario típico en el que esto sirve es una web que se comporta bien para usuarios, pero Google empieza a mostrar señales raras: URLs que desaparecen, canonicals inconsistentes, problemas de rastreo o una caída que no se explica con contenido ni enlaces. Antes de abrir veinte herramientas, compruebo si el servidor está diferenciando de alguna forma entre bot y navegador. Si lo está haciendo, ya tengo una línea de investigación mucho más clara.

No todo comportamiento distinto por user-agent es cloaking, pero todo comportamiento distinto en señales SEO críticas merece revisión.

2. Comparar producción y staging antes de publicar cambios

Otro uso muy práctico de la terminal es comparar producción con staging antes de mover cambios. No para revisar diseño, sino para detectar diferencias técnicas que pueden pasar desapercibidas: un noindex que se queda en staging, una canonical apuntando al entorno de pruebas, scripts nuevos, CSS añadido, schema duplicado o enlaces hardcodeados al dominio equivocado.

La sospecha aquí es clara: el entorno de desarrollo puede no estar replicando bien la configuración SEO de producción, o puede arrastrar señales que no deberían llegar a la web pública.

Defino los dos entornos:

PROD="https://tudominio.com/"
STAGING="https://staging.tudominio.com/"

Empiezo por cabeceras:

curl -sI "$PROD" | grep -Ei "HTTP|content-type|x-robots-tag|cache-control|location|server" > /tmp/prod-headers.txt

curl -sI "$STAGING" | grep -Ei "HTTP|content-type|x-robots-tag|cache-control|location|server" > /tmp/staging-headers.txt

diff -u /tmp/prod-headers.txt /tmp/staging-headers.txt

Que staging tenga noindex puede ser correcto. Que producción lo herede después de publicar, no. Que staging tenga una caché distinta puede ser normal. Que producción quede con una redirección o cabecera inesperada después del despliegue, no.

Este tipo de comprobación es especialmente útil en migraciones, rediseños o cambios técnicos donde algo deja de cuadrar después de publicar. Cuando una caída, una pérdida de visibilidad o una señal SEO rara viene de una mezcla de entorno, caché, plantilla y configuración, una consultoría SEO ayuda precisamente a acotar dónde está el problema antes de tocar piezas a ciegas.

Después comparo señales SEO básicas dentro del HTML:

curl -sL "$PROD" | grep -iE "canonical|meta name=\"robots\"|meta name=\"description\"|<title|ld\+json" > /tmp/prod-seo.txt

curl -sL "$STAGING" | grep -iE "canonical|meta name=\"robots\"|meta name=\"description\"|<title|ld\+json" > /tmp/staging-seo.txt

diff -u /tmp/prod-seo.txt /tmp/staging-seo.txt

Si el title cambia porque se ha actualizado el contenido, perfecto. Si la canonical de staging apunta a staging, cuidado. Si producción recibe un meta robots distinto tras publicar, se detiene el despliegue y se corrige antes de indexar problemas.

También suelo mirar tipos de schema, sobre todo cuando se han tocado plugins SEO, plantillas, FAQs o datos estructurados:

curl -sL "$PROD" | grep -oE '"@type"[[:space:]]*:[[:space:]]*"[^"]+"' | sort > /tmp/prod-schema.txt

curl -sL "$STAGING" | grep -oE '"@type"[[:space:]]*:[[:space:]]*"[^"]+"' | sort > /tmp/staging-schema.txt

diff -u /tmp/prod-schema.txt /tmp/staging-schema.txt

Si aparece un tipo nuevo de schema porque se ha añadido una funcionalidad, tiene sentido. Si aparecen duplicidades porque ahora el tema, Elementor y el plugin SEO están generando marcado parecido, hay que decidir qué capa manda.

Para comparar JavaScript cargado:

curl -sL "$PROD" | grep -oE 'src="[^"]+\.js[^"]*"' | sed 's/src="//;s/"//' | sort > /tmp/prod-js.txt

curl -sL "$STAGING" | grep -oE 'src="[^"]+\.js[^"]*"' | sed 's/src="//;s/"//' | sort > /tmp/staging-js.txt

diff -u /tmp/prod-js.txt /tmp/staging-js.txt

Para CSS:

curl -sL "$PROD" | grep -oE 'href="[^"]+\.css[^"]*"' | sed 's/href="//;s/"//' | sort > /tmp/prod-css.txt

curl -sL "$STAGING" | grep -oE 'href="[^"]+\.css[^"]*"' | sed 's/href="//;s/"//' | sort > /tmp/staging-css.txt

diff -u /tmp/prod-css.txt /tmp/staging-css.txt

Esto no mide rendimiento completo. No te dice Core Web Vitals, no calcula el peso total de la página ni sustituye una revisión en navegador. Pero sí detecta cambios evidentes de carga: una librería nueva, un plugin que añade archivos, un CSS que no existía o scripts que staging trae y producción no tenía.

Una comprobación muy útil antes de publicar es buscar URLs de staging hardcodeadas dentro del HTML de producción:

STAGING_HOST="staging.tudominio.com"

curl -sL "$PROD" | grep -i "$STAGING_HOST"

Si esto devuelve algo, reviso si son enlaces, imágenes, scripts, canonicals, rutas internas o referencias residuales al entorno de pruebas. Ninguna de esas opciones conviene dejarla pasar.

También se puede hacer una comprobación rápida del volumen de enlaces internos relativos:

curl -sL "$PROD" | grep -oE 'href="[^"]*"' | grep -v "http" | wc -l

curl -sL "$STAGING" | grep -oE 'href="[^"]*"' | grep -v "http" | wc -l

No es una auditoría de enlazado interno. Es una señal rápida. Si producción tiene 180 enlaces internos y staging tiene 40, quizá falta un menú, un bloque, un footer, una sección de relacionados o una parte de la plantilla. No concluyo solo con ese número, pero sí sé dónde mirar.

Staging no se revisa solo para ver si “se ve bien”. Se revisa para confirmar que no publica señales técnicas equivocadas.

3. Comparar caché y servidor cuando algo no cuadra

La caché puede esconder problemas y también puede mantener vivos problemas que ya has corregido. A veces modificas una canonical, un title, un schema o un meta robots y sigues viendo una versión antigua. O al revés: la plantilla genera bien la señal, pero la versión cacheada está sirviendo otra cosa.

La sospecha aquí es que la web cacheada y la respuesta fresca del servidor no están entregando exactamente el mismo HTML.

Primero miro la respuesta normal:

curl -sI https://tudominio.com/

Después intento forzar una petición sin caché, siempre que la capa de caché respete estas cabeceras:

curl -sI -H "Cache-Control: no-cache" -H "Pragma: no-cache" https://tudominio.com/

Esto permite comparar cabeceras: si cambia cache-control, si aparece una cabecera del CDN, si la respuesta viene marcada como HIT o MISS, o si hay diferencias en estado HTTP y tipo de contenido.

Pero lo más útil suele ser comparar el HTML:

curl -sL https://tudominio.com/ > /tmp/cache.html

curl -sL -H "Cache-Control: no-cache" -H "Pragma: no-cache" https://tudominio.com/ > /tmp/servidor.html

diff -u /tmp/cache.html /tmp/servidor.html | grep -Ei "canonical|robots|title|ld\+json|description|hreflang"

Si el diff no devuelve nada relevante, las señales principales coinciden. Si aparecen diferencias en canonical, robots, title, description, hreflang o schema, la caché está sirviendo una versión distinta a la que esperabas o el servidor está generando variantes según condiciones que conviene entender.

Este bloque sirve mucho cuando has tocado una plantilla, has limpiado un plugin SEO, has modificado datos estructurados o has cambiado reglas de caché y no sabes si el problema sigue existiendo o simplemente estás viendo una versión antigua. También sirve cuando una herramienta externa te reporta una señal que tú ya has corregido en WordPress. Antes de asumir que la herramienta va tarde, comparo HTML cacheado y HTML fresco.

Hay que tener un poco de cuidado: no todas las capas de caché obedecen Cache-Control: no-cache o Pragma: no-cache. Algunos CDN, plugins o configuraciones pueden ignorarlo. Si la comparativa no cambia, no significa siempre que no haya caché. Significa que con esa petición no has conseguido una variante diferente.

Si trabajas con Cloudflare, Varnish, caché de hosting o plugins de caché en WordPress, esta prueba puede no saltarse todas las capas. Aun así, sirve para detectar diferencias cuando la infraestructura sí permite una respuesta fresca.

Si una señal SEO ya está corregida en plantilla pero la caché sigue sirviendo la versión antigua, para Google el problema no está corregido todavía.

4. Comparar HTML estático y HTML renderizado con JavaScript

curl te enseña el HTML que entrega el servidor, pero no ejecuta JavaScript. Eso es justo lo que lo hace útil: si una canonical, un meta robots, un schema o un bloque importante no aparece con curl, sabes que no viene en la respuesta inicial.

Cuando la sospecha es que JavaScript está añadiendo, cambiando o duplicando señales SEO, hay que comparar dos versiones: el HTML estático del servidor y el HTML renderizado por un navegador real.

Este caso aparece bastante en webs con constructores visuales, plugins SEO, scripts de optimización, integraciones externas o componentes que modifican el head después de cargar. Si el HTML estático tiene una canonical y el renderizado tiene otra, el problema ya no es “la canonical está mal”. El problema es que hay una capa posterior reescribiendo una señal SEO crítica.

Primero guardo el HTML estático del servidor:

curl -sL https://tudominio.com/ > /tmp/html-estatico.txt

Para obtener el HTML renderizado uso Puppeteer con un script pequeño, así controlo exactamente qué se ejecuta y qué HTML comparo.

Instalación:

mkdir seo-render
cd seo-render
npm init -y
npm install puppeteer

Creo un archivo llamado render-html.js:

const puppeteer = require('puppeteer');

const url = process.argv[2];

if (!url) {
  console.error('Uso: node render-html.js https://tudominio.com/');
  process.exit(1);
}

(async () => {
  const browser = await puppeteer.launch({
    headless: 'new'
  });

  const page = await browser.newPage();

  await page.setUserAgent(
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'
  );

  await page.goto(url, {
    waitUntil: 'networkidle2',
    timeout: 30000
  });

  const html = await page.content();
  console.log(html);

  await browser.close();
})();

Ahora genero el HTML renderizado:

node render-html.js https://tudominio.com/ > /tmp/html-renderizado.txt

Y comparo señales SEO sensibles:

diff -u /tmp/html-estatico.txt /tmp/html-renderizado.txt | grep -Ei "canonical|robots|title|ld\+json|description|hreflang"

Si el diff muestra que una canonical, un title, un meta robots o un schema aparece solo en el HTML renderizado, hay que revisar la implementación. No porque Google no pueda renderizar JavaScript nunca, sino porque una señal crítica servida de forma tardía, duplicada o modificada por scripts puede generar inconsistencias y hacer el diagnóstico más difícil.

Un caso típico: una web donde el plugin SEO genera una canonical correcta en servidor, pero un script o integración posterior modifica el head. Otro caso: una plantilla aparentemente limpia donde el schema se inyecta después, se duplica o cambia según interacción. También puede ocurrir con landings hechas con constructores o integraciones que reescriben elementos del documento cuando carga la página.

Si el HTML estático y el renderizado difieren en contenido visual, puede ser normal. Si difieren en señales SEO críticas, ya no lo trato como detalle menor.

Las señales SEO importantes deberían ser estables. Si aparecen, desaparecen o cambian después de renderizar, hay que saber exactamente por qué.

La terminal no sustituye el criterio, lo afina

Estos comandos no sustituyen una auditoría SEO completa, pero ahorran tiempo en comprobaciones muy concretas. Sirven para validar si una web responde distinto a Googlebot, si staging arrastra señales peligrosas, si la caché está sirviendo HTML antiguo o si JavaScript modifica señales SEO críticas.

Lo útil no es memorizar comandos. Lo útil es convertirlos en pequeñas rutinas de trabajo: guardarlos, adaptarlos, crear scripts y usarlos cuando tienes una sospecha técnica concreta.

Una herramienta completa te da contexto, escala e interfaz. La terminal te da una respuesta directa. Cuando sabes qué preguntar, esa respuesta puede ahorrar muchas vueltas.

El comando no tiene mérito por sí solo. El valor está en saber cuándo lanzarlo y qué decisión tomar con la respuesta.

El SEO técnico no va de pasar herramientas, sino de saber interpretar lo que en un sitio web está ocurriendo.

Si quieres revisar tu proyecto con una lectura técnica seria, detectar señales que pueden estar frenando su visibilidad y tomar decisiones con criterio, ponte en contacto conmigo.

Escrito por:

Óscar Carrillo

Más de 15 años de carrera me han enseñado que el SEO es un ecosistema vivo. Paso cada jornada trabajando para que las empresas alcancen su máximo potencial en Google