Гео-маршрутизация и локали в Nginx: рабочие приёмы
Nginx может отправлять запросы в региональный backend и выбирать локаль по cookie или Accept-Language, но ошибочный порядок правил приводит к циклическим редиректам, утечке внутренних адресов и проблемам с кешем.

Запрос пользователя из Германии можно отправить в европейский backend, а нового посетителя — на немецкую версию сайта. Но Nginx не узнает страну из IP сам по себе, а заголовок Accept-Language не равен выбранной пользователем локали. В этой практике для Nginx Open Source 1.25.x разберём три рабочих приёма: доверенный заголовок от CDN, GeoIP2-модуль и отдельную маршрутизацию локали через cookie и Accept-Language.
География отвечает на вопрос «куда отправить запрос», локаль — «на каком языке показать ответ». Если смешать эти решения в одну переменную, отлаживать их в проде станет намного сложнее.
Что выбрать до настройки
У Nginx нет встроенной базы стран по IP в стандартной сборке. Географию нужно получить от внешнего балансировщика или подключить модуль с базой MaxMind. Локаль, напротив, можно определить штатными средствами: по cookie, URI и заголовку Accept-Language.
| Подход | Что нужно | Плюсы | Цена и риск |
|---|---|---|---|
| Заголовок от CDN или балансировщика | Провайдер, который определяет страну | Не нужно хранить базу IP на каждом сервере | Нельзя принимать такой заголовок напрямую от клиента |
| GeoIP2 в Nginx | Динамический модуль и актуальная база MMDB | Решение работает на своём сервере | Обновление базы, сборка модуля, дополнительная задержка и операционные риски |
| Локаль через cookie и Accept-Language | Штатный модуль map | Просто внедрить, не нужна геобаза | Первый выбор может быть неточным, кеш усложняется |
GeoIP — технология определения приблизительного региона по IP-адресу. Она не показывает точный адрес человека и не должна использоваться как единственный источник для юридически значимых решений.
Приём 1. Брать страну из доверенного заголовка
Это обычно самый дешёвый вариант для сайта за CDN или внешним балансировщиком. Провайдер определяет страну до Nginx и передаёт, например, двухбуквенный код. Название заголовка зависит от провайдера: подставляйте его из официальной документации, а не из примера ниже.
В конфигурации Nginx можно сопоставить код страны с именем upstream:
map $http_x_country_code $region_backend {
default app_default;
DE app_eu;
FR app_eu;
NL app_eu;
US app_us;
}
upstream app_default {
server 127.0.0.1:8100;
}
upstream app_eu {
server 127.0.0.1:8200;
}
upstream app_us {
server 127.0.0.1:8300;
}
server {
listen 80;
server_name example.com;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Geo-Country $http_x_country_code;
proxy_pass http://$region_backend;
}
}
Здесь app_default, app_eu и app_us — группы upstream, а не адреса, которые вводит пользователь. После проверки конфигурации запрос с кодом DE должен попасть на порт 8200.
Критическая часть — граница доверия. Если Nginx доступен из интернета напрямую, клиент сможет отправить собственный X-Country-Code: US. Закройте прямой доступ к origin на уровне firewall или разрешите принимать заголовок только от диапазонов адресов CDN. Если провайдер передаёт исходный IP через отдельный заголовок, настройте real IP только по официальному списку его сетей.
Приём 2. Определять страну через GeoIP2
Для самостоятельного определения страны используют сторонний модуль ngx_http_geoip2_module и базу в формате MMDB. Модуль не входит в стандартную сборку Nginx: его нужно установить как динамический модуль совместимой версии и подключить через load_module. Путь к файлу и способ установки зависят от дистрибутива.
load_module modules/ngx_http_geoip2_module.so;
http {
geoip2 /etc/nginx/geoip/GeoLite2-Country.mmdb {
$geo_country country iso_code;
}
map $geo_country $region_backend {
default app_default;
DE app_eu;
FR app_eu;
NL app_eu;
US app_us;
}
upstream app_default {
server 127.0.0.1:8100;
}
upstream app_eu {
server 127.0.0.1:8200;
}
upstream app_us {
server 127.0.0.1:8300;
}
server {
listen 80;
server_name example.com;
location / {
proxy_set_header Host $host;
proxy_set_header X-Geo-Country $geo_country;
proxy_pass http://$region_backend;
}
}
}
Синтаксис блока geoip2 относится к стороннему модулю, а не к базовому Nginx. Версия модуля должна соответствовать версии и способу сборки Nginx. Проверяйте это командой nginx -t до перезагрузки, а путь к базе и доступ процесса Nginx к файлу — отдельно.
База страны устаревает. Её обновление нужно включить в регламент, но не перезапускать Nginx после каждого запроса или скачивания. Сама геолокация приблизительна: особенности мобильной сети, корпоративной инфраструктуры и IPv6 могут привести к определению не того региона.
Приём 3. Выбирать локаль по приоритетам
Для языка задайте понятный порядок: явная локаль в cookie важнее автоматического выбора, а Accept-Language используется только при первом визите. Не меняйте язык на каждом запросе по географии: пользователь может жить в одной стране, предпочитать другой язык и ожидать, что его выбор сохранится.
map $http_accept_language $locale_from_header {
default ru;
~*^en(?:-|,|;) en;
~*^de(?:-|,|;) de;
~*^fr(?:-|,|;) fr;
}
map $cookie_locale $request_locale {
"" $locale_from_header;
default $cookie_locale;
}
map $request_locale $locale_prefix {
default /ru;
ru /ru;
en /en;
de /de;
fr /fr;
}
server {
listen 80;
server_name example.com;
location = / {
return 302 $locale_prefix/;
}
location / {
proxy_set_header Host $host;
proxy_set_header X-Locale $request_locale;
proxy_pass http://app_default;
}
}
Этот пример предполагает, что приложение или отдельные location обрабатывают пути /ru/, /en/, /de/ и /fr/. Проверка простая: без cookie запрос с Accept-Language: de-DE,de;q=0.9 получает редирект на /de/, а после установки cookie locale=en — на /en/.
Разбор Accept-Language здесь намеренно ограничен первой подходящей группой. Если нужны веса q=, список запасных языков и соответствие en-US к en, лучше делать это в приложении или на уровне CDN. Nginx хорошо выполняет простое сопоставление, но не заменяет полноценный механизм согласования локали.
Где эти схемы ломаются
Редирект на каждом запросе
Если правило для / срабатывает после того, как пользователь уже оказался на /en/, получится цикл или лишний 302 на каждый заход. Ограничивайте редирект точным location = / и не переписывайте локализованные пути обратно на корень.
Кеш смешивает языки и регионы
Ответы, зависящие от cookie или Accept-Language, нельзя бездумно складывать в общий кеш. Для CDN добавьте в ключ кеша путь и выбранную локаль либо кешируйте отдельные URL — например, /ru/ и /en/. Вариация только по заголовку увеличивает число объектов и требует проверить, учитывает ли её конкретный кеш.
Прокси передаёт неправильный IP
Если GeoIP2 видит адрес внутреннего балансировщика, все пользователи окажутся в одном регионе. Если заголовок с IP подменяется клиентом, геомаршрутизация становится недостоверной. Сначала составьте цепочку прокси, укажите доверенные сети и проверьте в access log исходный адрес и страну на нескольких тестовых точках.
Региональный backend недоступен
Geo-маршрутизация не должна превращать отказ одного региона в массовую ошибку. У upstream должны быть проверки доступности и понятный fallback. Не отправляйте запрос в другой регион автоматически, если данные обязаны оставаться в конкретной юрисдикции: технический fallback может нарушить требования проекта.
Когда какой подход выбирать
- Есть CDN с проверенным country header — используйте его и удаляйте входящий клиентский заголовок перед установкой своего.
- Origin должен принимать решение самостоятельно — подключайте GeoIP2, фиксируйте версию модуля и регулярно обновляйте базу.
- Нужно только выбрать язык — начинайте с cookie и Accept-Language, а географию не добавляйте без бизнес-причины.
- Нужна SEO-предсказуемость — делайте отдельные URL локалей и постоянные ссылки между ними, а автоматический редирект оставляйте только для корня.
Что проверить после внедрения
Сначала соберите матрицу тестов: страна или заголовок, cookie, URI, ожидаемый backend, статус и итоговый URL. Прогоните её через staging с несколькими прокси и IPv6, затем включите в лог значения $geo_country, $request_locale, upstream-адрес и время ответа. Следите за долей 3xx, ошибками 502/504, промахами кеша и распределением запросов между регионами. Инвалидация кеша и хранение пользовательских данных между регионами — отдельные темы; здесь важны только выбор маршрута и локали.
Инфобез и 152-ФЗ: Аудит политик конфиденциальности, правила сбора cookie.
Спросить за 20 ₽Источники: Nginx documentation: ngx_http_map_module, ngx_http_geoip2_module — documentation and source code
Частые вопросы
Умеет ли стандартный Nginx определить страну по IP?
Можно ли доверять заголовку с кодом страны от клиента?
Что важнее: cookie или Accept-Language?
Почему после настройки пользователи получают бесконечные редиректы?
Что делать, если у меня другая версия Nginx?
Почему GeoIP показывает неправильную страну?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.