Domina los archivos virtuales en Nginx: Guía completa
Sirve archivos virtuales en Nginx sin I/O de disco. Cubre root vs alias vs try_files, named locations, endpoints de salud en Kubernetes y containers.

Al desplegar aplicaciones web modernas —especialmente las que están en containers o en plataformas gestionadas— a menudo no tienes acceso directo al web root. ¿Qué pasa cuando necesitas servir robots.txt, security.txt o un endpoint de health check? No puedes simplemente meter un archivo en una imagen Docker.
Nginx es más que un reverse proxy. Es una herramienta potente para generar y servir contenido que no existe físicamente en disco. Esta técnica es esencial para:
- Aplicaciones en containers donde no puedes modificar la imagen
- Plataformas gestionadas sin acceso al filesystem
- Microservicios que requieren endpoints estandarizados
- Archivos de cumplimiento de seguridad que cambian independientemente de las versiones de la aplicación
En esta guía completa, exploraremos las potentes directivas de Nginx para servir archivos virtuales, desde técnicas básicas hasta patrones avanzados utilizados en despliegues de producción con Kubernetes.
Entendiendo el location matching
Antes de servir archivos virtuales, debes entender cómo Nginx selecciona qué bloque location gestiona una petición. Este es el concepto más importante para una configuración efectiva de Nginx.
Prioridad del location matching
Nginx evalúa los bloques location en un orden específico. Gana la primera coincidencia por prioridad, no la primera en el archivo de configuración:
| Prioridad | Modificador | Tipo | Ejemplo |
|---|---|---|---|
| 1 | = | Coincidencia exacta | location = /robots.txt |
| 2 | ^~ | Prefijo (detiene búsqueda regex) | location ^~ /static/ |
| 3 | ~ | Regex sensible a mayúsculas | location ~ \.php$ |
| 4 | ~* | Regex insensible a mayúsculas | location ~* \.(jpg|png)$ |
| 5 | (ninguno) | Prefijo (coincidencia más larga) | location /api/ |
Ejemplo práctico
Considera esta configuración:
server {
# Priority 5: Prefix (fallback)
location / {
proxy_pass http://app:3000;
}
# Priority 1: Exact match - WINS for /robots.txt
location = /robots.txt {
return 200 "User-agent: *\nAllow: /\n";
}
# Priority 4: Case-insensitive regex
location ~* \.(jpg|png|gif)$ {
root /var/www/images;
}
# Priority 2: Prefix with ^~ - WINS for /static/*
location ^~ /static/ {
alias /var/www/static/;
}
}Directivas principales: root, alias, try_files
Estas tres directivas determinan dónde busca Nginx los archivos.
Entender la diferencia es crucial.
La diferencia explicada
| Directiva | Comportamiento | Ideal para |
|---|---|---|
root | Añade la URI a la ruta | Document roots estándar |
alias | Reemplaza el prefijo coincidente con la ruta | Servir archivos desde ubicaciones no estándar |
try_files | Comprueba archivos en orden, recurre al último | SPAs, servicio condicional de archivos |
Comparación visual
location /images/ {
root /var/www;
}
# Request: /images/photo.jpg
# Serves: /var/www/images/photo.jpg
# ^^^^^^^^ root + URIlocation /images/ {
alias /data/photos/;
}
# Request: /images/photo.jpg
# Serves: /data/photos/photo.jpg
# ^^^^^^^^^^^^ alias replaces /images/try_files para cadenas de fallback
La directiva try_files es increíblemente potente para archivos virtuales:
location / {
# Check if physical file exists, then directory, then fallback
try_files $uri $uri/ @backend;
}
# Named location for backend fallback
location @backend {
proxy_pass http://app:3000;
}El orden de comprobación es:
$uri— Intenta la ruta exacta del archivo$uri/— Intenta como directorio con index@backend— Recurre a la named location
La directiva return
Para contenido pequeño y sencillo, la directiva return es más eficiente que crear archivos físicos.
Variantes de sintaxis
| Patrón | Descripción | Ejemplo |
|---|---|---|
return CODE; | Devuelve solo el código de estado | return 204; |
return CODE TEXT; | Devuelve código de estado con cuerpo | return 200 “OK”; |
return CODE URL; | Redirección (301, 302, 307, 308) | return 301 https://…; |
Ejemplo de contenido inline
server {
# Simple text response
location = /robots.txt {
default_type text/plain;
return 200 "User-agent: *\nDisallow: /private/\n";
}
# JSON response
location = /api/status {
default_type application/json;
return 200 '{"status":"healthy","version":"1.0.0"}';
}
# Empty success (for pings)
location = /ping {
return 204;
}
# Redirect
location = /old-page {
return 301 /new-page;
}
}Named locations y fallbacks
Las named locations (con prefijo @) permiten patrones de fallback sofisticados sin reescritura de URL.
El patrón @fallback
server {
root /var/www/html;
location / {
# Try static file first, then directory, then app
try_files $uri $uri/ @app;
}
# Named location - can't be accessed directly
location @app {
proxy_pass http://backend:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}Patrón SPA (Single Page Application)
Para aplicaciones React, Vue o Angular que usan enrutamiento del lado del cliente:
server {
root /var/www/app;
index index.html;
location / {
# Try file, then directory, then index.html for client routing
try_files $uri $uri/ /index.html;
}
# Static assets (bypass try_files for performance)
location ^~ /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}Internal locations
Usa la directiva internal para prevenir el acceso directo:
location /protected-files/ {
internal; # Can only be reached via internal redirect
alias /var/secure/files/;
}
# Option A: Direct internal redirect with secure link and error_page
location /download {
internal;
alias /var/secure/files;
# Secure link check
secure_link $arg_md5,$arg_expires;
# IMPORTANT: Include $arg_file in hash to prevent path manipulation
secure_link_md5 "$secure_link_expires$uri$arg_file$remote_addr secret";
if ($secure_link = "") { return 403; }
if ($secure_link = "0") { return 410; }
# Forward to named location to serve file
error_page 418 = @serve_file;
return 418;
}
location @serve_file {
internal;
alias /var/secure/files/$arg_file;
}
# Option B: Backend sets X-Accel-Redirect header
location /download-via-backend {
# Backend (e.g., Node.js/Python) validates auth and returns:
# X-Accel-Redirect: /protected-files/myfile.pdf
proxy_pass http://backend:3000;
}Archivos web estándar
Estos archivos virtuales son fundamentales para SEO, seguridad y cumplimiento normativo.
robots.txt
El archivo robots.txt indica a los crawlers web qué partes de tu sitio pueden acceder.
location = /robots.txt {
default_type text/plain;
return 200 "User-agent: *\nAllow: /\nSitemap: https://example.com/sitemap.xml\n";
}location = /robots.txt {
alias /etc/nginx/assets/robots.txt;
default_type text/plain;
}humans.txt
Da crédito a tu equipo con humans.txt:
location = /humans.txt {
default_type text/plain;
# Note: Update the "Last update" date when deploying changes
return 200 "/* TEAM */\nLead: Jane Doe\nContact: jane@example.com\n\n/* SITE */\nPowered by: Nginx, Astro\nLast update: 2025-12-18\n";
}ads.txt y app-ads.txt
Para la transparencia publicitaria y la prevención de fraude. La especificación de IAB Tech Lab requiere un formato CSV específico: Domain, Publisher ID, Account Type, Certification Authority ID.
# Desktop ads.txt
location = /ads.txt {
default_type text/plain;
return 200 "google.com, pub-0000000000000000, DIRECT, f08c47fec0942fa0\n";
}
# Mobile app-ads.txt
location = /app-ads.txt {
alias /etc/nginx/assets/app-ads.txt;
default_type text/plain;
}favicon.ico
Aunque los navegadores modernos prefieren SVG o PNG, favicon.ico sigue siendo solicitado por herramientas legacy y crawlers. Un único archivo .ico típicamente agrupa tamaños de 16x16, 32x32 y 48x48.
# Serve from custom location
location = /favicon.ico {
alias /etc/nginx/assets/favicon.ico;
access_log off;
expires 1y;
}
# Or return empty (no favicon)
location = /favicon.ico {
return 204;
access_log off;
}Referencia: How to Favicon: A comprehensive guide
Iconos móviles y táctiles
Las aplicaciones web modernas necesitan iconos para pantallas de inicio móviles y funcionalidades de aplicaciones web progresivas (PWA).
Apple Touch Icon
iOS usa este icono cuando un sitio web se añade a la pantalla de inicio. El estándar es un archivo PNG de 180x180 sin transparencia.
Android y Web App Manifest
Android Chrome usa site.webmanifest para identificar los iconos de la aplicación (los tamaños estándar son 192x192 y 512x512).
# Apple Touch Icon
location = /apple-touch-icon.png {
alias /var/www/assets/apple-touch-icon.png;
access_log off;
expires 1y;
}
# Web App Manifest
location = /site.webmanifest {
default_type application/manifest+json;
return 200 '{
"name": "My App",
"short_name": "App",
"icons": [
{ "src": "/icon-192.png", "type": "image/png", "sizes": "192x192" },
{ "src": "/icon-512.png", "type": "image/png", "sizes": "512x512" }
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}';
}El directorio .well-known
El directorio .well-known es una ubicación estándar para metadatos del sitio.
security.txt (RFC 9116)
Permite a los investigadores de seguridad reportar vulnerabilidades:
location = /.well-known/security.txt {
default_type text/plain;
return 200 "Contact: mailto:security@example.com\nExpires: 2027-12-31T23:59:59.000Z\nPreferred-Languages: en, es\nCanonical: https://example.com/.well-known/security.txt\n";
}
# Also serve at root for compatibility
location = /security.txt {
return 301 /.well-known/security.txt;
}ACME Challenges (Let’s Encrypt)
Gestiona la validación de certificados entre múltiples servicios:
# ACME Challenges - Choose only ONE of the following two location blocks:
# Option A: Serve from shared directory (for certbot standalone/webroot mode)
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
default_type text/plain;
}
# Option B: Proxy to certbot container (for Docker setups - comment out Option A if using this)
# location ^~ /.well-known/acme-challenge/ {
# proxy_pass http://certbot:80;
# }Asociaciones de aplicaciones móviles
Vincula tu sitio web a aplicaciones móviles:
# Android App Links (assetlinks.json)
location = /.well-known/assetlinks.json {
alias /etc/nginx/assets/assetlinks.json;
default_type application/json;
}
# iOS Universal Links (no file extension!)
location = /.well-known/apple-app-site-association {
alias /etc/nginx/assets/apple-app-site-association;
default_type application/json;
}Otros archivos .well-known
| Ruta | Propósito | Content-Type |
|---|---|---|
/.well-known/change-password | Redirección a la página de cambio de contraseña | Redirect (302) |
/.well-known/webfinger | Descubrimiento de usuarios (ActivityPub, email) | application/jrd+json |
/.well-known/openid-configuration | Descubrimiento de OpenID Connect | application/json |
/.well-known/matrix/client | Descubrimiento de cliente Matrix | application/json |
Contenido dinámico con Nginx JavaScript (njs)
Para escenarios complejos, el estándar para servir archivos virtuales con lógica (contenido condicional, JSON dinámico) es njs (Nginx JavaScript). A diferencia de return, que solo sirve cadenas estáticas, njs permite control programático completo.
Configuración
Primero, asegúrate de que el módulo esté cargado en nginx.conf:
load_module modules/ngx_http_js_module.so;Ejemplo: robots.txt dinámico
Sirve diferentes reglas de robots.txt según la petición (p. ej., bloqueando bots de IA dinámicamente):
function robots(r) {
const ua = r.headersIn['User-Agent'] || "";
// Block specific bots dynamically
if (ua.includes("GPTBot") || ua.includes("CCBot")) {
r.return(200, "User-agent: *\nDisallow: /\n");
} else {
r.return(200, "User-agent: *\nAllow: /\n");
}
}
export default { robots };http {
js_import /etc/nginx/njs/virtual.js;
server {
location = /robots.txt {
js_content virtual.robots;
}
}
}Endpoints de salud y monitorización
Críticos para la orquestación de containers y la observabilidad.
Health check básico
location = /health {
access_log off; # Don't spam logs
default_type application/json;
return 200 '{"status":"healthy"}';
}Probes de Kubernetes
# Liveness probe - is the process alive?
location = /healthz {
access_log off;
return 200 'OK';
}
# Readiness probe - is it ready to receive traffic?
location = /ready {
access_log off;
# Could check backend connectivity here
return 200 'Ready';
}
# Startup probe - has initialization completed?
location = /startup {
access_log off;
return 200 'Started';
}Monitorización con stub_status
Expone métricas internas de Nginx
(requiere ngx_http_stub_status_module):
location = /nginx_status {
stub_status on;
access_log off;
# Restrict access to internal networks
allow 127.0.0.1;
allow 10.0.0.0/8;
allow 172.16.0.0/12;
allow 192.168.0.0/16;
deny all;
}Active connections: 42 server accepts handled requests 12345 12345 98765 Reading: 0 Writing: 3 Waiting: 39
Optimización de rendimiento
Maximiza la eficiencia al servir archivos virtuales.
Optimización del servicio de archivos
http {
# Zero-copy file transfers
sendfile on;
# Optimize packet sending
tcp_nopush on;
tcp_nodelay on;
# Keep connections alive
keepalive_timeout 65;
# Gzip compression
gzip on;
gzip_types text/plain text/css application/json application/javascript;
gzip_min_length 1000;
}Headers de caché
# Static assets - cache aggressively
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Virtual files - short cache or no cache
location = /robots.txt {
expires 1d;
add_header Cache-Control "public";
return 200 "...";
}
# Health endpoints - never cache
location = /health {
add_header Cache-Control "no-store" always;
return 200 '{"status":"ok"}';
}Patrones de despliegue
Configuraciones del mundo real para infraestructura moderna.
Patrón con Docker container
upstream app {
server app:3000;
}
server {
listen 80;
# Virtual files served by Nginx
location = /robots.txt {
default_type text/plain;
return 200 "User-agent: *\nAllow: /\n";
}
location = /health {
access_log off;
return 200 'OK';
}
# Static assets from volume
location /static/ {
alias /var/www/static/;
expires 1y;
}
# Everything else to app container
location / {
proxy_pass http://app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Patrón de Kubernetes ingress
server {
listen 80;
# Probes for K8s
location = /healthz { return 200 'OK'; access_log off; }
location = /ready { return 200 'Ready'; access_log off; }
# Standard virtual files
location = /robots.txt {
default_type text/plain;
return 200 "User-agent: *\nDisallow: /api/internal/\n";
}
# ACME for cert-manager (^~ prevents regex override)
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
}
# Service routing
location /api/ {
proxy_pass http://api-service:8080;
}
location / {
proxy_pass http://frontend-service:3000;
}
}Ejemplo de configuración completo
Una configuración lista para producción que combina todas las técnicas:
# Rate limiting zone (MUST be placed in http { } context, not inside server { } or location { })
limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
server {
listen 443 ssl;
http2 on;
server_name example.com;
# SSL configuration
ssl_certificate /etc/ssl/certs/example.com.crt;
ssl_certificate_key /etc/ssl/private/example.com.key;
# Performance
sendfile on;
tcp_nopush on;
# =========================================
# VIRTUAL FILES
# =========================================
# SEO / Crawlers
location = /robots.txt {
default_type text/plain;
return 200 "User-agent: *\nAllow: /\nDisallow: /api/internal/\nSitemap: https://example.com/sitemap.xml\n";
}
# Security
location = /.well-known/security.txt {
default_type text/plain;
return 200 "Contact: mailto:security@example.com\nExpires: 2027-12-31T23:59:59.000Z\n";
}
# Health & Monitoring
# Note: Nginx variables like $date_gmt are NOT interpolated in return body.
# For dynamic values, use add_header or a backend.
location = /health {
access_log off;
default_type application/json;
add_header X-Timestamp $date_gmt always;
return 200 '{"status":"healthy"}';
}
location = /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
# ACME Challenges
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
}
# Mobile Apps
location = /.well-known/apple-app-site-association {
alias /etc/nginx/assets/apple-app-site-association;
default_type application/json;
}
location = /.well-known/assetlinks.json {
alias /etc/nginx/assets/assetlinks.json;
default_type application/json;
}
# Favicon
location = /favicon.ico {
alias /var/www/static/favicon.ico;
access_log off;
expires 1y;
}
# =========================================
# STATIC ASSETS
# =========================================
location ^~ /static/ {
alias /var/www/static/;
expires 1y;
add_header Cache-Control "public, immutable";
}
# =========================================
# APPLICATION
# =========================================
location / {
limit_req zone=general burst=20 nodelay;
proxy_pass http://app:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
# HTTP to HTTPS redirect
server {
listen 80;
server_name example.com;
# Allow ACME on HTTP
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$server_name$request_uri;
}
}Resolución de problemas
Probando archivos virtuales
Validar la configuración
| Problema | Causa | Solución |
|---|---|---|
| 404 en archivo virtual | El location no coincide | Comprueba el modificador de location (= vs prefijo) |
| Se sirve contenido incorrecto | Otro location coincide primero | Revisa la prioridad; usa = para exacto |
| Content-Type incorrecto | Falta el header de tipo | Añade default_type o add_header |
| Problemas con la ruta de alias | Falta la barra final | Asegúrate de que tanto el location como el alias tengan / al final |
Más allá de los archivos estáticos: módulos de scripting
A veces necesitas más que simples cadenas estáticas. Así se comparan las principales opciones de scripting en 2025:
| Lenguaje | Rendimiento | Caso de uso | Recomendación |
|---|---|---|---|
| njs (JavaScript) | Alto | Contenido dinámico, manipulación de headers, enrutamiento complejo. | Recomendado para la mayoría de usuarios. Integración nativa. |
| Lua (OpenResty) | Muy alto (JIT) | API gateways de alta carga, lógica de negocio compleja, caché. | Mejor para aplicaciones exigentes. Requiere build de OpenResty. |
| Perl | Medio | Integración legacy, procesamiento de texto. | Evitar a menos que mantengas sistemas legacy. |
Profundización
Documentación oficial
- Nginx Beginner’s Guide
- ngx_http_core_module —
location,root,alias - ngx_http_rewrite_module —
return,rewrite