Капстоун: собираем production-gateway с нуля

Рейтинг: 60.1% · 14 голосов
Самый подробный курс по Nginx - от первого конфига до production API-gateway. Архитектура и event loop, контексты и location, виртуальные хосты, статика, reverse proxy и upstream-балансировка, кэширование, HTTPS и TLS (post-quantum), HTTP/2 и HTTP/3 QUIC, безопасность и rate limiting, realip, JSON-логи и сквозная трассировка, метрики, тюнинг и траблшутинг. Плюс OpenResty (Lua, фазы, JWT, динамика) и Angie (нативные Prometheus, ACME, dynamic upstream). На основе эталонного gateway и лучших практик. Для новичков и SRE/DevOps. Актуально на 2026 (nginx 1.30/1.31, Angie, ingress-nginx EOL -> Gateway API).
Ответить
Аватара пользователя
Igor_NgINX
Сообщения: 44
Зарегистрирован: 11 май 2026, 05:31

Капстоун: собираем production-gateway с нуля

Сообщение Igor_NgINX »

Оглавление курса (44)
  1. Что такое Nginx и почему он захватил веб
  2. Установка Nginx, Angie и OpenResty: пакеты, версии, Docker
  3. Первый конфиг с нуля: минимальный сервер за 5 минут
  4. Архитектура процессов: master, worker и event loop
  5. Анатомия конфигурации: контексты, наследование и модульность
  6. Виртуальные хосты: server и server_name
  7. Директива location: матчинг и приоритеты по шагам
  8. Раздача статики: root, alias, try_files, sendfile
  9. Переменные, map и блок if
  10. rewrite, return и канонизация URL
  11. Работа с заголовками: add_header и proxy_set_header
  12. Модель доверия прокси-заголовков: realip и защита от спуфинга
  13. Сжатие: gzip, brotli и zstd
  14. Reverse proxy: proxy_pass и проброс запроса
  15. upstream и балансировка нагрузки
  16. Таймауты, повторы и устойчивость прокси
  17. FastCGI и PHP: nginx + PHP-FPM (и uwsgi/scgi)
  18. WebSocket, gRPC и стриминг через Nginx
  19. Кэширование ответов: proxy_cache и микрокэш
  20. HTTPS и TLS: сертификаты, протоколы, шифры (2026)
  21. Let's Encrypt и ACME: certbot, acme.sh, нативный ACME Angie
  22. HTTP/2 и HTTP/3 (QUIC) в 2026
  23. Безопасность: заголовки, скрытие версии, ограничения
  24. Rate limiting и защита от перегрузки
  25. Контроль доступа: allow/deny, auth_basic, auth_request и mTLS
  26. Логирование: access_log, форматы и структурированный JSON
  27. Сквозная трассировка запросов и OpenTelemetry
  28. Метрики и мониторинг Nginx
  29. Производительность и тюнинг под нагрузку
  30. Траблшутинг: 502, 504, 403, 404 и debug-лог
  31. OpenResty: Nginx как платформа на LuaJIT
  32. Фазы обработки запроса и Lua-хуки
  33. Lua API: ngx.*, shared dict, cosocket и lua-resty-core
  34. Практика OpenResty: авторизация, JWT, кэш, Redis
  35. Angie: современный форк Nginx 2026
  36. Динамическая конфигурация и service discovery
  37. Nginx как API-gateway и Kubernetes Gateway API
  38. Динамические модули, njs и WAF
  39. Stream-модуль: проксирование TCP и UDP
  40. Деплой, перезагрузка и конфиг как код
  41. Сценарий: статика, SPA и кэширование за CDN
  42. Сценарий: микросервисный gateway по образцу ngx-trace-gateway
  43. Капстоун: собираем production-gateway с нуля (вы здесь)
  44. Чек-лист безопасности, CVE 2026 и аудит конфигурации
Зачем нам капстоун и какую боль он закрывает

Весь курс ты собирал шлюз по кусочкам - TLS тут, кэш там, rate limit в третьем уроке. Это нормально для учёбы, но в бою так нельзя. Реальный production-gateway - это не один файл на 800 строк, который страшно открыть. Это набор маленьких самодостаточных кусков, каждый из которых можно прочитать за минуту, протестировать через nginx -t и откатить независимо. Сегодня мы соберём такой шлюз руками с нуля - модульный, на nginx 1.30 (а где интересно - с оглядкой на Angie), с боевым TLS, проксированием, кэшем, лимитами, трассировкой и метриками.

Главная боль, которую решает этот урок: ты умеешь делать каждую вещь по отдельности, но не видел, как они уживаются в одном дереве конфигов так, чтобы порядок include не сломал rate limit, чтобы realip не подрался с логами, а кэш не закэшировал ответ с Set-Cookie. Это и есть разница между nginx best practices и набором скопированных сниппетов. Будем собирать боевой конфиг по принципам эталонного шлюза ngx-trace-gateway - модульная структура, JSON-логи, сквозная трассировка хопов.
Капстоун (capstone) - финальный проект, который связывает воедино все навыки курса. Цель не выучить новую директиву, а собрать рабочую систему и понять, как части влияют друг на друга.
Договоримся о фактах на 2026, чтобы ты не тащил старьё в свой nginx конфиг. Пример директив будет современным: HTTP/2 включается отдельной директивой http2 on, а не параметром listen. HTTP/3 - production-ready, не эксперимент. Соединение к бэкенду по умолчанию уже HTTP/1.1 с keepalive (с nginx 1.29.7, вошло в stable 1.30), но мы всё равно явно пропишем proxy_http_version 1.1 - чтобы конфиг работал и на старых сборках. И сразу мотаем на ус: ingress-nginx из kubernetes мёртв (read-only с марта 2026, CVE не патчатся) - в проде смотрим в сторону Gateway API, но это другой урок.

Изображение

Модульная структура: nginx настройка с нуля без монолита

Начнём со скелета. Идея простая: главный nginx.conf почти пустой, он только подключает события и http-слой. Весь http-слой - это include набора globals/* в строго заданном порядке плюс vhost-ы из sites-enabled. Порядок критичен: map-переменные и limit_req_zone должны быть объявлены ДО того, как их используют server-блоки.

Код: Выделить всё

# /etc/nginx/nginx.conf
user  nginx;
worker_processes  auto;
worker_rlimit_nofile 65535;
pcre_jit on;
error_log  /var/log/nginx/error.log warn;
pid        /run/nginx.pid;

include /etc/nginx/modules-enabled/*.conf;

events {
    worker_connections  10240;
    multi_accept on;
    use epoll;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    charset utf-8;

    # порядок имеет значение: сначала map/zone, потом server
    include /etc/nginx/globals/00-tcp.conf;
    include /etc/nginx/globals/10-trace.conf;
    include /etc/nginx/globals/20-proxy.conf;
    include /etc/nginx/globals/30-cache.conf;
    include /etc/nginx/globals/40-rate-limit.conf;
    include /etc/nginx/globals/50-gzip.conf;
    include /etc/nginx/globals/60-ssl.conf;
    include /etc/nginx/globals/70-security.conf;
    include /etc/nginx/globals/80-logging.conf;
    include /etc/nginx/globals/90-error-pages.conf;
    include /etc/nginx/globals/95-metrics.conf;

    include /etc/nginx/sites-enabled/*.conf;
}
Числовые префиксы в именах - не каприз. include с маской подключает файлы в алфавитном порядке, и префиксы гарантируют: tcp раньше trace, trace раньше proxy (потому что proxy пробрасывает трассирующие заголовки из map), zone-ы лимитов раньше server-ов. Это и есть nginx полный конфиг, разобранный на детали, а не сваленный в кучу.

Базовый транспорт - в один файл, чтобы не размазывать по vhost-ам:

Код: Выделить всё

# globals/00-tcp.conf
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65s;
keepalive_requests 1000;
types_hash_max_size 4096;
server_tokens off;
max_headers 100;
server_tokens off убирает версию nginx из заголовка Server и страниц ошибок - не отдаём сканерам подсказку. max_headers (директива из nginx 1.29.8, вошла в 1.30) ограничивает число заголовков в запросе - дешёвая защита от флуда заголовками, которой раньше просто не было.

TLS на 2026: post-quantum, HSTS и автопродление

TLS - то место, где чаще всего тащат конфиг пятилетней давности. Соберём современный globals/60-ssl.conf. Ключевые моменты: ssl_ciphers НЕ влияет на TLS 1.3 (для него отдельный механизм ssl_conf_command), а post-quantum обмен ключами включается через ssl_ecdh_curve с гибридной группой X25519MLKEM768.

Код: Выделить всё

# globals/60-ssl.conf
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;

# ciphers применяются ТОЛЬКО к TLS 1.2, на 1.3 не влияют
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;

# гибридный пост-квантовый обмен ключами (OpenSSL 3.5+)
ssl_ecdh_curve X25519MLKEM768:X25519:prime256v1;

ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
ssl_session_tickets off;

# OCSP stapling - legacy: Let's Encrypt убрал OCSP-URL в мае 2025
# ssl_stapling on;
# ssl_stapling_verify on;

resolver 1.1.1.1 8.8.8.8 valid=300s ipv6=off;
resolver_timeout 5s;
Разберём грабли. ssl_prefer_server_ciphers off - современная практика: на TLS 1.3 список серверных шифров фиксирован и сильный, навязывать порядок незачем, а клиенту (особенно мобильному) виднее, что у него быстрее на железе. ssl_ecdh_curve со строкой X25519MLKEM768 даёт квантово-устойчивый обмен ключами - Chrome и Firefox это уже умеют, и при OpenSSL 3.5+ хендшейк сам выберет гибрид. ssl_stapling я закомментировал намеренно: для сертификатов Let's Encrypt OCSP stapling фактически мёртв с мая 2025, когда LE выпилил OCSP-URL. Тащить его в новый конфиг - культ карго. Заметь: ssl_dhparam здесь нет вообще. Он нужен только для классических DHE-шифров; у нас везде ECDHE, так что генерить dhparam и копировать вслепую - лишнее.

Сам сертификат и HSTS вешаем на server-блок vhost-а, не в globals (ключи у каждого домена свои):

Код: Выделить всё

# sites-enabled/api.conf - HTTP -> HTTPS редирект
server {
    listen 80;
    listen [::]:80;
    server_name api.cyberlake.ru;

    # для ACME-челленджа certbot/acme.sh
    location /.well-known/acme-challenge/ { root /var/www/acme; }

    location / { return 301 https://$host$request_uri; }
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    listen 443 quic reuseport;   # HTTP/3
    listen [::]:443 quic reuseport;
    http2 on;                    # отдельная директива, НЕ listen ... http2
    http3 on;
    server_name api.cyberlake.ru;

    ssl_certificate     /etc/letsencrypt/live/api.cyberlake.ru/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.cyberlake.ru/privkey.pem;
    ssl_early_data on;           # 0-RTT для QUIC

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header Alt-Svc 'h3=":443"; ma=86400' always;

    include /etc/nginx/snippets/api-locations.conf;
}
Про автопродление. В ванильном nginx нативного core-ACME нет (есть реализация через njs-модуль, но это не core-директива) - на практике ставишь certbot с режимом webroot на каталог /var/www/acme или acme.sh, и cron/systemd-timer продлевает сертификат, дёргая nginx -s reload. А вот в Angie ACME встроен прямо в конфиг зрелыми директивами acme/acme_client с ECDSA из коробки - никакого certbot. Если твой шлюз на Angie, продление сертификатов перестаёт быть отдельной болью.

Reverse proxy, realip и кэш: ядро боевого шлюза

Теперь сердце. Сначала общий слой проксирования - таймауты, буферы, проброс заголовков и трассировка. Это nginx конфиг пример того, как НЕ дублировать proxy_set_header в каждом location.

Код: Выделить всё

# globals/20-proxy.conf
proxy_connect_timeout 5s;
proxy_send_timeout    10s;
proxy_read_timeout    10s;

proxy_http_version 1.1;          # явно, для совместимости со старыми сборками
proxy_set_header Connection "";  # пустой Connection включает keepalive к апстриму

proxy_buffering on;
proxy_buffer_size 16k;
proxy_buffers 16 16k;
proxy_busy_buffers_size 32k;

proxy_next_upstream error timeout http_502 http_503;
proxy_next_upstream_tries 3;

# проброс на бэкенд
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;

# WebSocket: апгрейд протокола
proxy_set_header Upgrade    $http_upgrade;
proxy_set_header Connection $connection_upgrade;

# сквозная трассировка хопов
proxy_set_header X-Request-ID $trace_request_id;
Тут спрятана тонкость WebSocket. Connection не может быть одновременно пустым (для keepalive) и равным upgrade (для веб-сокета). Решение - map: если в запросе есть Upgrade, проставляем upgrade, иначе close. Поэтому в globals/10-trace.conf (и рядом) живут map-ы, объявленные ДО proxy. Заодно собираем request_id для логов:

Код: Выделить всё

# globals/10-trace.conf
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

# если клиент/балансировщик прислал X-Request-ID - используем его,
# иначе генерим свой из встроенной $request_id
map $http_x_request_id $trace_request_id {
    default $http_x_request_id;
    ''      $request_id;
}
realip - частая и опасная ошибка. Шлюз почти всегда стоит за внешним балансировщиком или CDN. Без модуля realip переменная $remote_addr - это адрес ближайшего прокси, а не клиента. Тогда rate limit лимитирует балансировщик целиком (один IP на всех), geo врёт, логи бесполезны. Чиним так:

Код: Выделить всё

# globals/15-realip.conf  (подключить ПОСЛЕ trace, ДО rate-limit)
real_ip_header X-Forwarded-For;
real_ip_recursive on;

# доверяем ТОЛЬКО своим сетям балансировщиков/CDN
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
# set_real_ip_from <диапазоны CDN>;
Критично: set_real_ip_from должен перечислять только доверенные сети. Если доверишь 0.0.0.0/0, любой клиент подделает X-Forwarded-For и притворится кем угодно - обойдёт rate limit и подставит чужой IP в логи. real_ip_recursive on заставляет nginx идти по цепочке XFF справа налево, пропуская доверенные адреса, пока не найдёт первый недоверенный - это и есть настоящий клиент.

Кэш ответов. Объявляем зону глобально, политику - там же:

Код: Выделить всё

# globals/30-cache.conf
proxy_cache_path /var/cache/nginx/gate levels=1:2 keys_zone=appcache:128m
                 inactive=60m max_size=10g use_temp_path=off;

proxy_cache_key "$scheme$request_method$host$request_uri";
proxy_cache_valid 200 301 1h;
proxy_cache_valid 404 1m;

# не кэшируем приватное
proxy_no_cache       $http_authorization;
proxy_cache_bypass   $http_authorization;
proxy_ignore_headers Set-Cookie;
proxy_hide_header    Set-Cookie;

# защита от dog-pile и тёплый кэш
proxy_cache_lock on;
proxy_cache_lock_timeout 5s;
proxy_cache_background_update on;
proxy_cache_revalidate on;
proxy_cache_use_stale error timeout updating http_502 http_503 http_504;

add_header X-Cache-Status $upstream_cache_status always;
Три директивы здесь - это разница между кэшем-игрушкой и боевым. proxy_cache_lock on: когда сто запросов одновременно промахиваются мимо холодного ключа, к бэкенду уйдёт только один, остальные подождут (защита от cache stampede). proxy_cache_use_stale ... updating: пока один обновляет протухшую запись в фоне (background_update on), остальные получают чуть устаревший, но мгновенный ответ - бэкенд не штормит. proxy_cache_use_stale ... error timeout http_502: если апстрим упал, отдаём пользователю старый кэш вместо 502. Это буквально держит сайт живым во время инцидента. Заголовок X-Cache-Status показывает HIT/MISS/STALE/UPDATING - незаменимо при отладке.

Апстрим с балансировкой и failover:

Код: Выделить всё

# snippets/upstream-api.conf
upstream api_backend {
    least_conn;
    server 10.0.1.11:9000 max_fails=3 fail_timeout=30s;
    server 10.0.1.12:9000 max_fails=3 fail_timeout=30s;
    server 10.0.1.13:9000 backup;
    keepalive 64;
}
max_fails/fail_timeout - это пассивные проверки: после 3 ошибок за 30 секунд узел выводится из ротации на 30 секунд. Активных health checks в open-source nginx нет - он не пингует бэкенд сам, узнаёт о падении только на реальном запросе. Если нужны активные проверки и slow_start (плавный ввод восстановленного узла под нагрузку) бесплатно - это Angie из коробки. keepalive 64 держит пул живых соединений к бэкенду, что вместе с дефолтным HTTP/1.1 в 1.30 экономит TCP-хендшейки.

Сжатие, security-заголовки, лимиты: nginx best practices

Сжатие. gzip встроен, brotli - сторонний динамический модуль ngx_brotli (в ванильный nginx не входит), нативный Zstd есть только в Angie.

Код: Выделить всё

# globals/50-gzip.conf
gzip on;
gzip_comp_level 5;
gzip_min_length 1024;
gzip_vary on;
gzip_proxied any;
gzip_types text/plain text/css application/json application/javascript
           application/xml text/xml image/svg+xml;
# Если собран ngx_brotli:
# brotli on; brotli_comp_level 5; brotli_types text/css application/json ...;
# Если Angie: zstd on; zstd_comp_level 9; zstd_types ...;
Security-заголовки на 2026. Главное изменение: X-XSS-Protection устарел, OWASP советует его НЕ слать (или слать '0') и полагаться на Content-Security-Policy. В старых сниппетах он есть почти всегда - выкидываем.

Код: Выделить всё

# globals/70-security.conf
add_header X-Content-Type-Options    "nosniff" always;
add_header X-Frame-Options           "SAMEORIGIN" always;
add_header Referrer-Policy           "no-referrer" always;
add_header Permissions-Policy        "geolocation=(), microphone=(), camera=()" always;
# X-XSS-Protection НЕ добавляем - устарел, полагаемся на CSP
# add_header Content-Security-Policy "default-src 'self'" always;

# запрет точечных файлов и служебных каталогов
location ~ /\.(?!well-known) { deny all; }
location ~* \.(bak|sql|zip|git|svn|ht)$ { deny all; }
Параметр always обязателен: без него add_header не сработает на ответах 4xx/5xx, и страница ошибки уйдёт без заголовков безопасности. Запрет dotfiles с исключением для .well-known - чтобы не сломать ACME-челлендж.

Rate limiting. Зоны - глобально, применение - в location. Разные лимиты для логина, публичного API и внутренних клиентов:

Код: Выделить всё

# globals/40-rate-limit.conf
limit_req_zone $binary_remote_addr zone=app_login:10m   rate=5r/s;
limit_req_zone $binary_remote_addr zone=public_api:10m  rate=60r/m;
limit_req_status 429;

Код: Выделить всё

# в location логина - жёстко, без всплесков
location = /api/login {
    limit_req zone=app_login burst=5 nodelay;
    proxy_pass http://api_backend;
}
# публичное API - даём сглаженный всплеск
location /api/ {
    limit_req zone=public_api burst=120 delay=60;
    proxy_cache appcache;
    proxy_pass http://api_backend;
}
Разница burst nodelay против burst delay тонкая, но важная. nodelay пропускает всплеск немедленно, потом режет - хорошо для логина, где всплеск это легитимный ретрай. delay=60 пропускает первые 60 без задержки, остальные из burst придерживает - сглаживает API-трафик. $binary_remote_addr (а не $remote_addr) экономит память зоны - бинарное представление IP компактнее.

JSON-логи, трассировка и метрики: видимость боевого конфига

Шлюз, который ты не видишь - это шлюз, который ты не починишь в 3 часа ночи. Структурные JSON-логи с request_id и timing-полями:

Код: Выделить всё

# globals/80-logging.conf
map $status $log_level {
    ~^[23] info;
    ~^4   warn;
    default error;
}

log_format structured_log escape=json
  '{'
    '"time":"$time_iso8601","level":"$log_level",'
    '"request_id":"$trace_request_id",'
    '"remote_addr":"$remote_addr","xff":"$http_x_forwarded_for",'
    '"method":"$request_method","uri":"$uri","args":"$args",'
    '"status":$status,"bytes":$body_bytes_sent,'
    '"request_time":$request_time,'
    '"upstream_addr":"$upstream_addr",'
    '"upstream_status":"$upstream_status",'
    '"upstream_time":"$upstream_response_time",'
    '"cache":"$upstream_cache_status",'
    '"tls":"$ssl_protocol","cipher":"$ssl_cipher","sni":"$ssl_server_name"'
  '}';

access_log /var/log/nginx/access.log structured_log;
escape=json - обязателен, иначе кавычка в User-Agent сломает весь JSON, и парсер в ELK/Loki подавится. Поле request_time показывает полное время обработки, upstream_response_time - сколько занял именно бэкенд; их разница - это накладные расходы шлюза (буферизация, очередь). Когда request_time большой, а upstream_time маленький - проблема не в бэкенде.

Трассировку через цепочку хопов мы уже завели в 10-trace.conf и пробрасываем заголовком в 20-proxy.conf. Дальше - распределённая трассировка через официальный ngx_otel_module. Важно: он open-source (НЕ Plus-only) с nginx 1.25.3, ставится пакетом nginx-module-otel, с 1.27.5 идёт по умолчанию в -otel образах. Накладные ~10-15%, против ~50% у сторонних. Только OTLP/gRPC.

Код: Выделить всё

# подключить как динамический модуль и настроить экспортёр
otel_exporter {
    endpoint otel-collector:4317;   # OTLP/gRPC
}
otel_trace on;
otel_trace_context propagate;       # W3C trace context
otel_span_name $uri;
Метрики. Минимум - stub_status на закрытом порту, отдельный server только для localhost и внутренних сетей:

Код: Выделить всё

# globals/95-metrics.conf
server {
    listen 127.0.0.1:8080;
    location = /nginx_status {
        stub_status;
        allow 127.0.0.1;
        allow 10.0.0.0/8;
        deny all;
    }
}
stub_status даёт активные соединения, accepts/handled/requests, reading/writing/waiting - этого хватает Prometheus-экспортёру. Если шлюз на Angie - метрики Prometheus и детальный REST API /status (JSON по upstreams/zones/caches) есть нативно и бесплатно, плюс панель Console Light и динамическое управление апстримом без reload.

Красивые страницы ошибок - чтобы пользователь не видел голый 502:

Код: Выделить всё

# globals/90-error-pages.conf
error_page 404 /errors/404.html;
error_page 500 502 503 504 /errors/50x.html;
location ^~ /errors/ { internal; root /usr/share/nginx; }
Типичные грабли при сборке шлюза
  • Порядок include. limit_req_zone или map после server-блока, который их использует - nginx -t упадёт с "unknown variable" или зона не найдётся. Префиксы 00-95 решают это раз и навсегда.
  • add_header без always. HSTS и security-заголовки не уйдут на 4xx/5xx. Атакующий получит страницу ошибки без защит.
  • add_header в location перекрывает наследование. Если в location появился хоть один add_header, ВСЕ add_header из http/server в этом location перестают наследоваться. Дублируй или собирай в include.
  • realip с широким доверием. set_real_ip_from 0.0.0.0/0 - дыра: клиент подделает свой IP. Доверяй только сетям своих прокси.
  • Кэш с Set-Cookie. Без proxy_ignore_headers Set-Cookie и proxy_no_cache по Authorization можно закэшировать персональный ответ и отдать его другому пользователю - утечка сессии.
  • listen ... http2. Устаревший синтаксис. Только http2 on; отдельной директивой.
  • rewrite-правила и CVE-2026-42945. "NGINX Rift" - heap overflow в ngx_http_rewrite_module (версии до 1.30.0, эксплуатируется с мая 2026). Минимизируй rewrite, держи nginx обновлённым.
Мини-лаба: собери и проверь шлюз руками

Повтори по шагам на тестовой машине или в Docker (образ openresty/openresty или nginx:1.30).
  • Шаг 1. Создай дерево: /etc/nginx/nginx.conf, каталоги globals/, sites-enabled/, snippets/. Разложи файлы с префиксами 00-95 как выше.
  • Шаг 2. Подними фейковый бэкенд: docker run -d -p 9000:80 --name be1 kennethreitz/httpbin (или любой echo-сервер). Пропиши его в upstream api_backend как 127.0.0.1:9000.
  • Шаг 3. Проверь синтаксис и порядок: nginx -t. Любая ошибка "unknown variable" значит, что map подключён после использования - правь префиксы.
  • Шаг 4. Прогони статический анализатор gixy: pip install gixy && gixy /etc/nginx/nginx.conf. Он ловит ssrf, переопределение add_header, проблемы $uri в redirect. Чини findings.
  • Шаг 5. Запусти nginx, дёрни curl -k -I https://localhost/api/. Проверь в ответе HSTS, X-Cache-Status, отсутствие Server-версии и X-XSS-Protection.
  • Шаг 6. Проверь кэш: два раза curl на один URL, первый - X-Cache-Status: MISS, второй - HIT.
  • Шаг 7. Нагрузочный тест: wrk -t4 -c100 -d30s https://localhost/api/. Смотри latency p99 и requests/sec. Затем погаси бэкенд и убедись, что use_stale отдаёт старый кэш (X-Cache-Status: STALE), а не 502.
  • Шаг 8. Проверь rate limit: for i in $(seq 1 20); do curl -s -o /dev/null -w "%{http_code}\n" https://localhost/api/login; done - после порога должны пойти 429.
Контрольные вопросы
  • Почему limit_req_zone и map нельзя объявлять после server-блока, который их использует, и как этого избежать структурно?
  • Что произойдёт с HSTS-заголовком на ответе 502, если у add_header нет параметра always?
  • Чем отличается поведение клиента при proxy_cache_use_stale ... updating против отсутствия этой опции, когда запись в кэше протухла?
  • Почему set_real_ip_from 0.0.0.0/0 - это уязвимость, и как она ломает rate limit?
Итог

Ты собрал production-gateway с нуля: модульная структура с осмысленным порядком include, современный TLS с post-quantum обменом и HSTS, reverse proxy с балансировкой, keepalive и failover, корректный realip, кэш с lock, background_update и use_stale, сжатие, security-заголовки без устаревшего X-XSS-Protection, rate limit для логина и API, JSON-логи с request_id, трассировка через ngx_otel и метрики. И, главное, проверил его через nginx -t, gixy и wrk. Это и есть nginx боевой конфиг - не магия, а дисциплина: маленькие файлы, явные зависимости, проверка перед reload, видимость в логах и метриках. Дальше - только эксплуатация: мониторь 5xx-rate, latency p99, upstream_response_time, cache hit ratio и алерть на их рост.
👍3 ❤️1 🔥 😄 🤔1
Аватара пользователя
dholiday
Сообщения: 1
Зарегистрирован: 03 июн 2026, 18:22

Re: Капстоун: собираем production-gateway с нуля

Сообщение dholiday »

Собрал по шагам в докере, на шаге 7 погасил бэкенд и реально увидел STALE вместо 502 - вот это и есть тот момент когда понимаешь зачем use_stale. Спасибо за порядок префиксов в include, у меня раньше map постоянно падал с unknown variable.
👍 ❤️1 🔥 😄 🤔1
Аватара пользователя
Peace08
Сообщения: 1
Зарегистрирован: 20 май 2026, 01:51

Re: Капстоун: собираем production-gateway с нуля

Сообщение Peace08 »

Вопрос по realip: у нас перед nginx стоит Cloudflare, я в set_real_ip_from прописал только их диапазоны и real_ip_header CF-Connecting-IP вместо X-Forwarded-For - так правильнее же чем XFF? И gixy кстати ругнулся на мой add_header в location, починил дублированием как в граблях.
👍1 ❤️ 🔥 😄 🤔
Ответить
← Предыдущая глава
Сценарий: микросервисный gateway по образцу ngx-trace-gateway
Следующая глава →
Чек-лист безопасности, CVE 2026 и аудит конфигурации

Все главы курса «Nginx профессионально: от первого конфига до API-gateway на OpenResty и Angie»

Поделиться темой: ✈ Telegram VK

Вернуться в «Nginx профессионально: от первого конфига до API-gateway на OpenResty и Angie»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость