Начинаем понимать Nginx
Привет, заголовок — это пародия на ту самую вещь. Просто песня, которую я внезапно вспомнил.
Причина, по которой в последнее время так много обновлений статей, заключается в том, что как только я начинаю о чем-то беспокоиться, я не могу остановиться, так что сейчас просто такой период.
Nginx
До сих пор я использовал его просто как обратный прокси и кэширующий сервер, но вместе с поддержкой IPv6 меня это заинтересовало, поэтому я пересмотрел настройки, включая рефакторинг.
Структура файлов
Некоторые части скрыты, но выглядит это примерно так:
├── 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
Я немного сомневался, в папке с каким названием управлять файлами conf, которые загружаются на уровне директивы location, но после консультации с ChatGPT получилось вот так. http.d — это файлы, которые подключаются через include из директивы http в nginx.conf, так что это решение кажется вполне логичным.
Хотя название snipets (snippets) кажется немного спорным, ну да ладно.
Давайте рассмотрим каждый пункт настроек.
http.d/bot_rate_limit.conf
Краулеры Meta были слишком агрессивными, поэтому я решил ввести ограничения не только через fail2ban, но и на основе User-Agent (UA).
Кроме того, по поводу feed/RSS мне вежливо написали, но применять ограничения к ним нет смысла, так как ответы в основном идут из кэша и почти не создают нагрузки на Origin, поэтому я сделал для них исключение.
# Ограничение скорости только для ботов и краулеров развертывания ссылок
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 используется для тестирования, поэтому исключается из ограничений даже для ботов
# Если ключ — пустая строка, он не учитывается в limit_req_zone
map $server_name $bot_limit_host_key {
stg.api.1btc.love "";
default $binary_remote_addr;
}
# feed.xml / feed.json исключаются из ограничений даже для ботов
map $uri $is_feed_path {
default 0;
~*feed\.(xml|json)$ 1;
}
# Использовать ключ на основе IP только если это бот и путь не является 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;
Конечно, поскольку это применяется к директиве http, настройки действуют глобально, но я сделал так, чтобы можно было настроить минимальные исключения. Я решил, что это то, что в идеале должно быть включено по умолчанию.
Если кто-то подделывает UA или имитирует браузер для явной DoS-атаки, он будет заблокирован fail2ban со статусом drop и не сможет отправлять запросы какое-то время, так что это двухэтапная защита.
http.d/gzip.conf
Раньше я настраивал поддержку brotli, но отдельная сборка усложняет процесс обновления версий, поэтому я отказался от этого. Преимущество возможности обновления через pkg/apt огромно.
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;
Особо сказать нечего, раньше я его не использовал, но несколько лет назад начал добавлять gzip_min_length. Это было тогда, когда я решил, что неэффективное сжатие — пустая трата ресурсов, и решил настроить всё правильно.
http.d/proxy_cache_zones.conf
Может показаться, что отступы немного поехали, но это ограничение форматировщика.
Я удалил ненужное, поэтому номера zone начинаются с 4 или идут вразнобой, но что поделать...
Что касается inactive, данные удаляются, если к ним не обращались в течение 7 дней, а use_temp_path настроен так, чтобы кэширование происходило напрямую в путь кэша. Похоже, что даже если установлена настройка вроде proxy_temp_path /tmp/nginx;, кэширование происходит в обход этого пути, что ускоряет работу.
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
Я установил proxy_cache_valid как общее правило, а остальные параметры перезаписываются в директиве location.
Таким образом, кэширование может работать, даже если настройки кэша не заданы явно.
Использование proxy_cache_bypass $http_cookie предотвращает ситуацию, когда после входа в систему (запроса с куки) пользователю может отобразиться закэшированная страница для неавторизованных пользователей.
Кроме того, кэширование редиректов, ошибок и ответов any — это мера защиты от атак. Это позволяет отдавать хотя бы закэшированные ответы, предотвращая аномальную нагрузку на Origin.
Для proxy_temp_path в идеале лучше использовать путь с постоянным хранением, но для моих целей в этом нет необходимости, поэтому я использую /tmp. На сайтах с приличным трафиком это может быть рискованно: при перезагрузке сервера весь кэш удалится, что приведет к резкому росту нагрузки на Origin и риску сбоя.
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
Здесь особо нечего сказать, кроме того, что настроен возврат специальной ошибки при прямом обращении по IP-адресу из A-записи.
Я включил multi_accept, так как мой сервер (реверс-прокси и кэш) работает на слабом инстансе с невысокими характеристиками.
Стало гораздо нагляднее после того, как настройки, ранее прописанные прямо в директиве http, были разделены по назначению и подключены через include.
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;
# IPアドレスへのアクセスを拒否
return 444;
}
### Damepo.jp
include ./sites-enabled/damepo.jp.conf;
### Soulminingrig My Blog
include ./sites-enabled/soulminingrig.com.conf;
# ~~~略~~~~
}
sites-enabled/soulminingrig.com.conf
В качестве примера приведу только один файл из sites-enabled. Это настройки данного сайта.
Кроме того, я не использую символические ссылки просто потому, что хочу иметь возможность быстро удалять ненужное.
Пожалуйста, учтите, что некоторые части все еще захардкожены, так как работа над ними продолжается.
К слову, недавно я перешел на www.soulminingrig.com, но до этого сайт работал на корневом домене без www, поэтому сейчас я все еще принимаю запросы на корневой домен без редиректа.
На период тестирования я настроил передачу заголовков, чтобы было понятно, ответил ли кэш сервера или кэш клиента.
И еще, я все думаю, нельзя ли как-то улучшить правила кэширования для изображений, шрифтов, CSS и т.д... Эх, можно ли с этим что-то сделать...
Я настроил upstream, чтобы иметь возможность быстро добавить бэкенды в будущем. Хотя сейчас он всего один, его наличие в самом верху позволяет легко понять, на какой бэкенд направлены настройки.
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
}
Бонус: nginxfmt.py
Это действительно отличная вещь.
Мне кажется, это самый качественный форматер из всех существующих.
Он доступен в AUR.
yay -S nginx-config-formatter
Использовать его просто: nginxfmt.py example.conf отформатирует файл, а команда
find . -name "*conf" | xargs -I{} nginxfmt.py {}
позволит выполнить массовое форматирование.
До недавнего времени я использовал nginxbeautifier, но он ломал синтаксис в правилах редиректа, поэтому я перешел на этот вариант.