Empezando a ver Nginx

19 min

language: ja bn en es hi pt ru zh-cn zh-tw

Hola, el título es un juego de palabras con aquello. Es solo una canción que recordé de repente.

La razón por la que ha habido una cantidad inusual de actualizaciones de artículos recientemente es simplemente porque una vez que algo me empieza a preocupar, no puedo parar, y estoy en esa fase.

Nginx

Hasta ahora, lo había configurado básicamente como un servidor de caché y proxy inverso, pero con la compatibilidad con IPv6, me empezó a picar la curiosidad, así que revisé la configuración, incluyendo una refactorización.

Estructura de archivos

He ocultado algunas partes, pero se ve algo así:

├── http.d
│   ├── bot_rate_limit.conf
│   ├── gzip.conf
│   ├── proxy_cache_zones.conf
│   └── proxy_common.conf
├── mime.types
├── mime.types-dist
├── nginx.conf
├── scgi_params
├── selfsigned.crt
├── selfsigned.key
├── sites-enabled
│   ├── 1btc.love.conf
│   ├── btclol.xyz.conf
│   ├── damepo.jp.conf
│   ├── git.soulminingrig.com.conf
│   ├── soulminingrig.com.conf
│   ├── starlink.soulminingrig.com.conf
│   ├── stg.api.1btc.love.conf
├── snippets
│   ├── common_error_pages.conf
│   ├── proxy_headers.conf
│   └── ssl_common.conf
├── uwsgi_params
└── win-utf

Dudé un poco sobre qué nombre darle a la carpeta para gestionar los archivos conf que se cargan por directiva location, pero después de consultarlo con ChatGPT, quedó así. http.d contiene los archivos que se incluyen desde la directiva http en nginx.conf, así que me pareció bastante lógico.

Aunque pensé que el nombre snippets era un poco peculiar, supongo que está bien.

Echemos un vistazo a cada elemento de configuración.

http.d/bot_rate_limit.conf

Como los rastreadores de Meta eran demasiado intensos, decidí aplicar límites no solo con fail2ban, sino también por User-Agent (UA).

Además, con respecto a feed/RSS, recibí una notificación muy amable, pero en primer lugar no tiene sentido aplicarlo y, dado que casi siempre responde desde la caché, no supone una carga significativa para el origen, así que hice una excepción.

# Aplicar límite de tasa solo a bots y rastreadores de expansión de enlaces
map $http_user_agent $is_bot {
    default 0;
    ~*bot 1;
    ~*crawler 1;
    ~*spider 1;
    ~*facebookexternalhit 1;
    ~*slackbot 1;
    ~*discordbot 1;
    ~*twitterbot 1;
    ~*linkedinbot 1;
    ~*embedly 1;
    ~*quora 1;
    ~*skypeuripreview 1;
    ~*whatsapp 1;
    ~*telegrambot 1;
    ~*applebot 1;
    ~*pingdom 1;
    ~*uptimerobot 1;
}
# stg.api.1btc.love es para fines de verificación, por lo que se excluye del límite de tasa incluso si es un bot
# Si la clave es una cadena vacía, no se cuenta en limit_req_zone
map $server_name $bot_limit_host_key {
    stg.api.1btc.love "";
    default $binary_remote_addr;
}
# feed.xml / feed.json se excluyen del límite de tasa incluso si es un bot
map $uri $is_feed_path {
    default 0;
    ~*feed\.(xml|json)$ 1;
}
# Usar clave por IP solo cuando es un bot y no es una ruta de feed.xml / feed.json
map "$is_bot:$is_feed_path" $bot_limit_key {
    default "";
    "1:0" $bot_limit_host_key;
}
limit_req_zone $bot_limit_key zone=bot:10m rate=1r/s;
limit_req_status 429;
limit_req zone=bot burst=5 nodelay;

Por supuesto, dado que se aplica a la directiva http, básicamente afecta a todo, pero me aseguré de permitir excepciones mínimas. Considero que esto es algo que debería estar habilitado por defecto.

Si alguien falsifica el UA o el navegador para realizar un DoS evidente, será bloqueado por fail2ban y tratado como drop, impidiéndole enviar solicitudes por un tiempo, así que es una defensa de dos capas.

http.d/gzip.conf

Antes solía usar brotli, pero tener que compilarlo por separado era un fastidio al actualizar versiones, así que dejé de hacerlo. La ventaja de poder actualizar mediante pkg/apt es enorme.

gzip on;
gzip_vary off;
gzip_proxied any;
gzip_min_length 1024;
gzip_comp_level 7;
gzip_http_version 1.1;
gzip_types text/plain
text/xml
text/css
text/javascript
image/gif
image/png
image/svg+xml
application/javascript
application/json
application/xml
application/x-javascript
application/font-woff
application/font-woff2
application/font-ttf
application/octet-stream;

No hay mucho más que decir; no solía incluirlo antes, pero desde hace unos años empecé a usar gzip_min_length. Fue cuando decidí que la compresión ineficiente era un desperdicio y quise configurarlo correctamente.

http.d/proxy_cache_zones.conf

Parece que la sangría está un poco desajustada, pero es una limitación del formateador.

He borrado lo que no necesitaba, por lo que las zone empiezan desde la 4 o están desordenadas, pero bueno...

En cuanto a inactive, los elementos se eliminan si no hay hits en 7 días, y con use_temp_path, se almacena en caché directamente en la ruta de caché. Parece que incluso si tienes una configuración como proxy_temp_path /tmp/nginx;, se almacena en caché sin pasar por ella, lo que lo hace más rápido.

proxy_cache_path /tmp/nginx/zone4 levels=1:2 keys_zone=zone4:10m
inactive=7d
max_size=3g
use_temp_path=off;
proxy_cache_path /tmp/nginx/posts levels=1:2 keys_zone=posts:10m inactive=7d max_size=2g use_temp_path=off; proxy_cache_path /tmp/nginx/git levels=1:2 keys_zone=git:10m inactive=7d max_size=2g use_temp_path=off; proxy_cache_path /tmp/nginx/static levels=1:2 keys_zone=static_cache:10m inactive=7d max_size=1g use_temp_path=off; proxy_cache_path /tmp/nginx/1btc_cache levels=1:2 keys_zone=1btc_cache:10m inactive=7d max_size=512m use_temp_path=off;

http.d/proxy_common.conf

He configurado proxy_cache_valid como una regla común y el resto se sobrescribe en la directiva location.

De esta manera, se puede operar con caché incluso sin haber introducido configuraciones de caché específicas.

Al usar proxy_cache_bypass $http_cookie, se evita que las solicitudes con cookies, como después de iniciar sesión, puedan mostrar la pantalla anterior al inicio de sesión.

Además, hacer que las redirecciones, los errores y any devuelvan respuestas en caché es una medida contra ataques. Al hacer esto, se sirve al menos una respuesta en caché, evitando que el tráfico anormal llegue al Origin.

Aunque lo ideal sería que proxy_temp_path fuera una ruta persistente, básicamente en mi caso no se dan esas situaciones, así que lo he puesto en /tmp. En un sitio con un tráfico considerable, hacer esto (junto con esa opción que no se usa mencionada antes) probablemente causaría que todo el caché desaparezca al reiniciar el servidor, aumentando la carga en el Origin y el riesgo de fallos.

proxy_buffering on;
proxy_cache_bypass $http_cookie;
proxy_cache_background_update on;
proxy_cache_key "$scheme$request_method$host$request_uri";
proxy_cache_revalidate on;
proxy_cache_use_stale updating;
proxy_connect_timeout 60;
proxy_no_cache $http_cookie;
proxy_read_timeout 90;
proxy_send_timeout 60;
proxy_temp_path /tmp/nginx;
proxy_cache_valid 200 201 60s;
proxy_cache_valid 301 1d;
proxy_cache_valid 302 3h;
proxy_cache_valid 304 1d;
proxy_cache_valid 404 1m;
proxy_cache_valid any 5s;
proxy_cache_lock on;

nginx.conf

Simplemente lo he configurado para devolver un error específico si se accede directamente a la IP del registro A, así que no hay mucho más que decir.

Tengo activado multi_accept porque mi servidor de proxy inverso y caché es una instancia modesta con especificaciones no muy altas, así que lo he habilitado para ese funcionamiento.

Las configuraciones que originalmente estaban escritas directamente en la directiva http ahora se incluyen mediante include según su propósito, lo que ha mejorado mucho la claridad.

worker_processes auto;
worker_cpu_affinity auto;
worker_rlimit_nofile 65535;
events {
    multi_accept on;
    worker_connections 65535;
}
http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    server_tokens off;
    types_hash_max_size 4096;
    client_max_body_size 16M;
    # MINE
    include mime.types;
    default_type application/octet-stream;
    include ./http.d/bot_rate_limit.conf;
    include ./http.d/proxy_common.conf;
    include ./http.d/proxy_cache_zones.conf;
    include ./http.d/gzip.conf;
    server {
        listen 80;
        listen [::]:80;
        server_name 163.44.113.145 91.98.169.80 2400:8500:2002:3317:163:44:113:145;
        include snippets/common_error_pages.conf;
        return 444;
    }
    server {
        listen 443 ssl;
        listen [::]:443 ssl;
        server_name 163.44.113.145 91.98.169.80 2400:8500:2002:3317:163:44:113:145;
        ssl_certificate ./selfsigned.crt;
        ssl_certificate_key ./selfsigned.key;
        include snippets/common_error_pages.conf;
        # Denegar el acceso a la dirección IP
        return 444;
    }
    ### Damepo.jp
    include ./sites-enabled/damepo.jp.conf;
    ### Soulminingrig My Blog
    include ./sites-enabled/soulminingrig.com.conf;
# ~~~omitido~~~~
}

sites-enabled/soulminingrig.com.conf

Presentaré solo un ejemplo bajo site-enabled. Es la configuración de este sitio.

Además, la razón por la que no utilizo enlaces simbólicos es simplemente porque quiero poder eliminar rápidamente lo que no sea necesario.

Tenga en cuenta que algunas partes todavía están codificadas de forma fija (hardcoded), ya que aún estoy en proceso de ajustarlas.

Por cierto, hace poco cambié a www.soulminingrig.com, pero antes de eso servía desde el dominio raíz sin www, así que por ahora sigo aceptando el dominio raíz sin redireccionar.

Como periodo de verificación, he habilitado respuestas de encabezado que facilitan ver si respondió el caché del servidor o el del cliente.

Y me pregunto si podría hacer algo más con las reglas de caché para imágenes, fuentes, CSS, etc., y eso me tiene pensativo... ¿No se puede hacer nada con esto...?

He configurado upstream para poder responder rápidamente si aumenta el número de backends. Bueno, de momento solo hay uno, pero tenerlo al principio facilita ver a qué backend apunta la configuración.

upstream backend_sm {
    server 10.1.0.228:8888 max_fails=3 fail_timeout=3s;
    keepalive 16;
    keepalive_timeout 30s;
}
map $uri $static_cache {
    ~\.(jpg|jpeg|png|webp|gif|mp4|css|js|ico|woff2)(\?.*)?$ "public, max-age=604800";
    ~\.html$ "public, max-age=600";
    default "public, max-age=600";
}
map $upstream_cache_status $server_cache_status {
    default $upstream_cache_status;
    "" "NONE";
}
map "$http_if_none_match:$http_if_modified_since" $client_cache_request {
    default "MISS";
    "~.+:.+" "REVALIDATE";
    "~.+:" "REVALIDATE";
    "~:.+" "REVALIDATE";
}
server {
    listen 80;
    listen [::]:80;
    server_name soulminingrig.com www.soulminingrig.com;
    return 301 https://$host$request_uri;
}
server {
    listen 443 ssl reuseport backlog=65535 rcvbuf=256k sndbuf=256k fastopen=256 so_keepalive=on;
    listen [::]:443 ssl reuseport backlog=65535 rcvbuf=256k sndbuf=256k fastopen=256 so_keepalive=on ipv6only=on;
    listen 443 quic reuseport;
    listen [::]:443 quic reuseport;
    http2 on;
    http3 on;
    server_name soulminingrig.com www.soulminingrig.com;
    client_max_body_size 50M;
    location ~* \.(jpg|jpeg|png|webp|gif|ico|mp4|js|css|woff2)(\?.*)?$ {
        proxy_pass http://backend_sm;
        include snippets/proxy_headers.conf;
        proxy_http_version 1.1;
        proxy_redirect off;
        proxy_cache static_cache;
        proxy_cache_valid 200 301 302 7d;
        proxy_cache_valid 404 1m;
        proxy_cache_revalidate on;
        proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
        proxy_cache_background_update on;
        proxy_cache_lock on;
        proxy_connect_timeout 3s;
        proxy_read_timeout 15s;
        expires 7d;
        add_header X-Cache-Status $upstream_cache_status always;
        add_header X-Server-Cache-Status $server_cache_status always;
        add_header X-Client-Cache-Request $client_cache_request always;
        add_header X-Client-Cache-Policy "public, max-age=604800" always;
        add_header Cache-Control "public, max-age=604800" always;
    }
    location / {
        proxy_pass http://backend_sm/;
        include snippets/proxy_headers.conf;
        proxy_http_version 1.1;
        proxy_redirect off;
        proxy_cache posts;
        proxy_cache_key $scheme$host$request_uri;
        proxy_cache_valid 200 10m;
        proxy_cache_valid 301 1h;
        proxy_cache_valid 404 1m;
        proxy_cache_revalidate on;
        proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504 http_403 http_404;
        proxy_cache_background_update on;
        proxy_cache_lock on;
        proxy_connect_timeout 3s;
        proxy_read_timeout 15s;
        expires $static_cache;
        add_header X-Cache-Status $upstream_cache_status always;
        add_header X-Server-Cache-Status $server_cache_status always;
        add_header X-Client-Cache-Request $client_cache_request always;
        add_header X-Client-Cache-Policy $static_cache always;
        add_header Cache-Control $static_cache always;
    }
    include snippets/common_error_pages.conf;
    include snippets/ssl_common.conf;
    ssl_certificate /hoge/fullchain.pem; # managed by Certbot
    ssl_certificate_key /hoge/oulminingrig.com/privkey.pem;
    # managed by Certbot
}

Extra: nginxfmt.py

Esto es realmente bueno.

Es un formateador, pero me parece que es el que tiene la mayor calidad.

Está disponible en AUR.

yay -S nginx-config-formatter

Para usarlo, nginxfmt.py example.conf realiza el formateo, así que con:

find . -name "*conf" | xargs -I{} nginxfmt.py {}

se puede formatear todo en lote.

Hasta hace poco usaba nginxbeautifier, pero la sintaxis de las redirecciones se rompía, así que me cambié.

Related Posts