Caching, compresion y headers
Tres palancas de rendimiento y control en el borde: cachear respuestas, comprimir cuerpos y fijar cabeceras HTTP (cache del navegador y seguridad).
Mapa mental
Peticion
|-> hit en proxy_cache? -> servir desde disco/memoria
|-> miss -> backend -> guardar en cache (si cacheable)
|-> gzip/brotli si el cliente lo acepta
+-> anadir/editar headers de respuestaCompresion gzip
http {
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5;
gzip_min_length 1024;
gzip_types
text/plain
text/css
text/xml
application/json
application/javascript
application/xml
application/xml+rss
image/svg+xml;
}No comprimas JPEG/PNG/MP4 ya comprimidos: gastas CPU sin ganar tamano.
Comprobar:
curl -H 'Accept-Encoding: gzip' -H 'Host: app.local' -I http://127.0.0.1/app.js
# Content-Encoding: gzip
# Vary: Accept-EncodingBrotli suele ir como modulo dinamico (ngx_brotli) o build custom:
brotli on;
brotli_comp_level 5;
brotli_types text/plain text/css application/json application/javascript;Si el paquete de tu distro no lo trae, gzip es el baseline correcto.
Cache de navegador (expires)
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff2)$ {
root /var/www/app;
expires 7d;
add_header Cache-Control "public";
access_log off;
try_files $uri =404;
}
location = /index.html {
root /var/www/app;
add_header Cache-Control "no-cache";
}expires genera Expires y Cache-Control. Para HTML dinamico o SPA entrypoint, preferir no-cache (revalidar siempre).
Proxy cache
Cachea respuestas del backend en disco:
# en http {}
proxy_cache_path /var/cache/nginx/api
levels=1:2
keys_zone=api_cache:20m
max_size=2g
inactive=60m
use_temp_path=off;
# en server {}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_cache api_cache;
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
proxy_cache_lock on;
add_header X-Cache-Status $upstream_cache_status always;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}sudo mkdir -p /var/cache/nginx/api
sudo chown www-data:www-data /var/cache/nginx/api # o nginx:nginx en RHEL
sudo nginx -t && sudo systemctl reload nginx
curl -I -H 'Host: api.example.com' http://127.0.0.1/items
# X-Cache-Status: MISS (primera)
curl -I -H 'Host: api.example.com' http://127.0.0.1/items
# X-Cache-Status: HITValores de $upstream_cache_status: HIT, MISS, BYPASS, EXPIRED, STALE, UPDATING, REVALIDATED.
Que no cachear
set $skip_cache 0;
if ($request_method = POST) { set $skip_cache 1; }
if ($http_authorization != "") { set $skip_cache 1; }
if ($http_cookie ~* "session|auth") { set $skip_cache 1; }
location / {
proxy_cache api_cache;
proxy_cache_bypass $skip_cache;
proxy_no_cache $skip_cache;
proxy_pass http://127.0.0.1:8080;
}Nunca cachees respuestas autenticadas o personalizadas sin una clave que incluya usuario (y aun asi suele ser mala idea en el borde).
Clave de cache
Por defecto incluye scheme, host, request_uri. Personalizar:
proxy_cache_key "$scheme$request_method$host$request_uri";Si variaciones por Accept-Language, incluye esa variable en la clave o no cachees.
Headers de seguridad
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header Content-Security-Policy "default-src 'self'; frame-ancestors 'self';" always;Con HTTPS:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;always asegura el header tambien en respuestas de error.
CSP hay que afinarla por aplicacion: una politica demasiado estricta rompe scripts inline o CDNs.
Headers de la app vs Nginx
Si el backend ya envia Cache-Control, decide quien manda:
proxy_hide_header Cache-Control;
add_header Cache-Control "public, max-age=60" always;O respeta al backend y no anadas expires conflictivos.
ETag y validacion
Para estaticos, Nginx puede generar ETag. Con etag on; (default en muchas builds) el cliente revalida con If-None-Match y recibe 304.
location /assets/ {
root /var/www;
etag on;
expires 1h;
}Ejemplo conjunto
proxy_cache_path /var/cache/nginx/web keys_zone=web:10m max_size=1g inactive=24h;
server {
listen 80;
server_name www.example.com;
root /var/www/example;
gzip on;
gzip_types text/css application/javascript application/json;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options SAMEORIGIN always;
location /assets/ {
expires 30d;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
location /api/ {
proxy_pass http://127.0.0.1:8080/;
proxy_cache web;
proxy_cache_valid 200 5m;
add_header X-Cache-Status $upstream_cache_status always;
proxy_set_header Host $host;
}
location / {
try_files $uri /index.html;
add_header Cache-Control "no-cache";
}
}Buenas practicas
- Mide hit ratio (
X-Cache-Status) antes de subir TTLs. - Comprime solo tipos text/json/svg.
- Separar politicas de cache: assets inmutables vs HTML vs API.
- Cabeceras de seguridad con
always. - No cachear por defecto respuestas con
Set-CookieoAuthorization.
Errores habituales
- Cachear HTML de SPA un ano: usuarios atrapados en version vieja.
gzip_comp_level 9en todo: CPU al pico, ganancia minima vs 4–5.- Proxy cache de endpoints autenticados sin bypass.
- Olvidar
chownen/var/cache/nginx-> errores enerror.log. - Duplicar
add_headerenlocationy perder headers heredados delserver(en Nginx,add_headeren un nivel reemplaza la herencia).
Ejercicios
- Activa gzip y verifica
Content-Encodingen un JS > 1 KB. - Configura
proxy_cacheconX-Cache-Statusy demuestra MISS seguido de HIT. - Anade bypass para
Authorizationy confirmaBYPASSconcurl -H 'Authorization: Bearer x'. - Pon
Cache-Control: no-cacheen/index.htmlyimmutableen/assets/; valida concurl -I.
Siguiente paso
En el capitulo 7 cerraras el manual con logs estructurados, rate limiting, restriccion de metodos y endurecimiento del servidor.
