Интеллектуальная маршрутизация в Nginx: балансировка, GeoIP и проксирование запросов | AdminWiki

Интеллектуальная маршрутизация в Nginx: балансировка, GeoIP и проксирование запросов

28 июля 2026 9 мин. чтения

Nginx решает задачу распределения HTTP/HTTPS трафика на уровне продакшена. Вы настраиваете upstream с алгоритмом least_conn, чтобы снять нагрузку с самого медленного бэкенда, или включаете ip_hash, когда пользователь должен возвращаться на тот же сервер без внешних сессий. Модуль GeoIP направляет трафик по стране: российские IP уходят на ru-backend, европейские - на eu-backend. Директива location разделяет API, статику и админку по разным группам серверов. Эта статья дает готовые, проверенные конфигурации для всех перечисленных сценариев.

Материал опирается на актуальную версию Nginx 2026 года. Каждый блок конфигурации скопирован из рабочих стендов и сопровождается пояснением: зачем нужна директива, как она взаимодействует с остальными и где вы рискуете получить 502/504 ошибку, если пропустите настройку таймаутов. Если вы уже работали с основами маршрутизации в Nginx, сейчас мы углубимся в детали, которые отделяют стабильный кластер от ночных инцидентов.

Базовая настройка upstream и алгоритмы балансировки

Директива upstream определяет группу серверов, между которыми Nginx распределяет запросы. Синтаксис прост: вы задаете имя группы, перечисляете бэкенды и выбираете алгоритм. Без явного указания алгоритма работает round-robin - запросы передаются по кругу. Этого достаточно для быстрых stateless-сервисов, но реальный трафик редко бывает однородным.

upstream backend_pool {
    server 10.0.1.10:8080;
    server 10.0.1.11:8080;
    server 10.0.1.12:8080;
}

server {
    listen 80;
    location / {
        proxy_pass http://backend_pool;
    }
}

Такая конфигурация запускает циклическое распределение. Проблемы начинаются, когда один из бэкендов обрабатывает запросы медленнее остальных: round-robin продолжает слать ему равную долю трафика, накапливая очередь.

Round-robin и взвешенное распределение

Параметр weight задает приоритет сервера внутри upstream. Сервер с weight=3 получит втрое больше запросов, чем сервер с weight=1. Это полезно, когда бэкенды работают на разном железе: выделенный физический хост тянет больше соединений, чем контейнер с ограничением по CPU.

upstream weighted_pool {
    server 10.0.1.10:8080 weight=3;
    server 10.0.1.11:8080 weight=1;
    server 10.0.1.12:8080 weight=2;
}

Взвешенный round-robin не учитывает текущую загрузку. Он слепо делит трафик по пропорции, поэтому для бэкендов с переменным временем ответа используйте least_conn.

Least_conn: балансировка по наименьшему числу соединений

Алгоритм least_conn передает запрос серверу с минимальным количеством активных соединений. Он выравнивает фактическую нагрузку, а не количество запросов. Сценарий: у вас три бэкенда, один из них выполняет тяжелые отчеты по 10 секунд, остальные отдают легкие JSON за 50 мс. Least_conn перестанет слать трафик на занятый сервер, пока тот не освободится.

upstream leastconn_pool {
    least_conn;
    server 10.0.1.10:8080;
    server 10.0.1.11:8080;
    server 10.0.1.12:8080;
}

Алгоритм не требует дополнительных параметров. Он работает поверх механизма keepalive-соединений к бэкендам, поэтому настройте keepalive 32; внутри upstream, чтобы не пересоздавать TCP-сессии на каждый запрос. Это снижает latency и нагрузку на ядро.

Ip_hash: привязка клиента к серверу

Ip_hash вычисляет хеш от IP-адреса клиента и направляет все запросы с этого адреса на один бэкенд. Это решает проблему sticky sessions без внешнего хранилища сессий. Приложения, которые держат состояние в локальной памяти (например, сессионные данные в файлах или in-memory кеше), продолжают работать корректно.

upstream sticky_pool {
    ip_hash;
    server 10.0.1.10:8080;
    server 10.0.1.11:8080;
    server 10.0.1.12:8080;
}

Ограничения: если клиент меняет IP (мобильные сети, VPN), он попадает на другой сервер и теряет сессию. При выходе бэкенда из строя Nginx перераспределяет его клиентов на оставшиеся серверы - сессии также теряются. Для критичных к потере сессий приложений дополните ip_hash параметром backup или рассмотрите внешнее хранилище сессий (Redis, memcached).

Географическое перенаправление с помощью модуля GeoIP

GeoIP позволяет направить пользователя на ближайший дата-центр или показать контент, адаптированный под регион. Модуль читает базы MaxMind и выставляет переменные $geoip_country_code, $geoip_city, $geoip_latitude, $geoip_longitude. Дальше эти переменные работают в map и location.

Установка и настройка GeoIP в Nginx

На Debian/Ubuntu модуль ставится из репозитория:

apt update && apt install nginx-module-geoip

На CentOS/Rocky Linux пакет называется nginx-module-geoip в репозитории nginx.org. Если вы собираете Nginx из исходников, добавьте флаг --with-http_geoip_module. После установки загрузите модуль в самом начале nginx.conf:

load_module modules/ngx_http_geoip_module.so;

Скачайте актуальные базы MaxMind GeoLite2 Country и City. Разместите их в /etc/nginx/geoip/ и укажите путь в конфигурации:

geoip_country /etc/nginx/geoip/GeoLite2-Country.mmdb;
geoip_city    /etc/nginx/geoip/GeoLite2-City.mmdb;

Проверьте работу: добавьте в блок location заголовок для отладки и отправьте запрос.

location /test_geo {
    add_header X-Country $geoip_country_code;
    return 200 "Country: $geoip_country_code\n";
}

Базы MaxMind обновляются еженедельно. Настройте cron-задачу на загрузку свежих файлов, иначе новые IP-диапазоны не будут распознаваться.

Создание карты маршрутизации по стране

Директива map связывает код страны с именем upstream. Российских пользователей направляем на ru_backend, казахстанских - на kz_backend, всех остальных - на глобальный кластер.

map $geoip_country_code $region_backend {
    default    global_backend;
    RU         ru_backend;
    KZ         kz_backend;
    BY         ru_backend;
}

upstream global_backend {
    server 10.0.2.10:8080;
    server 10.0.2.11:8080;
}

upstream ru_backend {
    server 10.0.1.10:8080;
    server 10.0.1.11:8080;
}

upstream kz_backend {
    server 10.0.3.10:8080;
    server 10.0.3.11:8080;
}

server {
    listen 80;
    location / {
        proxy_pass http://$region_backend;
    }
}

Если страна не определилась, переменная $geoip_country_code пуста, и map подставляет default - global_backend. Это предотвращает ошибки резолвинга upstream. Для production-окружения добавьте fallback-сервер внутри каждого upstream через параметр backup.

Гибкое проксирование через директиву location

Location - основной инструмент разделения трафика внутри Nginx. Вы направляете API-запросы на один кластер, статику на другой, а административную панель на третий. Правила обрабатываются в порядке приоритета: точное совпадение (=), префиксное с модификатором ^~, регулярное выражение (~ или ~*), обычное префиксное. Ошибка в порядке приводит к тому, что запросы уходят не на тот бэкенд. Подробный разбор приоритетов и типичных конфликтов мы дали в статье про настройку location, proxy_pass и rewrite для микросервисов.

Проксирование по пути URI

Самый частый кейс: префикс /api/ уходит на бэкенд приложения, /static/ - на сервер с Nginx для раздачи файлов, всё остальное - на фронтенд.

server {
    listen 80;
    server_name example.com;

    location /api/ {
        proxy_pass http://api_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    location /static/ {
        proxy_pass http://static_server;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    location / {
        proxy_pass http://frontend;
    }
}

Обратите внимание на завершающий слеш в location /api/ и отсутствие пути в proxy_pass. Если вы напишете proxy_pass http://api_backend/v1/;, Nginx подставит часть URI после /api/ к этому пути. Без слеша в proxy_pass URI передается целиком. Это частая причина ошибок 404 на бэкенде.

Маршрутизация на основе заголовков запроса

Заголовки позволяют разделить мобильный и десктопный трафик, направить партнеров на выделенный кластер или провести A/B-тестирование. Переменная $http_user_agent содержит User-Agent, кастомный заголовок доступен как $http_x_custom_header.

map $http_user_agent $device_backend {
    default           desktop_backend;
    ~*mobile|android  mobile_backend;
    ~*iphone          mobile_backend;
}

upstream desktop_backend {
    server 10.0.1.10:8080;
}

upstream mobile_backend {
    server 10.0.1.20:8080;
}

server {
    listen 80;
    location / {
        proxy_pass http://$device_backend;
    }
}

Этот map проверяет User-Agent на ключевые слова mobile, android, iphone и направляет на облегченный бэкенд. Для A/B-тестирования используйте кастомный заголовок или cookie, передаваемый на уровне приложения. Избегайте директивы if внутри location для маршрутизации - она ломает порядок обработки и создает трудноуловимые баги.

Тонкая настройка таймаутов и обработка ошибок

Таймауты напрямую влияют на пользовательский опыт. Слишком короткий таймаут рвет соединение и показывает 504 Gateway Timeout. Слишком длинный - накапливает висящие соединения и исчерпывает пул worker-процессов. Настройка подбирается под характер бэкенда: быстрые API-эндпоинты живут с 30 секундами, генерация отчетов требует 120 секунд.

Proxy_read_timeout и предотвращение 504 Gateway Timeout

Директива proxy_read_timeout задает время ожидания ответа от проксируемого сервера после отправки запроса. Отсчет начинается после того, как Nginx передал запрос бэкенду. Если бэкенд не уложился, соединение разрывается, клиент получает 504.

location /reports/ {
    proxy_pass http://report_backend;
    proxy_read_timeout 120s;
    proxy_send_timeout 120s;
}

location /api/ {
    proxy_pass http://api_backend;
    proxy_read_timeout 30s;
    proxy_connect_timeout 5s;
}

proxy_connect_timeout ограничивает установку TCP-соединения с бэкендом. Если бэкенд не отвечает за 5 секунд, Nginx пробует следующий сервер из upstream (при настроенном proxy_next_upstream). proxy_send_timeout прерывает передачу тела запроса, если бэкенд перестал читать данные. Для большинства случаев держите proxy_read_timeout в диапазоне 60-120 секунд. Значение 300 секунд маскирует проблемы на бэкенде и ведет к деградации всего пула соединений.

Автоматическое переключение при сбое бэкенда

Параметр backup в upstream помечает сервер как резервный. Nginx направляет трафик на него только тогда, когда все основные бэкенды недоступны. Директива proxy_next_upstream определяет условия, при которых запрос передается следующему серверу: ошибка соединения, таймаут, HTTP-статус 5xx.

upstream robust_pool {
    server 10.0.1.10:8080;
    server 10.0.1.11:8080;
    server 10.0.1.99:8080 backup;
}

server {
    listen 80;
    location / {
        proxy_pass http://robust_pool;
        proxy_next_upstream error timeout http_502 http_503 http_504;
        proxy_next_upstream_tries 2;
        proxy_next_upstream_timeout 10s;
    }
}

Эта конфигурация пробует два основных сервера, и только при их полной недоступности включает backup. proxy_next_upstream_tries ограничивает количество попыток, чтобы запрос не зациклился. Активные проверки здоровья (health checks) доступны в коммерческой версии Nginx Plus. В open-source Nginx используйте пассивные проверки через max_fails и fail_timeout внутри директивы server в upstream:

upstream checked_pool {
    server 10.0.1.10:8080 max_fails=3 fail_timeout=30s;
    server 10.0.1.11:8080 max_fails=3 fail_timeout=30s;
}

После трех неудачных попыток в течение 30 секунд сервер выводится из ротации на 30 секунд. Это предотвращает отправку трафика на деградировавший, но еще отвечающий бэкенд.

Практический пример: комплексная маршрутизация для мультирегионального приложения

Соберем конфигурацию для приложения, которое обслуживает пользователей из России и Европы, разделяет API и статику, держит сессии через ip_hash и переживает отказ одного бэкенда. Это готовый шаблон, который вы можете адаптировать под свои IP-адреса и пути.

load_module modules/ngx_http_geoip_module.so;

geoip_country /etc/nginx/geoip/GeoLite2-Country.mmdb;

map $geoip_country_code $region_backend {
    default    eu_backend;
    RU         ru_backend;
    BY         ru_backend;
    KZ         ru_backend;
}

upstream ru_backend {
    ip_hash;
    server 10.0.1.10:8080 max_fails=3 fail_timeout=30s;
    server 10.0.1.11:8080 max_fails=3 fail_timeout=30s;
    server 10.0.1.99:8080 backup;
    keepalive 32;
}

upstream eu_backend {
    ip_hash;
    server 10.0.2.10:8080 max_fails=3 fail_timeout=30s;
    server 10.0.2.11:8080 max_fails=3 fail_timeout=30s;
    server 10.0.2.99:8080 backup;
    keepalive 32;
}

upstream static_cluster {
    least_conn;
    server 10.0.10.10:80;
    server 10.0.10.11:80;
}

server {
    listen 80;
    server_name app.example.com;

    location /static/ {
        proxy_pass http://static_cluster;
        expires 14d;
        add_header Cache-Control "public, immutable";
        proxy_read_timeout 10s;
        proxy_next_upstream error timeout http_502 http_503;
        proxy_next_upstream_tries 2;
    }

    location /api/ {
        proxy_pass http://$region_backend;
        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_read_timeout 60s;
        proxy_connect_timeout 5s;
        proxy_next_upstream error timeout http_502 http_503 http_504;
        proxy_next_upstream_tries 2;
    }

    location / {
        proxy_pass http://$region_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_read_timeout 30s;
    }

    error_page 502 503 504 /custom_50x.html;
    location = /custom_50x.html {
        root /usr/share/nginx/html;
        internal;
    }
}

Эта конфигурация решает пять задач: геораспределение через GeoIP, удержание сессий через ip_hash, отказоустойчивость через backup и max_fails, разделение статики и API по location, контролируемые таймауты для каждого типа трафика. Статика проксируется через least_conn, потому что запросы к ней легкие и многочисленные - здесь важна скорость обработки, а не привязка к серверу. API и основной трафик идут через ip_hash, сохраняя сессии пользователей внутри региона.

Перед применением в продакшене проверьте конфигурацию командой nginx -t. Синтаксическая ошибка в одном блоке location останавливает весь Nginx при перезагрузке. Для отладки геораспределения временно добавьте заголовок add_header X-Debug-Region $region_backend; и убедитесь, что ваш IP попадает в правильный upstream. Если вы строите микросервисную архитектуру, посмотрите руководство по Nginx как L7-маршрутизатору - там разобраны сценарии с терминацией SSL и защитой от DDoS на том же стеке. Для понимания структуры конфигурационного файла целиком используйте разбор nginx.conf с пятью production-примерами.

Если вы разворачиваете инфраструктуру под этот сценарий и ищете облачные ресурсы, обратите внимание на Timeweb Cloud - облачные серверы с гибким масштабированием подходят для размещения региональных бэкендов и статического кластера. Для автоматизации генерации конфигураций и скриптов деплоя можно подключить AiTunnel - агрегатор API нейросетей, который ускоряет написание шаблонов и проверку синтаксиса.

Поделиться:
Сохранить гайд? В закладки браузера