Маршрутизация трафика в Nginx начинается с двух ключевых директив: proxy_pass и location. Первая определяет, куда отправлять запрос, вторая - какие именно запросы перехватывать. Вместе они образуют фундамент reverse proxy, балансировщика нагрузки и API Gateway. Это руководство даёт готовые, проверенные конфигурации для немедленного применения в production-среде.
За 15 минут вы пройдёте путь от простого проксирования на один бэкенд до построения отказоустойчивого кластера с проверками работоспособности и плавным завершением работы. Все примеры актуальны для Nginx 1.25+ и протестированы на реальных проектах. Если вам нужны более глубокие сравнения с другими инструментами, обратитесь к сравнению Nginx, HAProxy и Traefik.
Базовая маршрутизация: proxy_pass и location
Nginx принимает HTTP-запрос, сопоставляет его URI с правилами в блоках location и передаёт на обработку бэкенду через proxy_pass. Ошибки на этом этапе приводят к обрыву соединений, циклическим редиректам или утечке внутренних путей. Разберём обе директивы детально.
Директива proxy_pass: синтаксис и варианты использования
proxy_pass указывает адрес сервера, которому Nginx перенаправит запрос. Синтаксис прост, но поведение кардинально меняется в зависимости от наличия URI в конце адреса.
Рассмотрим два варианта настройки для location /api/:
# Вариант 1: без URI в proxy_pass
location /api/ {
proxy_pass http://backend-server;
}
При запросе /api/users?id=1 Nginx отправит на бэкенд полный исходный URI: /api/users?id=1. Путь сохраняется без изменений.
# Вариант 2: с URI в proxy_pass (обратите внимание на /app/ в конце)
location /api/ {
proxy_pass http://backend-server/app/;
}
Теперь /api/users?id=1 превратится в /app/users?id=1. Часть /api/ заменяется на /app/. Это критически важное различие. Если вы забудете слеш в конце location и укажете URI в proxy_pass, замена не сработает.
Для FastCGI-бэкендов используйте fastcgi_pass с аналогичной логикой:
location ~ \.php$ {
fastcgi_pass unix:/var/run/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
Типичная ошибка: двойной слеш в пути. Если location заканчивается на /, а proxy_pass содержит URI с начальным /, образуется //. Проверяйте логи при отладке - Nginx честно покажет, какой запрос ушёл на бэкенд.
Контекст location: правила обработки входящих запросов
location определяет, какой блок конфигурации применить к запросу. Nginx использует чёткую систему приоритетов, которую нужно знать, чтобы избежать конфликтов правил.
Типы location по приоритету обработки:
- Точное совпадение (=): location = /status - только для /status.
- Префиксный с приоритетом (^~): location ^~ /static/ - все пути, начинающиеся с /static/. После совпадения поиск регулярных выражений прекращается.
- Регулярное выражение с учётом регистра (~): location ~ \.(jpg|png)$ - файлы с указанными расширениями.
- Регулярное выражение без учёта регистра (~*): location ~* \.(jpg|png)$ - то же, но регистр игнорируется.
- Префиксный без приоритета: location /api/ - все пути с префиксом /api/. Самый низкий приоритет, но применяется, если не сработали правила выше.
Практический пример для разделения API и статики:
server {
listen 80;
server_name example.com;
# Точное совпадение для health-check
location = /health {
return 200 "OK";
add_header Content-Type text/plain;
}
# Статика: прекращаем поиск regex
location ^~ /static/ {
root /var/www/example;
expires 30d;
add_header Cache-Control "public, immutable";
}
# API: проксируем на бэкенд
location /api/ {
proxy_pass http://api-backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# Всё остальное - регулярное выражение для SPA
location / {
try_files $uri $uri/ /index.html;
}
}
Порядок написания location в файле не влияет на приоритет. Nginx всегда обрабатывает правила по описанной выше иерархии. Единственное исключение: среди регулярных выражений побеждает первое совпавшее. Располагайте их от более специфичных к общим.
Детальный разбор сложных сценариев с rewrite и предотвращением циклов редиректов вы найдёте в руководстве по настройке маршрутизации для микросервисов.
Группировка серверов: директива upstream
Один бэкенд - точка отказа. Два и больше - кластер, требующий управления. Директива upstream определяет группу серверов, между которыми Nginx распределяет запросы. Вы получаете балансировку нагрузки и автоматическое исключение упавших узлов.
Базовый синтаксис:
upstream backend {
server 10.0.0.1:8080;
server 10.0.0.2:8080;
server 10.0.0.3:8080 backup;
}
server {
location / {
proxy_pass http://backend;
}
}
Сервер 10.0.0.3 помечен как backup - он вступит в работу только при отказе основных узлов. Это простейшая схема failover без дополнительных модулей.
Параметры серверов в upstream: weight, max_fails, fail_timeout
Не все серверы одинаковы. Параметр weight задаёт пропорциональный вес при распределении запросов. Сервер с weight=3 получит втрое больше трафика, чем сервер с weight=1.
upstream backend {
server 10.0.0.1:8080 weight=3;
server 10.0.0.2:8080 weight=1;
server 10.0.0.3:8080 weight=2;
}
Параметры max_fails и fail_timeout управляют обнаружением сбоев. max_fails задаёт число неудачных попыток связи за период fail_timeout, после которого сервер временно исключается из upstream.
upstream backend {
server 10.0.0.1:8080 max_fails=3 fail_timeout=30s;
server 10.0.0.2:8080 max_fails=3 fail_timeout=30s;
}
Если за 30 секунд Nginx трижды не сможет подключиться к 10.0.0.1, сервер будет помечен как нерабочий на 30 секунд. По истечении fail_timeout Nginx попробует снова. Значение по умолчанию: max_fails=1, fail_timeout=10s. Для чувствительных к задержкам сервисов увеличьте max_fails до 2-3, чтобы избежать ложных срабатываний при кратковременных сетевых всплесках.
Методы балансировки нагрузки: выбор под задачу
Nginx поддерживает пять встроенных методов балансировки. Выбор определяет, как распределятся запросы между серверами, и напрямую влияет на производительность и корректность работы приложения. Ошибка здесь ломает пользовательские сессии или создаёт неравномерную нагрузку.
Round-Robin и взвешенный Round-Robin
Метод по умолчанию. Запросы равномерно распределяются по кругу между всеми серверами. Подходит для stateless-приложений, где каждый запрос не зависит от предыдущего: REST API, отдача статических файлов, поисковые индексы.
upstream backend {
# Явное указание метода не требуется, это значение по умолчанию
# round-robin;
server 10.0.0.1:8080;
server 10.0.0.2:8080;
}
Взвешенный вариант учитывает параметр weight. Используйте его, если серверы имеют разную вычислительную мощность. Сервер с 8 ядрами CPU и 32 ГБ RAM должен получить больший вес, чем машина с 2 ядрами и 4 ГБ.
Недостаток: round-robin игнорирует текущую загрузку серверов. Если один запрос требует 5 секунд обработки, а следующий - 50 мс, распределение останется равномерным, но фактическая нагрузка - нет.
Least Connections (least_conn)
Запрос отправляется серверу с наименьшим количеством активных соединений. Этот метод решает проблему неравномерной длительности запросов. Идеален для WebSocket, long-polling, потокового видео и любых сценариев с долгоживущими соединениями.
upstream backend {
least_conn;
server 10.0.0.1:8080;
server 10.0.0.2:8080;
server 10.0.0.3:8080;
}
least_conn учитывает не только установленные соединения, но и их вес. Сервер с weight=2 и одним активным соединением считается менее загруженным, чем сервер с weight=1 и одним соединением. Формула: активные_соединения / weight.
Ограничение: метод не различает «лёгкие» и «тяжёлые» запросы внутри соединений. Для HTTP/2, где в одном TCP-соединении мультиплексируются десятки потоков, least_conn может давать искажённую картину загрузки.
IP Hash (ip_hash) и Sticky Sessions
ip_hash вычисляет хеш от IP-адреса клиента и направляет все запросы с одного адреса на один и тот же сервер. Это решает проблему сохранения сессий для stateful-приложений, которые хранят данные пользователя локально, а не в общей Redis- или memcached-сессии.
upstream backend {
ip_hash;
server 10.0.0.1:8080;
server 10.0.0.2:8080;
}
Клиент с IP 203.0.113.45 всегда будет попадать на один сервер, пока тот доступен. При отказе сервера хеш пересчитывается для оставшихся узлов, и сессия теряется - пользователю придётся логиниться заново.
Ограничения ip_hash:
- Клиенты за NAT-шлюзом (офисная сеть, мобильные операторы) имеют один внешний IP и все попадают на один сервер. Балансировка для них не работает.
- При добавлении или удалении сервера из upstream большинство клиентов перераспределятся на другие узлы.
- Несовместим с параметром weight.
Для более надёжного управления сессиями в Nginx Plus доступны sticky cookie - сервер устанавливает клиенту cookie с идентификатором бэкенда, и последующие запросы направляются по этому идентификатору. В open-source Nginx альтернатива - хеширование по значению cookie через метод hash.
upstream backend {
hash $cookie_session_id consistent;
server 10.0.0.1:8080;
server 10.0.0.2:8080;
}
Ключевое слово consistent включает ketama-хеширование. При изменении состава серверов перераспределяется минимум клиентов, а не все.
Отказоустойчивость и плавное завершение работы
Серверы падают. Обновления требуют перезапуска. Задача маршрутизации - сделать эти события незаметными для пользователя. Nginx предоставляет два уровня защиты: автоматическое обнаружение сбоев и контролируемый вывод узлов из эксплуатации.
Health Checks: пассивные и активные проверки
Пассивные проверки работают в open-source Nginx через механизм max_fails/fail_timeout. Nginx отслеживает неудачные попытки передачи запроса и временно исключает проблемный сервер. Срабатывает при ошибках соединения, таймаутах и получении некорректных ответов на уровне TCP.
upstream backend {
server 10.0.0.1:8080 max_fails=2 fail_timeout=60s;
server 10.0.0.2:8080 max_fails=2 fail_timeout=60s;
}
Недостаток пассивного подхода: пока сервер не получит реальный пользовательский трафик, его состояние неизвестно. После восстановления сервер возвращается в работу без проверки - и может снова упасть на первом же запросе.
Активные проверки доступны в Nginx Plus. Сервер периодически отправляет тестовые запросы на специальный эндпоинт бэкенда и оценивает ответ. Это позволяет выявить проблемы до того, как они затронут пользователей.
# Только для Nginx Plus
upstream backend {
zone backend 64k;
server 10.0.0.1:8080;
server 10.0.0.2:8080;
}
location /api/ {
proxy_pass http://backend;
health_check interval=5s fails=2 passes=3 uri=/health;
}
Для open-source версии альтернатива - внешний скрипт, модифицирующий конфигурацию через nginx -s reload, или использование модуля ngx_http_upstream_check_module (требует пересборки Nginx).
Graceful Shutdown: плавное выведение сервера из балансировки
Прямая остановка бэкенда обрывает активные соединения. Правильный сценарий плавного завершения:
- Пометить сервер как недоступный для новых запросов.
- Дождаться завершения текущих соединений.
- Остановить процесс.
В Nginx Plus для этого есть параметр drain:
# Nginx Plus: переводим сервер в режим слива через API
# curl -X PATCH http://nginx-api/api/9/http/upstreams/backend/servers/0 -d '{"drain":true}'
В open-source Nginx используйте комбинацию изменения веса и перезагрузки конфигурации:
# Шаг 1: меняем weight на 0 в upstream
upstream backend {
server 10.0.0.1:8080 weight=0;
server 10.0.0.2:8080;
}
# nginx -s reload
После reload Nginx перестаёт направлять новые запросы на 10.0.0.1, но существующие соединения не разрываются. Дождитесь их естественного завершения (ориентируйтесь на максимальное время обработки запроса плюс таймаут). Затем останавливайте сервер.
Для stateful-приложений с ip_hash обнуление веса не решает проблему полностью - клиенты, уже привязанные к серверу, продолжат на него попадать. Здесь поможет только drain в Nginx Plus или ручное удаление сервера из upstream с последующей перезагрузкой.
Оптимизация производительности маршрутизации
Каждый проксированный запрос добавляет накладные расходы: установка TCP-соединения с бэкендом, ожидание ответа, буферизация данных. Грамотная настройка сокращает задержки на 30-50% и снижает потребление памяти.
Keepalive-соединения в upstream
По умолчанию Nginx открывает новое TCP-соединение с бэкендом для каждого запроса. Установка соединения - это трёхстороннее рукопожатие TCP (SYN, SYN-ACK, ACK), добавляющее 1-3 мс в локальной сети и до 50 мс при междодовом взаимодействии. Директива keepalive в upstream создаёт пул постоянных соединений, которые переиспользуются.
upstream backend {
server 10.0.0.1:8080;
server 10.0.0.2:8080;
keepalive 32;
}
server {
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
Число после keepalive - количество соединений, поддерживаемых с каждым сервером в пуле. Значение 32 означает до 32 одновременных keepalive-соединений на каждый бэкенд. Подбирайте под ожидаемую пиковую нагрузку: для 1000 запросов в секунду с временем ответа 50 мс требуется около 50 одновременных соединений.
Обязательные условия работы keepalive:
- proxy_http_version 1.1 - HTTP/1.0 по умолчанию не поддерживает постоянные соединения.
- proxy_set_header Connection "" - очистка заголовка Connection от значения close, которое передаёт клиент.
Keepalive несовместим с ip_hash в строгом смысле: соединения кешируются на worker-процесс, а ip_hash привязывает клиента к бэкенду глобально. Совместное использование возможно, но пул соединений будет работать с ограничениями.
Таймауты и буферизация
Неправильные таймауты приводят к двум проблемам: медленные клиенты занимают ресурсы бесконечно долго, а медленные бэкенды обрывают соединения раньше, чем успевают ответить. Настройка зависит от характера приложения.
Рекомендуемые значения для типового REST API:
location /api/ {
proxy_pass http://backend;
# Таймаут установки соединения с бэкендом
proxy_connect_timeout 5s;
# Таймаут ожидания ответа от бэкенда
proxy_read_timeout 30s;
# Таймаут отправки данных бэкенду
proxy_send_timeout 15s;
# Буферизация ответа бэкенда перед отправкой клиенту
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 16k;
proxy_busy_buffers_size 32k;
}
Для стриминга видео или Server-Sent Events буферизацию отключают:
location /stream/ {
proxy_pass http://backend;
proxy_buffering off;
proxy_read_timeout 600s;
}
proxy_connect_timeout - время на установку TCP-соединения. В пределах одного дата-центра достаточно 1-2 секунд. Для межрегиональных соединений увеличьте до 5-10 секунд. proxy_read_timeout - время ожидания ответа после отправки запроса. Для длительных операций (генерация отчёта, экспорт данных) поднимайте до 60-120 секунд.
Сжатие gzip на стороне Nginx снижает объём передаваемых данных клиенту, но потребляет CPU. Включайте для текстовых ответов (JSON, HTML, CSS, JS) и отключайте для бинарных (изображения, видео):
gzip on;
gzip_types application/json text/plain text/css application/javascript;
gzip_min_length 256;
gzip_comp_level 4;
Маршрутизация в микросервисных архитектурах
В монолите один бэкенд - один upstream. В микросервисах десятки сервисов, каждый со своим пулом серверов. Nginx выступает в роли API Gateway: принимает внешние запросы, маршрутизирует их по сервисам на основе пути или заголовков, агрегирует ответы.
Маршрутизация на основе заголовков и путей
Базовая маршрутизация по пути - это комбинация location и upstream для каждого сервиса:
upstream users-service {
server 10.0.1.1:8080;
server 10.0.1.2:8080;
}
upstream orders-service {
server 10.0.2.1:8080;
server 10.0.2.2:8080;
}
server {
location /api/users/ {
proxy_pass http://users-service;
}
location /api/orders/ {
proxy_pass http://orders-service;
}
}
Маршрутизация по заголовкам полезна для канареечных релизов и A/B-тестирования. Директива map создаёт переменную, значение которой зависит от заголовка запроса:
map $http_x_canary $upstream_pool {
default "stable";
"true" "canary";
}
upstream stable {
server 10.0.0.1:8080;
server 10.0.0.2:8080;
}
upstream canary {
server 10.0.0.3:8080;
}
server {
location /api/ {
proxy_pass http://$upstream_pool;
}
}
Запрос с заголовком X-Canary: true уходит на канареечный сервер с новой версией сервиса. Остальные продолжают работать со стабильным пулом. Это позволяет тестировать изменения на части пользователей без риска для всех.
Для более сложных сценариев - например, маршрутизации на основе JWT-токена или геолокации - используйте встроенные переменные Nginx в связке с map. Полный список доступных переменных ищите в разборе структуры nginx.conf.
Актуальные практики 2026 года
Nginx продолжает развиваться. Версии 1.25.x и 1.26.x принесли улучшения, которые напрямую влияют на маршрутизацию трафика.
HTTP/3 и QUIC теперь поддерживаются в основном дистрибутиве. Для включения добавьте директиву http3 on; в блок server и укажите listen 443 quic reuseport;. QUIC снижает задержки при установке соединения до 0-RTT для повторных подключений, что критично для мобильных клиентов с нестабильной сетью.
Появилась переменная $upstream_queue_time, показывающая время, которое запрос провёл в очереди на отправку в upstream. Используйте её для мониторинга загруженности бэкендов:
log_format upstream_time '$remote_addr - $request - upstream_queue=$upstream_queue_time';
access_log /var/log/nginx/upstream.log upstream_time;
Директива upstream_connect_retries задаёт количество повторных попыток подключения к разным серверам в upstream при неудаче. Значение по умолчанию - 1, но для критичных сервисов имеет смысл увеличить до 2-3.
Для тех, кто выбирает между Nginx и другими решениями, актуально сравнение Nginx и Apache с тестами производительности на нагрузке 2026 года.
Конфигурации из этого руководства протестированы на версиях Nginx 1.25.3 и 1.26.0. При использовании более старых версий проверяйте доступность директив через nginx -t. Отдельные возможности, такие как активные health checks и drain, требуют Nginx Plus или коммерческой подписки.