Анатомия конфигурации: контексты, наследование и модульность

Рейтинг: 64.6% · 12 голосов
Самый подробный курс по 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

Анатомия конфигурации: контексты, наследование и модульность

Сообщение 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 и аудит конфигурации
Боль: один файл на 800 строк, который никто не понимает

В прошлом уроке мы подняли первый рабочий nginx и впихнули весь конфиг в один файл. Пока строк двадцать - это удобно: все на ладони. Но прод так не живет. Полгода спустя в твоем nginx.conf уже восемь сайтов, три апстрима, rate limit, кэш, TLS, и редактировать его страшно: меняешь таймаут для одного location, а ломается совсем другой. Знакомо? Так выглядит nginx конфиг, который вырос без структуры.

Сегодня разберем анатомию по косточкам. Сначала поймем, как nginx вообще читает конфиг - что такое контексты и как директивы наследуются сверху вниз (тут прячется половина всех граблей новичка). Потом возьмем include и распилим монолит на части. И в конце посмотрим, как устроена реальная продакшн-структура nginx config на примере эталонного gateway - чтобы ты не изобретал велосипед, а копировал проверенный паттерн. Плюс два инструмента, без которых отладка превращается в гадание: nginx -t и nginx -T.

Изображение

Контексты nginx: вложенные коробки

Конфиг nginx - это не плоский список настроек, а дерево вложенных блоков. Каждый блок - это контекст. Самый внешний, который не обернут ни в какие фигурные скобки, называется main (главный, он же глобальный). Внутри него лежат другие контексты:

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

# это main-контекст - тут нет внешних скобок
user  nginx;
worker_processes  auto;
error_log  /var/log/nginx/error.log  warn;

events {
    # events-контекст: как воркеры обрабатывают соединения
    worker_connections  10240;
}

http {
    # http-контекст: все про HTTP
    include       mime.types;
    sendfile      on;

    server {
        # server-контекст: один виртуальный хост
        listen      80;
        server_name example.com;

        location / {
            # location-контекст: конкретный путь URL
            root  /var/www/html;
        }
    }
}
Иерархия для HTTP такая: main -> events -> http -> server -> location. Для балансировки L4 (голый TCP и UDP, без разбора HTTP) есть отдельная ветка main -> stream -> server - ее тронем в уроках про проксирование, сейчас просто знай, что она существует параллельно http.

Зачем вообще нужны контексты? Они задают область видимости директивы. listen и server_name имеют смысл только для конкретного виртуального хоста - значит, живут в server. worker_connections - это про модель воркеров, ей место в events, и в server она просто не разрешена, nginx ругнется. Контекст - это ответ на вопрос "к чему применяется эта настройка".

Важно понять модель раз и навсегда: http может содержать много server (много сайтов на одном nginx), server может содержать много location (разные пути одного сайта). Когда приходит запрос, nginx сначала выбирает подходящий server (по listen и server_name из заголовка Host), затем внутри него - подходящий location (по URI). Этот выбор - отдельная большая тема следующих уроков, сегодня нам важнее, как настройки просачиваются вниз по этому дереву.

Простые и блочные директивы

Директивы бывают двух видов. Простые - имя, параметры, точка с запятой в конце:

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

worker_processes  auto;
gzip              on;
proxy_read_timeout 10s;
Блочные (их еще зовут контекстами) - имя и фигурные скобки с набором директив внутри:

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

location /api/ {
    proxy_pass http://backend;
}
Две классические ошибки новичка, которые ловит парсер. Первая - забыть точку с запятой у простой директивы. Вторая - перепутать единицы. nginx понимает суффиксы размеров: k/m (килобайты, мегабайты) и времени: ms, s, m, h, d. Внимание на ловушку: m в контексте размера - это мегабайты, а m в контексте времени - это минуты. nginx разбирает по смыслу директивы:

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

client_max_body_size  16m;     # 16 мегабайт
keepalive_time        1h;      # 1 час
proxy_read_timeout    10s;     # 10 секунд
ssl_session_timeout   10m;     # 10 минут, а не мегабайт!
Если написать просто число без суффикса там, где ждут время - для большинства таймаутов это будут секунды, но лучше всегда писать единицу явно. Читается однозначно и тебе, и тому, кто откроет конфиг после тебя.

Наследование вниз по контекстам и его грабли

Вот это - сердце урока. Большинство директив, заданных в родительском контексте, наследуются дочерними. Поставил gzip on в http - он действует во всех server и location, если они явно не сказали иначе. Это удобно: общие настройки задаешь один раз наверху.

Но наследование работает по принципу "ближайший выигрывает". Если ту же директиву переопределить ниже - в server или location - локальное значение перебивает унаследованное. Разберем на живом примере, потому что именно тут люди ловят часы отладки:

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

http {
    gzip               on;          # включили сжатие глобально
    proxy_read_timeout 10s;         # таймаут чтения от бэкенда

    server {
        server_name app.example.com;
        # тут gzip и таймаут унаследованы как есть: on и 10s

        location / {
            proxy_pass http://backend;
            # gzip on, proxy_read_timeout 10s - все из http
        }

        location /report/ {
            proxy_pass http://backend;
            proxy_read_timeout 120s;   # переопределили ТОЛЬКО тут
            # тяжелый отчет генерится долго - даем ему 2 минуты
        }
    }
}
В location /report/ таймаут стал 120s, в остальных местах остался 10s. Локальное значение не "добавляется" к родительскому, а замещает его целиком в своей области.

Теперь главная грабля наследования. Возьмем add_header. Многие думают, что заголовки накапливаются - в http добавил один, в location другой, и оба прилетят клиенту. Это не так. add_header (а также expires, и ряд других директив, работающих по принципу "массив директив") наследуется по правилу все или ничего: если в дочернем контексте есть хотя бы один add_header, то ВСЕ add_header из родителя в этом контексте пропадают.

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

http {
    add_header X-Frame-Options SAMEORIGIN always;
    add_header X-Content-Type-Options nosniff always;

    server {
        location / {
            # тут оба заголовка наследуются - все ок
            root /var/www;
        }

        location /api/ {
            add_header X-Api-Version 2 always;
            # ВНИМАНИЕ: X-Frame-Options и X-Content-Type-Options
            # тут НЕ прилетят! Их затер локальный add_header.
            proxy_pass http://backend;
        }
    }
}
Человек добавил один технический заголовок в /api/ и молча потерял всю свою security-обвязку на этом пути. Дыра в безопасности, которую не видно глазами в конфиге. Лечится либо повтором всех заголовков в дочернем контексте, либо выносом общих заголовков в include-файл и подключением его в каждом нужном location, либо аккуратной архитектурой. Запомни правило: add_header не суммируется, ближайший контекст с ним обнуляет родительские. В эталонном gateway по этой причине security-заголовки заданы единым блоком на уровне http и продуманы так, чтобы их не приходилось локально перебивать.

Не все директивы наследуются. listen, server_name живут строго в своем server. Но для повседневной работы держи в голове две модели: "обычные директивы - ближайший перебивает" и "add_header-подобные - ближайший обнуляет родителя". Этого хватит, чтобы не попадать в ловушки.

nginx include: режем монолит на части

Монолит мы поняли. Теперь сделаем его поддерживаемым. Директива include - это буквально текстовая вставка: nginx на этапе чтения конфига берет содержимое указанного файла и подставляет его дословно на место include, как будто ты сам туда это вписал. Никакой магии, чистая склейка текста перед парсингом.

Из этого следует важное: include не создает новый контекст. Файл, который ты подключаешь, наследует тот контекст, где стоит сама директива include. Если include написан внутри http - содержимое файла попадает в http-контекст, и внутри файла должны лежать директивы, допустимые в http. Если внутри server - значит, в файле должны быть server-уровневые директивы. Поэтому подключаемые куски нельзя бездумно вставлять куда угодно: место include определяет, что в файле допустимо.

Самый знакомый пример include ты уже видел - mime.types:

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

http {
    include  /etc/nginx/mime.types;   # сотни строк type -> расширение
    default_type  application/octet-stream;
}
mime.types - это огромная таблица соответствий "MIME-тип -> расширения файлов". Без нее nginx отдавал бы все файлы как default_type (обычно application/octet-stream), и браузер не понимал бы, что .css - это стиль, а .js - скрипт. Держать эти сотни строк прямо в nginx.conf - безумие, поэтому их вынесли в отдельный файл и подключают одной строкой. Вот зачем нужен include в его базовой форме.

include понимает маски (wildcards). Звездочка раскрывается в список файлов по алфавиту:

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

http {
    # подключить ВСЕ файлы политик по порядку имен
    include  /etc/nginx/globals/*.conf;
    # подключить все включенные сайты
    include  /etc/nginx/sites-enabled/*.conf;
}
Порядок тут критичен. Файлы подтягиваются в алфавитном порядке, а в nginx порядок директив иногда важен (например, какой server станет default_server, или последовательность map). Поэтому в продакшн-структурах файлы часто нумеруют (10-proxy.conf, 20-cache.conf) или дают говорящие имена, чтобы алфавит давал нужную последовательность. Если файл по маске не найден при wildcard - nginx промолчит, а вот если ты указал include с конкретным именем несуществующего файла, конфиг не загрузится. Это тоже надо держать в голове, когда читаешь чужой конфиг и не находишь подключенный файл.

nginx config структура эталонного gateway: модульность по-взрослому

Теперь самое ценное - как организуют nginx config структуру на реальном проде. Возьмем эталонный OpenResty-gateway ngx-trace-gateway. Его nginx.conf - тонкий и почти пустой, вся работа размазана по include. Логика такая: главный файл - это оглавление, а не свалка настроек.

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

# nginx.conf - корневой файл, тонкий "диспетчер"
worker_processes  auto;
error_log         /var/log/nginx/error.log  warn;
pcre_jit          on;            # JIT-компиляция регэкспов, быстрее

include  events.conf;            # настройки воркеров отдельным файлом

http {
    include  mime.types;
    # карта для апгрейда соединений (WebSocket)
    map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      close;
    }
    charset  utf-8;
    recursive_error_pages  on;

    # 1) Глобальные политики - по порядку, каждая в своем файле
    include  globals/*.conf;
    # 2) Сами сайты
    include  sites-enabled/*.conf;
}
Что лежит в globals/? Каждый файл - это одна политика, одна зона ответственности, подключаемая в контекст http. В эталоне их полтора десятка, и они идут осмысленным порядком: hop_name, tcp, proxy, fastcgi, rate_limit, trace, gzip, logging, cache, ssl, security, error_pages, metrics. Не пугайся объема - суть в принципе разделения:
  • globals/proxy.conf - все общие proxy_* директивы: таймауты proxy_connect_timeout 5s, буферы, проброс заголовков Host и X-Forwarded-For, политика proxy_next_upstream. Задал раз - наследуется во всех сайтах.
  • globals/gzip.conf - gzip on, уровень сжатия, gzip_types. Одна политика сжатия на весь шлюз.
  • globals/ssl.conf - ssl_protocols TLSv1.2 TLSv1.3, ssl_session_cache, общие TLS-параметры. Не дублируем в каждом server.
  • globals/security.conf - server_tokens off и те самые security-заголовки единым блоком (помнишь грабли add_header? их тут решили на уровне http продуманно).
  • globals/rate_limit.conf - зоны limit_req_zone, описанные централизованно.
А в sites-enabled/ лежат уже конкретные виртуальные хосты - server-блоки конкретных доменов, которые просто пользуются унаследованными политиками. Сайт получается коротким: listen, server_name, пара location с proxy_pass - и все. Вся тяжелая обвязка приехала наследованием из globals.

Зачем так? Три причины. Первая - единая точка правды: меняешь таймаут проксирования в одном файле globals/proxy.conf, и он применяется ко всем сайтам сразу, нигде не забыл. Вторая - читаемость: открыл sites-enabled/shop.conf и видишь логику именно этого сайта, не утопая в общих настройках. Третья - безопасное добавление/выключение сайтов: чтобы погасить сайт, достаточно убрать симлинк из sites-enabled (паттерн sites-available + sites-enabled через симлинки - классика). Это и есть зрелая настройка nginx конфигурации: монолит распилен на политики и хосты.

Подсмотри в память по @realistic-content-seeding мысль о структуре - тут она тоже работает: лучше один проверенный модульный каркас, чем десяток одноразовых правок в монолите.

nginx -t и nginx -T: проверка и рентген конфига

Распилив конфиг на include, ты получаешь новую проблему: настройки разбросаны по двадцати файлам, и понять итоговую картину глазами тяжело. Тут спасают две команды.

nginx -t - проверка синтаксиса без применения. Парсит весь конфиг (со всеми include) и говорит, валиден ли он. Запускай ВСЕГДА перед reload - иначе рискуешь уронить работающий nginx кривым конфигом:

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

$ nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
А если ошибка - nginx покажет точный файл и строку, что особенно ценно при include:

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

$ nginx -t
nginx: [emerg] unknown directive "proxy_reed_timeout" in /etc/nginx/globals/proxy.conf:4
nginx: configuration file /etc/nginx/nginx.conf test failed
Видишь? Опечатался в proxy_read_timeout, и nginx ткнул носом - файл globals/proxy.conf, строка 4. Без include он бы не знал про этот файл вовсе.

nginx -T (большая T) - то же тестирование плюс дамп всего итогового конфига в стандартный вывод, со всеми раскрытыми include. Это рентген: ты видишь финальную склеенную конфигурацию ровно так, как ее видит сам nginx, с пометками, из какого файла приехал каждый кусок. Бесценная вещь для отладки модульной структуры:

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

$ nginx -T | less
# configuration file /etc/nginx/nginx.conf:
worker_processes auto;
...
# configuration file /etc/nginx/globals/proxy.conf:
proxy_connect_timeout 5s;
proxy_read_timeout 10s;
...
# configuration file /etc/nginx/sites-enabled/shop.conf:
server {
    listen 80;
...
Когда настройка "не применяется", а ты клянешься, что задал ее правильно - в 90% случаев nginx -T мгновенно показывает правду: либо include не подтянулся, либо значение перебито в другом файле, либо ты редактировал не тот файл из двух похожих. Конкретный приемчик:

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

nginx -T | grep -n proxy_read_timeout
- и сразу видишь все места, где директива задана, и какое значение победит в каком контексте.

После того как nginx -t прошел, применяем без даунтайма:

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

$ nginx -s reload         # или: systemctl reload nginx
reload поднимает новые воркеры с новым конфигом, старые доживают текущие запросы и тихо гаснут - клиент ничего не замечает. Но reload применяет конфиг без проверки, поэтому связка железная: сначала nginx -t, и только потом reload. Никогда наоборот.

Как читать чужой nginx конфиг

Тебе постоянно будут падать незнакомые конфиги - на новой работе, в чужом проекте, в гайде из интернета. Алгоритм чтения:
  • Начни с корневого nginx.conf и иди по include как по оглавлению - построй в голове дерево файлов.
  • Не доверяй глазам - сделай nginx -T и читай итоговую склейку. Только она показывает реальность с учетом порядка include и переопределений.
  • Для конкретной директивы делай grep по дампу: где задана, в каком контексте, не перебита ли ниже.
  • Помни про наследование: настройка в location могла прийти из http через три уровня, ее там физически не видно.
  • Отдельно проверь add_header - не затерты ли security-заголовки локальным блоком.
Заодно отметь общие удобства из эталона: charset utf-8 задает кодировку ответов (чтобы кириллица в простых страницах не превращалась в кракозябры), recursive_error_pages on разрешает error_page ссылаться на страницу, которая сама может вернуть ошибку (полезно для цепочек кастомных страниц ошибок). Мелочи, но в чужом конфиге их полезно узнавать в лицо.

Мини-лаба: распили монолит сам

Повтори руками, это 15 минут и навсегда закрепит модель. Работаем на тестовом nginx (можно в Docker-образе из прошлого урока).
  • Шаг 1. Возьми монолитный nginx.conf из прошлого урока (один server с парой location). Проверь:

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

    nginx -t
    . Должно быть ok.
  • Шаг 2. Создай каталог /etc/nginx/globals/. Вынеси из http в файл globals/gzip.conf три строки: gzip on; gzip_comp_level 5; gzip_min_length 1024;. В nginx.conf на их место впиши

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

    include globals/*.conf;
  • Шаг 3. Проверь

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

    nginx -t
    - должно остаться ok. Затем

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

    nginx -T | grep gzip
    - убедись, что директивы на месте и приехали из globals/gzip.conf.
  • Шаг 4. Теперь спровоцируй грабли наследования. В http добавь

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

    add_header X-Test base always;
    , а в один из location добавь

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

    add_header X-Local local always;
    . Сделай reload и проверь заголовки:

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

    curl -I http://localhost/тот-location
    . Убедись, что X-Test пропал на этом пути - вот оно, обнуление add_header. На другом location X-Test есть.
  • Шаг 5. Сломай конфиг нарочно: убери точку с запятой в globals/gzip.conf. Запусти

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

    nginx -t
    и прочитай, как точно nginx указал файл и строку. Верни точку с запятой.
  • Шаг 6. Финал:

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

    nginx -t && nginx -s reload
    . Связка через && гарантирует, что reload не выполнится при битом конфиге.
Контрольные вопросы
  • Ты задал proxy_read_timeout 10s в http и proxy_read_timeout 60s в одном location. Какое значение действует в этом location, а какое - в соседнем location, где директива не указана?
  • В http стоят два add_header с security-заголовками. В location /api/ ты добавил один свой add_header. Что прилетит клиенту на /api/ и почему это опасно?
  • Чем nginx -T отличается от nginx -t и в какой ситуации именно -T спасет тебя за минуту вместо часа отладки?
  • include подключает файл внутри http-контекста. Можно ли в этом файле написать директиву listen 80; и почему?
Итог

Конфиг nginx - это дерево контекстов main -> events -> http -> server -> location (плюс stream для L4). Директивы наследуются вниз, и ближайший контекст перебивает родительский - а add_header-подобные и вовсе обнуляют родительские при любом локальном вхождении, это главная ловушка. include - это дословная текстовая вставка, не создающая контекста; через wildcard-маски в алфавитном порядке он позволяет распилить монолит на политики (globals/*) и сайты (sites-enabled/*), как в эталонном gateway: тонкий nginx.conf-оглавление, тяжелая обвязка в глобальных политиках, короткие виртуальные хосты сверху. И два инструмента на каждый день: nginx -t проверяет синтаксис перед каждым reload, nginx -T дампит итоговую склейку для отладки include и наследования. В следующем уроке возьмемся за то, как nginx выбирает server и location для входящего запроса - механику маршрутизации.
👍3 ❤️5 🔥 😄 🤔1
Аватара пользователя
mstoneman
Сообщения: 1
Зарегистрирован: 31 май 2026, 06:46

Re: Анатомия конфигурации: контексты, наследование и модульность

Сообщение mstoneman »

Блин, вот про add_header это просто боль из жизни. Полгода назад потеряли X-Frame-Options на половине роутов и не могли понять почему security-сканер ругается. Оказалось ровно эта грабля - локальный add_header в одном location все затер. nginx -T бы сразу показал.
👍2 ❤️1 🔥1 😄 🤔
Аватара пользователя
nginx_enjoyer
Сообщения: 1
Зарегистрирован: 15 май 2026, 16:54

Re: Анатомия конфигурации: контексты, наследование и модульность

Сообщение nginx_enjoyer »

А подскажите, globals/*.conf подтягиваются по алфавиту - а если мне надо чтобы proxy.conf загрузился строго до sites, как гарантировать порядок между двумя разными include в http? Просто именами файлов рулить или есть способ почище?
👍 ❤️2 🔥 😄 🤔
Ответить
← Предыдущая глава
Архитектура процессов: master, worker и event loop
Следующая глава →
Виртуальные хосты: server и server_name

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

Поделиться темой: ✈ Telegram VK
Похожие запросы: Установка и первый конфиг nginxnginx в Dockernjs и динамические модули nginx

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

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

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