Qué significa “hosting de sitio estático” en Nginx
Un sitio estático es aquel donde Nginx entrega archivos tal cual están en disco (por ejemplo .html, .css, .js, imágenes, fuentes). No hay ejecución de código del lado del servidor: el “render” ocurre en el navegador. Esto lo hace rápido, simple de desplegar y fácil de cachear.
Configurar un sitio estático completo
1) Document root (raíz del sitio) y estructura recomendada
El root define la carpeta base desde la cual Nginx buscará los archivos solicitados. Para un proyecto estático, una estructura típica podría ser:
/var/www/mi-sitio/ # raíz del proyecto (document root) /index.html # entrada principal /assets/ # recursos estáticos /css/ styles.css /js/ app.js /img/ logo.png /fonts/ ...Esta organización separa el HTML de los recursos, facilita el caching por tipo de archivo y evita mezclar archivos “de build” con configuración.
2) Manejo de index
La directiva index define qué archivo se sirve cuando el usuario pide un directorio (por ejemplo /). Lo más común es index.html.
3) Tipos MIME (Content-Type)
Nginx envía el encabezado Content-Type según la extensión del archivo (MIME types). Para que esto funcione correctamente, la configuración debe incluir el archivo de tipos MIME (normalmente ya viene habilitado en la configuración global). En el contexto del sitio, lo importante es entender que si el navegador recibe un tipo incorrecto (por ejemplo JS como texto plano), pueden aparecer errores o bloqueos.
- Escuche el audio con la pantalla apagada.
- Obtenga un certificado al finalizar.
- ¡Más de 5000 cursos para que explores!
Descargar la aplicación
En una configuración típica, verás algo como:
http { include /etc/nginx/mime.types; default_type application/octet-stream; ...}default_type se usa si no se reconoce la extensión.
4) try_files: servir recursos y devolver 404 correctamente
try_files es clave en sitios estáticos porque controla cómo Nginx busca archivos en disco antes de decidir qué responder. Es especialmente útil para:
- Servir el archivo exacto si existe.
- Servir un
index.htmlsi se pide un directorio. - Devolver
404real si no existe el recurso (en lugar de respuestas ambiguas).
Ejemplo básico recomendado para un sitio estático “clásico” (sin SPA):
location / { try_files $uri $uri/ =404;}Cómo funciona: $uri intenta el archivo exacto (por ejemplo /assets/css/styles.css), $uri/ intenta como directorio (por ejemplo /docs/ buscando su index), y si nada existe devuelve 404.
Nota: en aplicaciones SPA (Single Page Application) a veces se usa try_files $uri /index.html; para “fallback” al index. Aquí nos enfocamos en devolver 404 correctamente para recursos inexistentes, tal como se espera en un sitio estático tradicional.
5) Compresión básica (gzip)
La compresión reduce el tamaño de respuesta para texto (HTML/CSS/JS/SVG/JSON). En Nginx, gzip es una opción común y suficiente a nivel introductorio.
Configuración típica (en http o dentro del server):
gzip on;gzip_types text/plain text/css application/javascript application/json image/svg+xml;gzip_min_length 1024;gzip_vary on;gzip_types: define qué tipos se comprimen (no conviene comprimir binarios como PNG/JPG, ya vienen comprimidos).gzip_min_length: evita comprimir respuestas muy pequeñas.gzip_vary: añadeVary: Accept-Encodingpara caches intermedios.
6) Caching para archivos estáticos (expires y cache-control)
Para mejorar rendimiento, se suele cachear agresivamente recursos versionados (CSS/JS con hash o versión) y cachear menos el HTML (porque cambia más). A nivel introductorio, puedes aplicar reglas por extensión.
Ejemplo: cache largo para assets y cache corto (o sin cache) para HTML.
# Dentro del server {}location ~* \.(css|js|png|jpg|jpeg|gif|svg|ico|webp|woff2?)$ { expires 30d; add_header Cache-Control "public, max-age=2592000"; try_files $uri =404;}location ~* \.(html)$ { expires -1; add_header Cache-Control "no-cache"; try_files $uri =404;}Puntos a entender:
expires 30dgenera un encabezadoExpiresen el futuro y también influye enCache-Control(según configuración). Aquí además lo fijamos explícitamente conadd_headerpara que sea visible y claro.- Para HTML usamos
no-cache(permite almacenar pero obliga a revalidar). Para un primer enfoque es una opción segura. - Se mantiene
try_files ... =404para evitar que una URL inexistente devuelva algo inesperado.
Guía práctica paso a paso: mini-proyecto de despliegue
Objetivo
Desplegar un sitio estático con HTML/CSS/JS, configurar Nginx para servirlo con try_files, habilitar compresión básica y aplicar caching para assets. Luego verificar encabezados con curl -I.
Paso 1: crear directorios del sitio
sudo mkdir -p /var/www/mi-sitio/assets/{css,js,img}Paso 2: crear archivos de ejemplo
Crea un index.html simple:
sudo tee /var/www/mi-sitio/index.html > /dev/null <<'HTML'<!doctype html><html lang="es"><head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Mi Sitio Estático</title> <link rel="stylesheet" href="/assets/css/styles.css" /></head><body> <main class="container"> <h1>Hola desde Nginx</h1> <p id="msg">Cargando...</p> <button id="btn">Probar JS</button> </main> <script src="/assets/js/app.js"></script></body></html>HTMLCrea el CSS:
sudo tee /var/www/mi-sitio/assets/css/styles.css > /dev/null <<'CSS'body { font-family: system-ui, Arial, sans-serif; margin: 0; padding: 0; } .container { padding: 24px; } button { padding: 10px 14px; cursor: pointer; }CSSCrea el JS:
sudo tee /var/www/mi-sitio/assets/js/app.js > /dev/null <<'JS'const msg = document.getElementById('msg');const btn = document.getElementById('btn');msg.textContent = 'Listo. Recursos estáticos cargados.';btn.addEventListener('click', () => { alert('JS funcionando');});JSPaso 3: permisos básicos
Asegura que Nginx pueda leer los archivos. En muchas distribuciones, con permisos de lectura global es suficiente:
sudo chmod -R 755 /var/www/mi-sitioPaso 4: crear el bloque de servidor para el sitio
Crea un archivo de configuración del sitio (el nombre y ruta pueden variar según la distro, aquí se usa el patrón común):
sudo tee /etc/nginx/sites-available/mi-sitio > /dev/null <<'NGINX'server { listen 80; server_name _; root /var/www/mi-sitio; index index.html; location / { try_files $uri $uri/ =404; } location ~* \.(css|js|png|jpg|jpeg|gif|svg|ico|webp|woff2?)$ { expires 30d; add_header Cache-Control "public, max-age=2592000"; try_files $uri =404; } location ~* \.(html)$ { expires -1; add_header Cache-Control "no-cache"; try_files $uri =404; } gzip on; gzip_types text/plain text/css application/javascript application/json image/svg+xml; gzip_min_length 1024; gzip_vary on;}NGINXHabilita el sitio (si tu sistema usa sites-enabled):
sudo ln -s /etc/nginx/sites-available/mi-sitio /etc/nginx/sites-enabled/mi-sitioVerifica sintaxis y recarga:
sudo nginx -tsudo systemctl reload nginxPaso 5: probar en el navegador
Visita la IP o dominio del servidor. Deberías ver el HTML cargando CSS y JS. Si un recurso no existe (por ejemplo /assets/js/nope.js), Nginx debe responder 404 gracias a try_files.
Paso 6: comprobar encabezados de caché con curl -I
Inspecciona encabezados del HTML (debería ser no-cache):
curl -I http://TU_HOST/Busca en la salida:
Cache-Control: no-cacheInspecciona encabezados de un asset (debería cachear 30 días):
curl -I http://TU_HOST/assets/css/styles.cssBusca:
Cache-Control: public, max-age=2592000Expires: ...Paso 7: comprobar compresión gzip
Pide el HTML indicando que aceptas gzip:
curl -I -H 'Accept-Encoding: gzip' http://TU_HOST/Si aplica compresión (y el tamaño supera gzip_min_length), deberías ver:
Content-Encoding: gzipVary: Accept-EncodingErrores comunes y cómo detectarlos rápido
- 404 en archivos que existen: revisa que
rootapunte a la carpeta correcta y que la ruta solicitada coincida con la estructura real. Usatry_files $uri =404;en locations de assets para evitar “falsos positivos”. - Tipos MIME incorrectos: si el navegador no interpreta CSS/JS, confirma que se está incluyendo
/etc/nginx/mime.typesen la configuración global y que no se está forzando undefault_typeinapropiado. - Caché demasiado agresiva en HTML: si cambias
index.htmly el navegador no refleja cambios, reduce caché del HTML (comono-cache) y deja caché largo solo para assets. - Gzip no aparece: recuerda enviar
Accept-Encoding: gzipal probar concurly que el contenido sea de un tipo incluido engzip_types.