Hosting de sitios estáticos con Nginx

Capítulo 4

Tiempo estimado de lectura: 7 minutos

+ Ejercicio

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.

Continúa en nuestra aplicación.
  • Escuche el audio con la pantalla apagada.
  • Obtenga un certificado al finalizar.
  • ¡Más de 5000 cursos para que explores!
O continúa leyendo más abajo...
Download App

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.html si se pide un directorio.
  • Devolver 404 real 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ñade Vary: Accept-Encoding para 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 30d genera un encabezado Expires en el futuro y también influye en Cache-Control (según configuración). Aquí además lo fijamos explícitamente con add_header para 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 ... =404 para 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>HTML

Crea 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; }CSS

Crea 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');});JS

Paso 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-sitio

Paso 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;}NGINX

Habilita el sitio (si tu sistema usa sites-enabled):

sudo ln -s /etc/nginx/sites-available/mi-sitio /etc/nginx/sites-enabled/mi-sitio

Verifica sintaxis y recarga:

sudo nginx -tsudo systemctl reload nginx

Paso 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-cache

Inspecciona encabezados de un asset (debería cachear 30 días):

curl -I http://TU_HOST/assets/css/styles.css

Busca:

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

Errores comunes y cómo detectarlos rápido

  • 404 en archivos que existen: revisa que root apunte a la carpeta correcta y que la ruta solicitada coincida con la estructura real. Usa try_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.types en la configuración global y que no se está forzando un default_type inapropiado.
  • Caché demasiado agresiva en HTML: si cambias index.html y el navegador no refleja cambios, reduce caché del HTML (como no-cache) y deja caché largo solo para assets.
  • Gzip no aparece: recuerda enviar Accept-Encoding: gzip al probar con curl y que el contenido sea de un tipo incluido en gzip_types.

Ahora responde el ejercicio sobre el contenido:

En un sitio estático tradicional servido con Nginx, ¿qué configuración ayuda a entregar el archivo si existe y a devolver un 404 real cuando el recurso no existe?

¡Tienes razón! Felicitaciones, ahora pasa a la página siguiente.

¡Tú error! Inténtalo de nuevo.

try_files intenta servir el archivo exacto ($uri), luego tratarlo como directorio ($uri/) y, si no encuentra nada, devuelve =404. Esto evita respuestas ambiguas para URLs inexistentes.

Siguiente capítulo

Ruteo y control de solicitudes con location en Nginx

Arrow Right Icon
Portada de libro electrónico gratuitaNginx para Principiantes: Domina el Servidor Web Moderno desde Cero
40%

Nginx para Principiantes: Domina el Servidor Web Moderno desde Cero

Nuevo curso

10 páginas

Descarga la aplicación para obtener una certificación gratuita y escuchar cursos en segundo plano, incluso con la pantalla apagada.