# Domina los archivos virtuales en Nginx: Guía completa

> Una entrada de jmrp.io, publicada como documento propio. Índice: https://jmrp.io/llms-full.txt

> Generated: 2026-08-31

URL: https://jmrp.io/es/blog/002-serve-virtual-files-nginx/
Language: es
Alternate: https://jmrp.io/blog/002-serve-virtual-files-nginx/index.md
License: https://creativecommons.org/licenses/by/4.0/
Type: TechArticle
Published: 2025-12-18
Updated: 2026-08-02
Last verified: 2026-08-22 · nginx 1.31.3
Author: José Manuel Requena Plens
Summary: 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.
Tags: Nginx, DevOps, Security
Topics: Nginx (Q306144), Reverse proxy (Q1192309), Kubernetes (Q22661306), Docker (Q15206305), robots.txt (Q80776), security.txt (Q65057155)

Preguntas que responde:

**¿Cómo sirvo robots.txt o un health check en Nginx sin un archivo en disco?**

Usa un bloque location con la directiva return para enviar contenido inline, por ejemplo `location = /robots.txt { default_type text/plain; return 200 "User-agent: *\nAllow: /\n"; }`. Esto sirve el contenido directamente desde la configuración sin ningún archivo físico, ideal para containers y plataformas gestionadas donde no puedes modificar la imagen ni el filesystem.

**¿Cuál es la diferencia entre root y alias en Nginx?**

`root` añade la URI de la petición a la ruta indicada, así que `root /var/www` con una petición a `/images/photo.jpg` sirve `/var/www/images/photo.jpg`. `alias` reemplaza el prefijo coincidente del location con la ruta, así que `alias /data/photos/` para `location /images/` sirve `/data/photos/photo.jpg`. Usa `root` para document roots estándar y `alias` para archivos en ubicaciones no estándar.

**¿Por qué mi bloque location de Nginx no coincide con la petición correcta?**

Nginx selecciona los locations por prioridad, no por el orden en el archivo de configuración. La coincidencia exacta (`=`) gana primero, luego el prefijo con `^~`, luego regex (`~` y `~*`), y por último el prefijo simple por coincidencia más larga. Usa `=` para archivos virtuales como `/robots.txt` para que gane la coincidencia exacta y Nginx deje de buscar inmediatamente, que es además la opción más rápida.

**¿Cómo establezco el Content-Type de una respuesta return inline en Nginx?**

Usa la directiva `default_type` dentro del bloque location, por ejemplo `default_type application/json;` antes de un `return 200` con JSON. La directiva `add_header` puede añadir otros headers pero solo se aplica a ciertos códigos de respuesta (200, 201, 204, 206, 301, 302, 303, 304, 307, 308) a menos que se use el flag `always`.

**¿Debo usar njs o la directiva return para archivos virtuales en Nginx?**

Usa `return` para contenido estático porque es más rápido; usa njs (Nginx JavaScript) solo cuando necesites lógica como contenido condicional o JSON dinámico. A diferencia de `return`, que solo sirve cadenas estáticas, njs permite control programático completo.

**¿Cómo añado probes de liveness y readiness de Kubernetes con Nginx?**

Define bloques location de coincidencia exacta que devuelvan una respuesta simple, como `location = /healthz { access_log off; return 200 'OK'; }` para liveness y `location = /ready { access_log off; return 200 'Ready'; }` para readiness. Desactivar access_log evita que los logs se saturen con los probes frecuentes.


Pasos (Servir archivos virtuales en Nginx sin I/O de disco):
1. Entender la prioridad del location matching
2. Elegir entre root, alias o try_files
3. Servir contenido inline con la directiva return
4. Añadir named locations y fallbacks internos
5. Definir archivos web estándar y .well-known
6. Usar njs para archivos virtuales dinámicos
7. Añadir endpoints de salud y monitorización
8. Validar y recargar la configuración

---

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.

## TL;DR — sirve archivos que Nginx nunca lee del disco

- **El location matching gana por prioridad, no por el orden de la configuración:** exacto `=` primero, luego `^~`, después regex y por último el prefijo más largo — usa `=` para archivos virtuales como `/robots.txt`.
- **`root` vs `alias` vs `try_files`:** `root` añade la URI, `alias` reemplaza el prefijo coincidente y `try_files` comprueba archivos en orden con un fallback.
- **La directiva `return` sirve contenido inline** (texto, JSON, redirecciones, `204` vacío) sin ningún archivo físico; establece el tipo con `default_type`.
- **Las named locations (`@nombre`) e `internal`** habilitan fallbacks de SPA, proxy a backend y descargas protegidas (cuidado con el path traversal usando `$arg_file`).
- **Sirve archivos web estándar y `.well-known`** (robots.txt, humans.txt, favicon.ico, security.txt, ACME challenges) inline o con `alias`.
- **Usa njs solo para lógica;** añade probes de salud de Kubernetes de coincidencia exacta con `access_log off`, y valida con `nginx -t` y recarga.

---

## ¿Cómo elige Nginx qué bloque location gana?

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)**

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

**Fichero: `location-priority.conf`**

```nginx
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/;
    }
}
```

**Idea clave**

**Consejo de rendimiento:** Usa `=` (coincidencia exacta) para archivos virtuales como `/robots.txt`. Es el más rápido porque Nginx deja de buscar inmediatamente después de encontrar la coincidencia.

---

## Directivas principales: root, alias, try_files

Estas tres directivas determinan _dónde_ busca Nginx los archivos.

Entender la diferencia es crucial.

### ¿Cuál es la diferencia entre root y alias?

**root vs alias vs try_files**

| 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

**Comparación: root (añade la URI) vs alias (reemplaza el prefijo)**

**root (añade la URI)**

```nginx
location /images/ {
    root /var/www;
}

# Request: /images/photo.jpg
# Serves:  /var/www/images/photo.jpg
#          ^^^^^^^^ root + URI
```

**alias (reemplaza el prefijo)**

```nginx
location /images/ {
    alias /data/photos/;
}

# Request: /images/photo.jpg
# Serves:  /data/photos/photo.jpg
#          ^^^^^^^^^^^^ alias replaces /images/
```

**Advertencia**

**Error común:** Para alias de directorios con prefijo (p. ej., `location /images/`), incluye una barra final tanto en el location como en la ruta del alias. Para alias de coincidencia exacta (p. ej., `location = /robots.txt`), las barras finales no aplican.

### try_files para cadenas de fallback

La directiva `try_files` es increíblemente potente para archivos virtuales:

**try_files example**

```nginx
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ó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

**return examples**

```nginx
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;
    }
}
```

**Información**

**Nota:** Usa `default_type` para establecer el Content-Type en los cuerpos de `return` inline. La directiva `add_header` puede añadir otros headers pero solo se aplica a ciertos códigos de respuesta (200, 201, 204, 206, 301, 302, 303, 304, 307, 308) a menos que se use el flag `always`.

---

## Named locations y fallbacks

Las named locations (con prefijo `@`) permiten patrones de fallback sofisticados sin reescritura de URL.

### ¿Cómo funciona el patrón @fallback?

**Fichero: `named-location.conf`**

```nginx
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:

**Fichero: `spa-fallback.conf`**

```nginx
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:

**Fichero: `internal-location.conf`**

```nginx
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;
}
```

**Advertencia**

**Advertencia de seguridad:** El patrón `alias /var/secure/files/$arg_file;` es vulnerable a ataques de path traversal si `$arg_file` no se valida. Un atacante podría solicitar `?file=../../../etc/passwd` para acceder a archivos fuera del directorio previsto.

**Mitigaciones:**

- Asegúrate de que `secure_link_md5` incluya `$arg_file` en el cálculo del hash
- Valida los nombres de archivo con una directiva `map` (p. ej., rechazar patrones que contengan `..`)
- Usa una aplicación backend para validar y generar headers `X-Accel-Redirect`

**Información**

**Nota:** El header `X-Accel-Redirect` debe ser establecido por una respuesta upstream (p. ej., desde una aplicación backend), no mediante `add_header` en Nginx. Usa `try_files` o `rewrite` para redirecciones internas directas dentro del propio Nginx.

---

## Archivos web estándar

Estos archivos virtuales son fundamentales para SEO, seguridad y cumplimiento normativo.

### robots.txt

El archivo [robots.txt](https://developers.google.com/search/docs/crawling-indexing/robots/intro) indica a los crawlers web qué partes de tu sitio pueden acceder.

**Opción 1/2 — Inline (return)**

```nginx
location = /robots.txt {
    default_type text/plain;
    return 200 "User-agent: *\nAllow: /\nSitemap: https://example.com/sitemap.xml\n";
}
```

**Opción 2/2 — Externo (alias)**

```nginx
location = /robots.txt {
    alias /etc/nginx/assets/robots.txt;
    default_type text/plain;
}
```

### humans.txt

Da crédito a tu equipo con [humans.txt](https://humanstxt.org/):

**Fichero: `humans.txt` location**

```nginx
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](https://iabtechlab.com/ads-txt/) requiere un formato CSV específico: `Domain, Publisher ID, Account Type, Certification Authority ID`.

**Fichero: `ads.txt` location**

```nginx
# 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**

```nginx
# 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](https://evilmartians.com/chronicles/how-to-favicon-in-2021-six-files-that-fit-most-needs)

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

**Fichero: `mobile-icons.conf`**

```nginx
# 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"
    }';
}
```

**Consejo**

**Consejo de producción:** Aunque el JSON inline es conveniente para ejemplos, servir archivos de manifiesto desde el filesystem es más mantenible. Crea un archivo `site.webmanifest` real y sírvelo con `alias` para evitar errores de sintaxis y simplificar las actualizaciones.

---

## El directorio .well-known

El directorio [.well-known](https://www.iana.org/assignments/well-known-uris) es una ubicación estándar para metadatos del sitio.

### security.txt (RFC 9116)

Permite a los investigadores de seguridad reportar vulnerabilidades:

**Fichero: `security.txt`**

```nginx
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:

**Fichero: `acme-challenge.conf`**

```nginx
# 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:

**Fichero: `mobile-associations.conf`**

```nginx
# 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**

| 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`:

```nginx
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):

**Fichero: `/etc/nginx/njs/virtual.js`**

```javascript
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 };
```

**Fichero: `njs-location.conf`**

```nginx
http {
    js_import /etc/nginx/njs/virtual.js;

    server {
        location = /robots.txt {
            js_content virtual.robots;
        }
    }
}
```

**Advertencia**

**Nota:** Servir diferente contenido de `robots.txt` según el User-Agent se muestra aquí solo con fines ilustrativos. En la práctica, los crawlers esperan un `robots.txt` consistente y puede que no lo vuelvan a solicitar con diferentes User-Agents. Para el [bloqueo real de bots](/es/blog/005-implementing-tarpit-nginx/), considera usar `robots.txt` con reglas específicas de User-Agent, o bloqueo del lado del servidor basado en patrones de IP/User-Agent.

**Información**

**Nota de rendimiento:** `njs` es muy rápido, pero `return` es más rápido. Usa `return` para contenido estático y `njs` solo cuando necesites lógica.

---

## Endpoints de salud y monitorización

Críticos para la orquestación de containers y la observabilidad.

### Health check básico

**Fichero: `health-endpoint.conf`**

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

### Probes de Kubernetes

**Fichero: `k8s-probes.conf`**

```nginx
# 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`):

**Fichero: `stub_status.conf`**

```nginx
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;
}
```

**Salida de stub_status**

```text
Active connections: 42
server accepts handled requests
 12345 12345 98765
Reading: 0 Writing: 3 Waiting: 39
```

**Consejo**

**Panel moderno:** Si prefieres una interfaz visual, **[Nginx-UI](https://nginxui.com/)** es una potente herramienta open-source que aprovecha el módulo `stub_status` para proporcionar gráficos de monitorización en tiempo real, edición de configuración y gestión de SSL en un panel web limpio.

---

## Optimización de rendimiento

Maximiza la eficiencia al servir archivos virtuales.

### Optimización del servicio de archivos

**Fichero: `performance.conf`**

```nginx
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é

**Fichero: `caching.conf`**

```nginx
# 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

**Fichero: `docker-nginx.conf`**

```nginx
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

**Fichero: `k8s-ingress-nginx.conf`**

```nginx
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:

**Fichero: `/etc/nginx/sites-available/example.com.conf`**

```nginx
# 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

```bash
# 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

```bash
# Syntax check
sudo nginx -t

# Reload configuration
sudo nginx -s reload

# Check error logs
tail -f /var/log/nginx/error.log
```

**Problemas comunes**

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

**Comparación de opciones de scripting en Nginx**

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

---

## Cómo lo uso en mi propia infraestructura

Este sitio retiró quince etiquetas del blog de una vez, lo que dejó sesenta URLs
que estaban indexadas y enlazadas apuntando a páginas que ya no existían. Cada
una de ellas es un bloque `location` de coincidencia exacta en la configuración
de nginx que hay delante de esta misma página: la técnica que describe este
artículo aplicada a un problema real, no a uno de ejemplo.

```nginx
location = /blog/tags/mtls/  { return 301 /blog/tags/security/; }
location = /blog/tags/mtls   { return 301 /blog/tags/security/; }
```

Ahora mismo están los sesenta en producción: quince etiquetas, cada una escrita
dos veces para las formas con y sin barra final, en los dos idiomas. Son
coincidencias `=` a propósito: una coincidencia por prefijo sobre `/blog/tags/` se tragaría las
etiquetas que siguen existiendo, y además la forma `=` hace que nginx deje de
buscar, que es la regla de prioridad del principio de este artículo trabajando
de verdad.

Uno de ellos me costó una tarde. La etiqueta retirada `key derivation` lleva un
espacio, así que la URL es `/blog/tags/key%20derivation/`. El bloque evidente no
funciona:

```nginx
# No coincide nunca. nginx decodifica la URI *antes* de compararla con el
# valor de un `location =`, así que cuando llega la comparación la ruta
# contiene un espacio literal y este patrón contiene "%20".
location = /blog/tags/key%20derivation/ { return 301 /blog/tags/cryptography/; }
```

La URI se normaliza y se decodifica antes de la coincidencia de location, de
modo que el valor en la configuración tiene que ser aquello a lo que la ruta
*decodifica* —un espacio literal—, que a su vez hay que entrecomillar para que
nginx llegue siquiera a parsear la directiva:

```nginx
location = "/blog/tags/key derivation/" { return 301 /blog/tags/cryptography/; }
location = "/blog/tags/key derivation"  { return 301 /blog/tags/cryptography/; }
```

Esa es la versión que corre ahora, y
`curl -I "https://jmrp.io/blog/tags/key%20derivation/"` devuelve el 301. Conviene
saberlo antes de escribir un bloque de coincidencia exacta para cualquier ruta
que pueda contener un carácter codificado, porque el fallo es silencioso: nginx
acepta la configuración, `nginx -t` pasa, y la regla simplemente no se dispara
nunca.

---

## Profundización

### Documentación oficial

- [Nginx Beginner's Guide](https://nginx.org/en/docs/beginners_guide.html)
- [ngx_http_core_module](https://nginx.org/en/docs/http/ngx_http_core_module.html) — `location`, `root`, `alias`
- [ngx_http_rewrite_module](https://nginx.org/en/docs/http/ngx_http_rewrite_module.html) — `return`, `rewrite`

### Tutoriales

- [DigitalOcean: Understanding Server and Location Blocks](https://www.digitalocean.com/community/tutorials/understanding-nginx-server-and-location-block-selection-algorithms)
- [Nginx Admin Guide: Web Server](https://docs.nginx.com/nginx/admin-guide/web-server/web-server/)

### Estándares

- [RFC 9116: security.txt](https://datatracker.ietf.org/doc/html/rfc9116)
- [IANA Well-Known URIs](https://www.iana.org/assignments/well-known-uris)
- [robots.txt specification](https://developers.google.com/search/docs/crawling-indexing/robots/intro)

