Nginx gzip y Brotli: compresión, caché del navegador y bloquear bots de IA
Publicado el 07-10-20268 min de lecturaPor la redacción de SmoothSeen
En nginx, gzip se activa con gzip on, gzip_types para los tipos de texto y gzip_vary on. Brotli no viene en los paquetes de nginx.org: necesita el módulo ngx_brotli, que hay que compilar aparte. La caché del navegador se fija por location con expires o add_header Cache-Control, y los bots de IA de entrenamiento se vetan con map y return 403.
Lo esencial
- Con gzip on, gzip_types y gzip_vary, un HTML de 6.143 bytes viajó en 413 bytes en nuestra prueba con nginx 1.30.5 y 1.31.6.
- Los paquetes de nginx.org no incluyen Brotli. El módulo ngx_brotli es un proyecto de Google en GitHub que hay que compilar; NGINX Plus lo ofrece como paquete.
- Una expresión regular con llaves en un location tiene que ir entre comillas; sin ellas, nginx -t falla con «unknown directive».
- Un add_header de caché en un location borra las cabeceras de seguridad del server; add_header_inherit merge en el server lo evita desde nginx 1.29.3.
- Vetar GPTBot o ClaudeBot con map y return 403 no te saca de ChatGPT ni de Claude; vetar OAI-SearchBot o Claude-SearchBot, sí.
Para comprobarlo en tu web: Auditoría SEO
En esta página
Nginx decide aquí tres cosas: si una respuesta viaja comprimida, cuánto tiempo puede guardarla el navegador y a quién responde con un 403. No controla lo que haga un CDN que tengas delante, ni puede distinguir a un bot auténtico de uno que se hace pasar por él. Los fragmentos se han probado el 07-10-2026 con nginx 1.30.5 (estable) y nginx 1.31.6 (principal), en sus imágenes oficiales de Docker, con curl desde otro contenedor; la excepción es Brotli, y se explica por qué. HTTPS y cabeceras de seguridad están en la guía de nginx para HTTPS, HSTS y cabeceras.
Paso 1: activar gzip
Estas líneas van en el bloque http, o en un fichero de conf.d/, que la configuración de serie incluye dentro de http:
gzip on;
gzip_vary on;
gzip_min_length 1000;
gzip_types text/plain text/css text/javascript application/javascript application/json application/xml image/svg+xml;- Directiva
gzip- Qué hace
- Activa la compresión
- Valor por defecto1
off
- Directiva
gzip_vary- Qué hace
- Añade
Vary: Accept-Encoding, para que un proxy no sirva la versión comprimida a quien no la entiende - Valor por defecto1
off
- Directiva
gzip_min_length- Qué hace
- No comprime respuestas más pequeñas que esto, en bytes
- Valor por defecto1
20
- Directiva
gzip_types- Qué hace
- Tipos que se comprimen, además de
text/html, que siempre se comprime - Valor por defecto1
text/html
- Directiva
gzip_comp_level- Qué hace
- Nivel de 1 (rápido) a 9 (más compresión)
- Valor por defecto1
1
- Directiva
gzip_proxied- Qué hace
- Si comprime peticiones que llegan a través de un proxy
- Valor por defecto1
off
Bytes transferidos en la prueba, idénticos en las dos versiones:
- Fichero
- HTML
- Sin comprimir
- 6.143
- gzip
- 413
- Fichero
- CSS
- Sin comprimir
- 4.710
- gzip
- 277
- Fichero
- JavaScript
- Sin comprimir
- 5.670
- gzip
- 297
- Fichero
- Imagen WebP
- Sin comprimir
- 4.000
- gzip
- 4.000
Tres detalles prácticos:
- No pongas
text/htmlengzip_types: ya se comprime siempre. - Pon los dos tipos de JavaScript. El
mime.typesde nginx sirve los.jscomoapplication/javascript, pero el de Apache usatext/javascript; si tu nginx hace de proxy de otro servidor, puede llegarte cualquiera de los dos. - Detrás de un CDN, mira
gzip_proxied. Nginx considera que una petición llega por un proxy si trae la cabeceraVia, y con el valor por defectooffno la comprime1.
La documentación de nginx avisa también de que las respuestas comprimidas por SSL/TLS pueden ser vulnerables a los ataques BREACH1: si tu web maneja tokens en páginas dinámicas, revísalo.
¿Y Brotli?
Nginx no trae Brotli de serie. La página de paquetes de nginx.org enumera sus módulos dinámicos (geoip, image-filter, njs, perl, xslt, otel y acme) y Brotli no está entre ellos2. En la imagen oficial de Docker tampoco hay ningún módulo Brotli en /usr/lib/nginx/modules/: lo comprobamos.
Hay dos vías:
- ngx_brotli, un proyecto de Google en GitHub. Se compila como módulo dinámico contra tu versión exacta de nginx (
./configure --with-compat --add-dynamic-module=...) y se carga conload_module3. - NGINX Plus, la versión comercial, que lo ofrece como paquete
nginx-plus-module-brotli4.
Este bloque, con los nombres de módulo y directivas que documenta el propio proyecto, no lo hemos probado, porque las imágenes oficiales no incluyen el módulo:
# En nginx.conf, fuera de cualquier bloque (contexto principal)
load_module modules/ngx_http_brotli_filter_module.so;
load_module modules/ngx_http_brotli_static_module.so;
# En el bloque http
brotli on;
brotli_types text/plain text/css text/javascript application/javascript application/json application/xml image/svg+xml;Si compilar un módulo de terceros y repetirlo en cada actualización no te compensa, gzip bien configurado ya cubre lo esencial.
Ficheros precomprimidos con gzip_static
gzip_static sirve el fichero .gz que tengas al lado del original, en vez de comprimir en cada petición. El módulo no se compila por defecto, pero los paquetes oficiales lo traen (--with-http_gzip_static_module aparece en nginx -V)5.
# En el bloque http, junto a las líneas de gzip
gzip_static on;En la prueba, con app.3f9a1c2b.js.gz creado con gzip -9 -k, nginx devolvió Content-Encoding: gzip y los 318 bytes del fichero precomprimido, con el Cache-Control de su location intacto; sin Accept-Encoding, sirvió el original de 5.670 bytes. Donde no hay .gz, sigue comprimiendo al vuelo. La documentación pide que el original y el .gz tengan la misma fecha de modificación5; gzip -k la conserva. La ventaja es el tiempo de CPU, no tanto los bytes: el mismo fichero comprimido al vuelo ocupó 297.
Paso 2: la caché del navegador por tipo de fichero
La regla de web.dev: las URL con versión o huella en el nombre pueden guardarse un año (max-age=31536000), y las que no la llevan, como el HTML, deben ir con no-cache, que significa «revalida antes de usar»6. MDN define immutable como la promesa de que la respuesta no cambiará mientras esté fresca7.
# HTML y todo lo demás: se guarda, pero se revalida
location / {
add_header Cache-Control "no-cache";
}
# Ficheros con huella en el nombre: un año, immutable
# (la expresión lleva llaves: tiene que ir entre comillas)
location ~* "\.[0-9a-f]{8,}\.(css|js|mjs|woff2|webp|avif|png|jpe?g|svg)$" {
add_header Cache-Control "max-age=31536000, immutable";
}
# CSS y JS sin huella: una semana
location ~* \.(css|js|mjs)$ {
expires 7d;
}
# Imágenes y fuentes sin huella: un mes
location ~* \.(webp|avif|png|jpe?g|gif|svg|woff2)$ {
expires 30d;
}- Petición
/- Cache-Control obtenido
no-cache
- Petición
/assets/estilos.css- Cache-Control obtenido
max-age=604800yExpires
- Petición
/assets/app.3f9a1c2b.js- Cache-Control obtenido
max-age=31536000, immutable
- Petición
/assets/foto.webp- Cache-Control obtenido
max-age=2592000yExpires
Tres cosas que aprendimos en la prueba:
- Las llaves rompen la configuración si no hay comillas. La primera versión, con la expresión sin comillas, hizo fallar
nginx -tconunknown directive "8,}\.(css|js...: nginx interpretó la llave como el inicio del bloque. - El orden importa. Nginx usa la primera expresión regular que encaja, en el orden en que aparecen; por eso la de los ficheros con huella va antes que la genérica de CSS y JS.
expiresponeExpiresyCache-Control: max-agea la vez8, y no cuenta comoadd_headera efectos de herencia: el location conexpiressiguió recibiendo las cabeceras de seguridad del server.
Y la trampa de siempre: un add_header Cache-Control en un location borra las cabeceras de seguridad que pusiste en el server, porque add_header solo se hereda si el nivel actual no tiene ninguno8. Desde nginx 1.29.3 basta con una línea en el server:
add_header_inherit merge;Con ella, en las dos versiones probadas, /, el fichero con huella y el CSS llevaron su Cache-Control y además X-Content-Type-Options y Referrer-Policy. Sin ella, los dos locations con add_header las perdieron.
Paso 3: bots de IA, separando búsqueda y entrenamiento
Los rastreadores de búsqueda deciden si ChatGPT, Claude o Perplexity pueden citarte: OAI-SearchBot (si lo vetas, no sales en las respuestas de búsqueda de ChatGPT9), Claude-SearchBot y Claude-User (vetarlos puede reducir tu visibilidad en Claude10) y PerplexityBot, que no se usa para entrenar modelos11. Los de entrenamiento, como GPTBot o ClaudeBot, solo deciden si tu contenido se usa para entrenar; vetarlos es una decisión editorial que no te saca de las respuestas910.
Este veto, en contexto http (map solo se admite ahí12), deja fuera a tres rastreadores de entrenamiento y a CCBot, el de Common Crawl, y deja siempre abierto robots.txt:
map $http_user_agent $bot_entrenamiento {
default 0;
"~*(GPTBot|ClaudeBot|CCBot|meta-externalagent)" 1;
}
map "$bot_entrenamiento:$uri" $vetar_bot {
default 0;
"1:/robots.txt" 0;
"~^1:" 1;
}Y en el server:
if ($vetar_bot) {
return 403;
}El segundo map funciona porque nginx comprueba primero las cadenas exactas y después las expresiones regulares12: 1:/robots.txt gana a ~^1:. Resultado en las dos versiones: los cuatro vetados recibieron 403 en /blog/ y 200 en /robots.txt; OAI-SearchBot, ChatGPT-User, Claude-SearchBot, PerplexityBot, Googlebot y un Chrome normal, 200 en las dos. Anthropic explica que bloquear a sus bots de forma que no puedan leer robots.txt puede no garantizar la exclusión10: por eso la excepción.
Los límites de cualquier veto por user-agent:
- Se puede falsificar. Common Crawl dice que conoce rastreadores que se hacen pasar por CCBot y publica sus rangos de IP para comprobarlo13; OpenAI y Perplexity también publican los suyos.
- Google-Extended no es un user-agent. Es una marca de robots.txt sin user-agent propio, que no afecta a la Búsqueda de Google14. Con
mapno se ve: va en robots.txt. - ChatGPT-User y Perplexity-User pueden no seguir robots.txt, según sus dueños911. Vetarlos en el servidor te saca de las consultas que hacen los usuarios.
Para los bots que respetan robots.txt, robots.txt es la vía más clara y la que cualquiera puede leer; el map queda como refuerzo:
User-agent: GPTBot
User-agent: ClaudeBot
User-agent: CCBot
User-agent: meta-externalagent
User-agent: Google-Extended
Disallow: /
User-agent: *
Allow: /Cómo comprobarlo
- Sintaxis.
nginx -tantes de cada recarga. - Compresión.
curl -sI -H "Accept-Encoding: gzip" https://www.example.com/debe mostrarContent-Encoding: gzipyVary: Accept-Encoding. Repite con un.jsy un.css. - Caché y cabeceras.
curl -sIsobre la portada, un CSS y un fichero con huella: compara cadaCache-Controlcon la tabla y comprueba que las cabeceras de seguridad siguen ahí. - Bots.
curl -sI -A "GPTBot/1.4" https://www.example.com/debe dar 403, y con-A "OAI-SearchBot/1.4", 200.
Qué hace SmoothSeen con esto
SmoothSeen comprueba dentro de Posicionamiento web si tu web sirve el texto comprimido con gzip o Brotli. En Posicionamiento IA lee tu robots.txt separando los rastreadores de búsqueda de los de entrenamiento, sin contar como fallo vetar a los de entrenamiento, y pide la página con el user-agent de cada rastreador de IA, de un navegador y de Googlebot para detectar si el servidor responde 403 a alguno.
Qué hacer esta semana
Pide un .js de tu web con curl -sI -H "Accept-Encoding: gzip": si no sale comprimido, revisa gzip_types. Después pide la portada con -A "OAI-SearchBot/1.4" y confirma que recibe un 200. Para revisar compresión, robots.txt y vetos del servidor de una vez, analiza tu web con SmoothSeen.
Preguntas frecuentes
¿Por qué nginx no comprime mis ficheros JavaScript?
Lo habitual es que el tipo MIME con el que salen no esté en gzip_types. El mime.types de nginx usa application/javascript, pero si nginx hace de proxy de otra aplicación puede recibir text/javascript. Mira la cabecera Content-Type con curl -sI y añade a la lista exactamente ese valor. Comprueba también que el fichero supere gzip_min_length.
¿Tengo que recompilar nginx para usar Brotli?
Con los paquetes de nginx.org, sí: hay que compilar el módulo ngx_brotli contra tu versión exacta de nginx y cargarlo con load_module, y repetirlo en cada actualización. Algunas distribuciones lo empaquetan para su propio nginx, y NGINX Plus lo ofrece como paquete. Si nada de eso encaja, gzip sigue siendo una mejora grande.
¿Bloquear GPTBot me saca de ChatGPT?
No. OpenAI separa GPTBot, que usa para entrenar modelos, de OAI-SearchBot, que decide si tu web aparece en la búsqueda de ChatGPT, y cada uno se configura por separado. Puedes vetar el entrenamiento y seguir saliendo en las respuestas con búsqueda. Lo que sí te saca es vetar a OAI-SearchBot.
¿Por qué no se ven mis cambios de CSS después de activar la caché?
Porque el navegador usa su copia guardada hasta que caduca. Pasa cuando das un año, o immutable, a un fichero cuyo nombre no cambia al cambiar su contenido. La solución es que tu herramienta de publicación añada una huella o un número de versión al nombre, o limitar esos ficheros a una semana con expires 7d.
Fuentes
- 1Module ngx_http_gzip_module, nginx.org, consultada el 07-10-2026.
- 2nginx: Linux packages, nginx.org, consultada el 07-10-2026.
- 3google/ngx_brotli, Google, GitHub, consultada el 07-10-2026.
- 4Brotli (dynamic module), F5 NGINX, consultada el 07-10-2026.
- 5Module ngx_http_gzip_static_module, nginx.org, consultada el 07-10-2026.
- 6Prevent unnecessary network requests with the HTTP Cache, web.dev (Google), consultada el 07-10-2026.
- 7Cache-Control, MDN Web Docs, actualizada el 17-09-2026.
- 8Module ngx_http_headers_module, nginx.org, consultada el 07-10-2026.
- 9Overview of OpenAI Crawlers, OpenAI, consultada el 07-10-2026.
- 10Does Anthropic crawl data from the web, and how can site owners block the crawler?, Anthropic, actualizada el 07-04-2026.
- 11Perplexity Crawlers, Perplexity, consultada el 07-10-2026.
- 12Module ngx_http_map_module, nginx.org, consultada el 07-10-2026.
- 13CCBot, Common Crawl, consultada el 07-10-2026.
- 14Google's common crawlers: Google-Extended, Google Search Central, actualizada el 14-07-2026.
Cómo citar este artículo
SmoothSeen. (2026, 7 de octubre). Nginx gzip y Brotli: compresión, caché del navegador y bloquear bots de IA. https://smoothseen.com/es/blog/nginx-gzip-brotli-cache-bots/
Sigue leyendo
Posicionamiento web (SEO): qué es, cómo funciona y cómo mejorarlo en 2026
Qué es el posicionamiento web, cómo decide Google qué páginas muestra y una checklist priorizada para mejorarlo con herramientas gratuitas.
.htaccess y caché: activar gzip y Brotli, la caché del navegador y bloquear bots de IA en Apache
Cómo activar gzip y Brotli, configurar la caché del navegador y vetar bots de IA de entrenamiento en el .htaccess de Apache sin salir de ChatGPT. Probado.
.htaccess y HTTPS: redirigir http a https, activar HSTS y cabeceras de seguridad en Apache
Cómo redirigir http a https en .htaccess o en el VirtualHost de Apache, activar HSTS sin riesgos y añadir cabeceras de seguridad. Configuración probada.