# Gestión de exclusiones de caché en Nginx con map en un proxy inverso multi-sitio

- Link: https://binariocero.com/linux/gestion-de-exclusiones-de-cache-en-nginx-con-map-en-un-proxy-inverso-multi-sitio
- Published: 2025-08-28T14:00:24+02:00
- Author: Juan José Fernández Durán

![Loading](https://binariocero.com/wp-content/plugins/page-views-count/ajax-loader-
2x.gif)

Cuando gestionamos un servidor Nginx que actúa como **proxy inverso** para varios
sitios web y backends, es habitual implementar **reglas de caché** para mejorar 
el rendimiento. Sin embargo, no todas las peticiones deben ser cacheadas: hay URIs
sensibles, peticiones `POST`, parámetros de tracking o recursos dinámicos que deben
excluirse.

En Nginx, estas condiciones suelen gestionarse con la directiva `map`. Es fundamental
entender que **los `map` declarados en la configuración de Nginx tienen un alcance
global** dentro del bloque `http { ... }`. Esto significa que:

 * Los `map` se declaran una sola vez a nivel global (http).
 * Una vez definidos, sus variables destino (ej. `$skip_cache`) **están disponibles
   en todos los servidores virtuales (`server {}`)**.
 * Si intentamos definir dos `map` con la misma variable destino en diferentes vhosts,
   obtendremos un error de configuración (`duplicate variable name`).

## Contexto: Proxy inverso con varios sitios

Imaginemos que tenemos un Nginx con varios sitios configurados en `/etc/nginx/sites-
enabled/`, por ejemplo:

 * `undominio.com` → backend en `192.168.2.14`7
 * `otrodominio.com` → backend en `192.168.2.143`

Cada sitio puede tener políticas de caché, pero la lógica de qué **debe o no debe
cachearse** se repite:

 * No cachear `POST`
 * No cachear peticiones con `query string`
 * No cachear accesos a `/wp-admin/`, `xmlrpc.php`, sitemaps, trackers (`utm`, `
   gclid`, `fbclid`, etc.)
 * No cachear ciertos scripts de terceros como Google Analytics o reCAPTCHA

En lugar de duplicar esta lógica en cada sitio, lo ideal es **centralizar los `map`
en un único archivo**.

## Declaración de los `map` globales

Creamos el archivo `/etc/nginx/conf.d/cache-maps.conf` con el siguiente contenido:

    ```wp-block-code
    # /etc/nginx/conf.d/cache-maps.conf
    # Reglas globales para exclusión de caché

    # 1. Detectar si la petición es POST
    map $request_method $is_post {
        POST    1;
        default 0;
    }

    # 2. Detectar si hay query string
    map $query_string $has_query_string {
        ""      0;
        default 1;
    }

    # 3. Detectar URIs sensibles (WordPress admin, xmlrpc, sitemaps, trackers…)
    map $request_uri $is_sensitive_uri {
        ~*/wp-admin/|xmlrpc.php|wp-.*\.php|index.php|.*sitemap.*\.(xml|xsl|hmtl)|.*_ga.*|.*utm.*|.*gclid.*|.*fbclid.*|.*gdpr.*  1;
        default 0;
    }

    # 4. Detectar scripts que nunca deben cachearse (reCAPTCHA, GA, GTM…)
    map $request_uri $is_excluded_js {
        ~*^/recaptcha/api\.js(\?.*)?$|^/analytics\.js(\?.*)?$|^/gtag/js$|^/gtm\.js(\?.*)?$|^/tag/js/gpt\.js(\?.*)?$|^/dist/scripts/main\.js(\?.*)?$|^/assets/js/gdpr_cc_addon\.js(\?.*)?$|^/gdpr-cookie-compliance/dist/scripts/main\.js(\?.*)?$  1;
        default 0;
    }

    # 5. Variable final: si cualquiera de las condiciones anteriores se cumple, saltar caché
    map "$is_post$has_query_string$is_sensitive_uri$is_excluded_js" $skip_cache {
        ~*1     1;
        default 0;
    }
    ```

Este archivo se incluye automáticamente porque la directiva por defecto en `/etc/
nginx/nginx.conf` suele tener:

    ```wp-block-code
    http {
        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;
    }
    ```

## Uso de `$skip_cache` en cada vhost

Ahora, dentro de cada sitio en `/etc/nginx/sites-enabled/`, podemos usar directamente
la variable `$skip_cache` sin necesidad de volver a declarar los `map`.

Ejemplo para **undominio.com**:

    ```wp-block-code
    server {
        listen 443 ssl; 
        http2 on;
        server_name undomino.com www.undomino.com;

        ssl_certificate     /etc/letsencrypt/live/undomino.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/undomino.com/privkey.pem;
        include /etc/letsencrypt/options-ssl-nginx.conf;

        access_log /var/log/nginx/access_undomino.log;

        location / {
            proxy_pass https://192.168.2.147:443;
            include proxy_params;

            proxy_cache undomino;
            proxy_cache_key "$scheme$host$request_uri";
            proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;

            proxy_cache_bypass $skip_cache;
            proxy_no_cache    $skip_cache;

            proxy_cache_valid 200 4h;
            proxy_cache_revalidate on;
            proxy_cache_background_update on;
            proxy_cache_min_uses 1;

            add_header X-Skip-Cache   $skip_cache;
            add_header X-Cache-Status $upstream_cache_status;
        }
    }
    ```

El backend recibirá normalmente todas las peticiones, pero Nginx sólo cacheará aquellas
que **no cumplan las condiciones de exclusión**.

## Validación y pruebas

 1. Validar configuración: `sudo nginx -t`
 2. Recargar servicio: `sudo systemctl reload nginx`
 3. Probar peticiones:
 4.  * `curl -I https://undomino.com/` → debería devolver `X-Skip-Cache: 0` (cacheable).
     * `curl -I https://undomino.com/?utm_source=test` → debería devolver `X-Skip-Cache:
       1` (no cacheable).
     * `curl -I -X POST https://undomino.com/` → también `X-Skip-Cache: 1`.

## Conclusiones

 * Los `map` en Nginx **son globales a todo el bloque `http`**, no a un único `server`.
 * Declararlos dentro de cada vhost provoca conflictos si se repiten variables.
 * La práctica recomendada es **centralizar la lógica de exclusión de caché en un
   archivo común** (`/etc/nginx/conf.d/cache-maps.conf`).
 * Una vez definidos, las variables como `$skip_cache` se pueden reutilizar en todos
   los sitios, simplificando y unificando la gestión de la caché en un entorno multi-
   sitio.
