Volver al Blog
José Manuel Requena Plens

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.

Imagen de portada de Domina los archivos virtuales en Nginx: Guía completa

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 de bloques Location (de mayor a menor)
PrioridadModificadorTipoEjemplo
1=Coincidencia exactalocation = /robots.txt
2^~Prefijo (detiene búsqueda regex)location ^~ /static/
3~Regex sensible a mayúsculaslocation ~ \.php$
4~*Regex insensible a mayúsculaslocation ~* \.(jpg|png)$
5(ninguno)Prefijo (coincidencia más larga)location /api/

Ejemplo práctico

Considera esta configuración:

location-priority.conf
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

root vs alias vs try_files
DirectivaComportamientoIdeal para
rootAñade la URI a la rutaDocument roots estándar
aliasReemplaza el prefijo coincidente con la rutaServir archivos desde ubicaciones no estándar
try_filesComprueba archivos en orden, recurre al últimoSPAs, servicio condicional de archivos

Comparación visual

root (añade la URI)
location /images/ {
    root /var/www;
}

# Request: /images/photo.jpg
# Serves:  /var/www/images/photo.jpg
#          ^^^^^^^^ root + URI
alias (reemplaza el prefijo)
location /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:

try_files example
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:

  1. $uri — Intenta la ruta exacta del archivo
  2. $uri/ — Intenta como directorio con index
  3. @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

Patrones de la directiva return
PatrónDescripciónEjemplo
return CODE;Devuelve solo el código de estadoreturn 204;
return CODE TEXT;Devuelve código de estado con cuerporeturn 200 “OK”;
return CODE URL;Redirección (301, 302, 307, 308)return 301 https://…;

Ejemplo de contenido inline

return examples
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

named-location.conf
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:

spa-fallback.conf
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:

internal-location.conf
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:

humans.txt location
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.

ads.txt location
# 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.

favicon handling
# 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).

mobile-icons.conf
# 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:

security.txt
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-challenge.conf
# 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:

mobile-associations.conf
# 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

URIs .well-known comunes
RutaPropósitoContent-Type
/.well-known/change-passwordRedirección a la página de cambio de contraseñaRedirect (302)
/.well-known/webfingerDescubrimiento de usuarios (ActivityPub, email)application/jrd+json
/.well-known/openid-configurationDescubrimiento de OpenID Connectapplication/json
/.well-known/matrix/clientDescubrimiento de cliente Matrixapplication/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):

/​etc/​nginx/​njs/​virtual.js
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 };
njs-location.conf
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

health-endpoint.conf
location = /health {
    access_log off;  # Don't spam logs
    default_type application/json;
    return 200 '{"status":"healthy"}';
}

Probes de Kubernetes

k8s-probes.conf
# 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):

stub_status.conf
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

performance.conf
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é

caching.conf
# 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

docker-nginx.conf
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

k8s-ingress-nginx.conf
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:

/​etc/​nginx/​sites-available/​example.com.conf
# 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

# Test exact match curl -v https://example.com/robots.txt # Check response headers curl -I https://example.com/health # Follow redirects curl -L https://example.com/old-page

Validar la configuración

# Syntax check sudo nginx -t # Reload configuration sudo nginx -s reload # Check error logs tail -f /var/log/nginx/error.log
Problemas comunes
ProblemaCausaSolución
404 en archivo virtualEl location no coincideComprueba el modificador de location (= vs prefijo)
Se sirve contenido incorrectoOtro location coincide primeroRevisa la prioridad; usa = para exacto
Content-Type incorrectoFalta el header de tipoAñade default_type o add_header
Problemas con la ruta de aliasFalta la barra finalAsegú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:

Comparación de opciones de scripting en Nginx
LenguajeRendimientoCaso de usoRecomendación
njs (JavaScript)AltoContenido 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.
PerlMedioIntegración legacy, procesamiento de texto.Evitar a menos que mantengas sistemas legacy.

Profundización

Documentación oficial

Tutoriales

Estándares