Servir Archivos Estáticos con Nginx: Consejos de Optimización

Optimiza la entrega de archivos estáticos de Nginx con encabezados de caché adecuados, compresión Gzip, valores predeterminados seguros y consejos para proteger archivos ocultos, logrando sitios más rápidos con menos dolores de cabeza por activos obsoletos.

Servir Archivos Estáticos con Nginx: Consejos de Optimización

Servir archivos estáticos con Nginx es una de las formas más comunes y eficientes de entregar imágenes, CSS, JavaScript, descargas y activos frontend compilados. Nginx es muy bueno en esta tarea, pero algunas elecciones de configuración pueden marcar la diferencia entre un sitio rápido y predecible y uno que desperdicia ancho de banda o sirve contenido obsoleto.

La optimización de archivos estáticos se basa principalmente en rutas claras, encabezados de caché correctos, compresión y valores predeterminados seguros. No necesitas una configuración complicada para obtener buenos resultados, pero sí necesitas que las reglas de caché coincidan con cómo se nombran e implementan tus archivos.

Comienza con una Ubicación Clara para Archivos Estáticos

La configuración más simple para archivos estáticos usa root y try_files:

server {
    listen 80;
    server_name example.com;

    root /var/www/example.com/public;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

Con esta configuración, una solicitud de /css/app.css se asigna a /var/www/example.com/public/css/app.css. Si el archivo no existe, Nginx devuelve 404.

Esa asignación directa es útil cuando estás depurando. Puedes tomar una URL del navegador, convertirla en una ruta del sistema de archivos y verificar si el archivo existe:

ls -l /var/www/example.com/public/css/app.css

Si el archivo existe pero Nginx devuelve 404, busca un root diferente, un bloque location más específico o un archivo incluido que sobrescriba la ruta.

Para una aplicación de una sola página, es posible que desees que las rutas desconocidas recurran a index.html:

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

Esto es útil para enrutadores frontend, pero no lo uses a ciegas en todos los sitios. Si los activos faltantes también devuelven index.html, depurar rutas de JavaScript o imágenes rotas puede volverse confuso. Muchos equipos usan una ubicación separada para los activos, de modo que los archivos faltantes aún devuelvan un 404 real.

También puedes usar alias cuando una ruta URL debe asignarse a una ruta diferente del sistema de archivos:

location /assets/ {
    alias /srv/shared-assets/;
}

Ten cuidado con las barras diagonales finales. Con alias, la ruta de ubicación y la ruta del sistema de archivos generalmente deben terminar ambas con /. Una discrepancia puede producir rutas de archivo inesperadas.

Un patrón seguro es:

location /downloads/ {
    alias /srv/downloads/;
    try_files $uri =404;
}

Aquí /downloads/manual.pdf se asigna a /srv/downloads/manual.pdf. Sin la disciplina de la barra diagonal final, es fácil construir accidentalmente rutas que no existen o exponer un directorio que no pretendías publicar.

Para una visión más profunda del comportamiento de coincidencia, consulta Bloques de ubicación de Nginx.

Agrega Encabezados de Caché del Navegador

Los archivos estáticos son excelentes candidatos para el almacenamiento en caché del navegador. Si un usuario descarga app.css una vez, el navegador no debería volver a obtenerlo en cada vista de página a menos que haya cambiado.

Para activos versionados, usa tiempos de vida de caché largos:

location /assets/ {
    root /var/www/example.com/public;
    expires 1y;
    add_header Cache-Control "public, immutable";
}

Esto funciona mejor cuando los nombres de archivo cambian durante la implementación, como app.8f3a91.css o bundle.20260523.js. Si el nombre del archivo cambia cuando cambia el contenido, los navegadores pueden almacenar en caché el archivo antiguo de forma segura durante mucho tiempo.

Para archivos que mantienen el mismo nombre, usa un almacenamiento en caché más corto:

location = /index.html {
    root /var/www/example.com/public;
    expires -1;
    add_header Cache-Control "no-cache";
}

Este patrón es común para aplicaciones frontend. El archivo HTML se mantiene fresco, mientras que los archivos CSS y JavaScript con hash se almacenan en caché de forma agresiva.

Un ejemplo práctico: tu compilación de React o Vue genera activos con hash en /assets/ y un index.html simple. Almacena en caché /assets/ durante un año, pero haz que index.html se revalide. Los usuarios obtienen visitas repetidas rápidas y las nuevas implementaciones aún cargan las referencias de activos más recientes.

Después de cambiar las reglas de caché, prueba los encabezados en lugar de adivinar:

curl -I https://example.com/assets/app.8f3a91.css
curl -I https://example.com/

Deseas que el activo con hash muestre un valor Cache-Control de larga duración. Generalmente deseas que la página HTML se revalide o use una vida útil corta. Si ambos se almacenan en caché durante un año, una implementación puede dejar a los usuarios atascados en un archivo HTML antiguo que apunta a JavaScript antiguo.

Usa Compresión para Activos de Texto

Los activos de texto como CSS, JavaScript, SVG y JSON se comprimen bien. Puedes habilitar Gzip en el bloque http:

gzip on;
gzip_comp_level 5;
gzip_min_length 1024;
gzip_vary on;
gzip_types
    text/css
    application/javascript
    application/json
    image/svg+xml;

No esperes que Gzip ayude mucho con archivos JPEG, PNG, WebP, MP4 o zip. Esos formatos ya están comprimidos. Intentar comprimirlos nuevamente generalmente desperdicia CPU.

Para sitios estáticos de alto tráfico, considera archivos precomprimidos. Tu proceso de compilación puede crear versiones .gz de archivos CSS y JavaScript grandes, y Nginx puede servirlos cuando el navegador admita Gzip:

gzip_static on;

Esto reduce el trabajo de compresión en tiempo de ejecución porque Nginx lee el archivo precomprimido del disco. Es más útil cuando los activos se compilan con anticipación y no cambian entre solicitudes.

La compresión es solo una parte de la entrega de activos. El tamaño del archivo sigue siendo importante. Elimina JavaScript no utilizado, optimiza las imágenes durante tu proceso de compilación y evita enviar archivos grandes que los usuarios no necesiten.

Cuando pruebes la compresión, incluye un encabezado Accept-Encoding:

curl -I -H 'Accept-Encoding: gzip' https://example.com/assets/app.js

Busca Content-Encoding: gzip y Vary: Accept-Encoding. El encabezado Vary es importante cuando una CDN o caché compartida se encuentra frente a Nginx, porque las respuestas comprimidas y no comprimidas no deben mezclarse.

Mejora la Entrega y Seguridad de Archivos

Nginx puede servir archivos estáticos de manera eficiente con la configuración predeterminada, pero algunos detalles ayudan en producción.

Primero, deshabilita los listados de directorios a menos que los necesites explícitamente:

autoindex off;

Los listados de directorios pueden revelar nombres de archivos y estructuras que no pretendías publicar.

Segundo, bloquea el acceso a archivos ocultos como .env, .git y otros archivos de puntos:

location ~ /\.(?!well-known) {
    deny all;
}

La excepción para .well-known es común porque la validación de certificados y los archivos basados en estándares pueden usar ese directorio.

Tercero, asegúrate de que los permisos de tus archivos estáticos permitan a Nginx leerlos, pero no le den al servidor web acceso de escritura innecesario. Una configuración típica permite que las herramientas de implementación escriban archivos y que el usuario trabajador de Nginx los lea.

Cuarto, verifica los tipos MIME. Nginx generalmente incluye un archivo mime.types, pero los contenedores reducidos o las compilaciones personalizadas pueden omitirlo. Si CSS se sirve como text/plain, los navegadores pueden rechazarlo o comportarse de manera diferente.

Usa:

include /etc/nginx/mime.types;
default_type application/octet-stream;

Finalmente, observa los registros en busca de respuestas 404 repetidas en los activos. Eso a menudo significa que una implementación hizo referencia a archivos que no existen, un caché aún apunta a un nombre de archivo antiguo o una ruta alias es incorrecta.

Si la entrega estática se siente lenta, no comiences copiando cada directiva de ajuste que puedas encontrar. Primero verifica si el problema es realmente Nginx. Las imágenes grandes, los paquetes frontend no optimizados, los montajes de almacenamiento remoto y las fallas de caché de CDN son causas más comunes que una microoptimización faltante en el bloque del servidor.

Para una verificación local rápida:

curl -o /dev/null -s -w 'status=%{http_code} size=%{size_download} time=%{time_total}\n' https://example.com/assets/app.js

Luego compara eso con los registros de CDN, las herramientas de desarrollo del navegador o una solicitud desde la misma región que tus usuarios. Una respuesta rápida de Nginx y una respuesta lenta en el navegador generalmente apuntan a otro lugar en la ruta de entrega.

Cuándo Buscar Ayuda

Involucra a un ingeniero de DevOps si tus archivos estáticos se sirven desde almacenamiento compartido, volúmenes montados, puertas de enlace de almacenamiento de objetos o una CDN frente a Nginx. La mejor estrategia de almacenamiento en caché depende de toda la ruta de entrega, no solo del bloque del servidor Nginx.

También debes buscar ayuda si los usuarios informan JavaScript obsoleto después de las implementaciones. Eso generalmente significa que las reglas de caché y la estrategia de versionado de nombres de archivo no coinciden.

Servir archivos estáticos con Nginx funciona mejor cuando las rutas son predecibles, los tiempos de vida de caché coinciden con la estrategia de nombres de archivo y los activos de texto están comprimidos. Mantén visibles los archivos faltantes, protege los archivos ocultos y prueba los encabezados después de cada cambio de configuración. Una configuración estática limpia hace que tu sitio sea más rápido sin agregar partes móviles.