Выберите продукт

Nginx GeoIP2 и MaxMind GeoLite2: установка, обновление БД и безопасный reload без простоя

Практическая настройка GeoIP2 для Nginx: проверяем наличие модуля, подключаем базы MaxMind GeoLite2 (.mmdb), выводим страну/город/ASN в переменные и access_log, настраиваем автообновление через systemd timer и безопасный reload без обрыва трафика.
Nginx GeoIP2 и MaxMind GeoLite2: установка, обновление БД и безопасный reload без простоя

GeoIP в Nginx обычно нужен не ради «красивых отчётов», а для прикладных задач: региональные правила, антифрод, маршрутизация на разные upstream, условные редиректы, метки в логах для аналитики. В 2025 году почти всегда речь идёт о модуле ngx_http_geoip2_module и базах MaxMind в формате .mmdb (GeoLite2/GeoIP2).

Ниже — рабочая схема: проверяем наличие GeoIP2-модуля, легально скачиваем GeoLite2 через MaxMind, подключаем базы в Nginx, заводим переменные (страна/город/ASN) и настраиваем регулярное обновление через systemd timer так, чтобы базы заменялись атомарно, а reload проходил без простоя.

Что важно знать про MaxMind и GeoLite2 до настройки

MaxMind распространяет бесплатные базы GeoLite2 по лицензии EULA: скачивание идёт по персональному License Key. «Просто скачать по прямой ссылке» нельзя, поэтому для автоматизации обновлений ключ нужно хранить отдельно (желательно файл с правами 600) и регулярно обновлять базы.

Чаще всего используют такие базы:

  • GeoLite2-Country — страна (обычно достаточно для большинства правил).

  • GeoLite2-City — город/регион/координаты (тяжелее, но даёт больше деталей).

  • GeoLite2-ASN — ASN и организация провайдера (полезно для антибот/антифрод и сегментации).

Нюанс: GeoIP — вероятностная оценка по IP. Для легитимных пользователей иногда будут «ошибочные» страны (мобильные сети, VPN, CGNAT). Поэтому жёсткие блокировки по гео лучше избегать: чаще практичнее мягкие ограничения (лимит, доп. проверка), чем вечный 403.

Проверяем, есть ли GeoIP2 в вашем Nginx

Сначала выясните, собран ли модуль GeoIP2 в текущий Nginx. Это зависит от способа установки (пакеты дистрибутива, официальный репозиторий, сборка из исходников).

nginx -V 2>&1 | tr ' ' '\n' | grep -E 'geoip|geoip2'

Если увидели упоминания geoip2, это хороший знак. Важно не перепутать со старым http_geoip_module (legacy, формат .dat): для .mmdb нужен именно GeoIP2.

Варианты установки

  • Динамический модуль из пакета: в ряде дистрибутивов GeoIP2 идёт отдельным пакетом; тогда в конфиге будет load_module.

  • Статическая сборка: если у вас кастомная сборка Nginx, GeoIP2 подключают при компиляции.

Дальше конфигурация одинаковая: если модуль динамический — добавите load_module, если статический — пропустите этот шаг.

Проверка наличия GeoIP2-модуля в сборке Nginx через nginx -V

Устанавливаем зависимости и GeoIP2-модуль (пример для Debian/Ubuntu)

Обычно нужна библиотека для чтения mmdb. Если GeoIP2 доступен как пакетный модуль для вашего Nginx — ставьте его отдельно, но названия пакетов зависят от репозитория.

apt update
apt install -y libmaxminddb0 libmaxminddb-dev

Проверить, есть ли модуль GeoIP2 в репозитории:

apt install -y nginx
apt-cache search geoip2 | grep -i nginx

Если подходящего пакета нет, практичный вариант — собрать Nginx с ngx_http_geoip2_module. Но сборку лучше оформлять отдельной инструкцией под вашу версию и ABI. В этой статье фокус — эксплуатация: подключение .mmdb и обновления без простоя.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Готовим каталоги и права под базы .mmdb

Храните базы в отдельном каталоге, который логично воспринимать как обновляемые данные, например /var/lib/geoip:

install -d -m 0755 /var/lib/geoip

Обновление лучше делать от root, а Nginx — только читать. Поэтому достаточно прав 0644 на файлы и 0755 на каталог. Главное — обновлять базы атомарно (через замену файла), чтобы Nginx не увидел «полфайла».

Скачиваем GeoLite2 через MaxMind легально (License Key)

Ключ удобнее хранить в отдельном файле с жёсткими правами и не светить его в истории shell и логах:

install -d -m 0700 /etc/maxmind
bash -lc 'umask 077; printf "%s" "YOUR_MAXMIND_LICENSE_KEY" > /etc/maxmind/license_key'

Далее можно использовать прямую загрузку архивов GeoLite2, распаковку и копирование .mmdb в нужный каталог. Это проще, чем тонко настраивать geoipupdate, и лучше контролируется в проде.

Скрипт обновления баз с атомарной заменой и безопасным reload

Скрипт ниже делает следующее: скачивает архивы, извлекает .mmdb во временный каталог, проверяет, что файл не пустой, заменяет целевой файл через mv (атомарно в пределах одного filesystem), затем выполняет nginx -t и только после успешной проверки — reload.

install -d -m 0755 /usr/local/sbin
cat > /usr/local/sbin/update-geolite2-mmdb <<'EOF'
#!/bin/sh
set -eu

LICENSE_FILE="/etc/maxmind/license_key"
DBDIR="/var/lib/geoip"
TMPBASE="/var/tmp/geolite2"

if [ ! -s "$LICENSE_FILE" ]; then
  echo "License key file is missing or empty: $LICENSE_FILE" 1>&2
  exit 1
fi

LICENSE_KEY="$(cat "$LICENSE_FILE")"

download_and_install() {
  EDITION="$1"
  TARGET="$2"

  mkdir -p "$TMPBASE"
  WORKDIR="$(mktemp -d "$TMPBASE.XXXXXX")"
  ARCHIVE="$WORKDIR/${EDITION}.tar.gz"

  cleanup() {
    rm -rf "$WORKDIR"
  }
  trap cleanup EXIT

  curl -fsSL -o "$ARCHIVE" "https://download.maxmind.com/app/geoip_download?edition_id=${EDITION}&license_key=${LICENSE_KEY}&suffix=tar.gz"

  tar -xzf "$ARCHIVE" -C "$WORKDIR"

  MMDB_PATH="$(find "$WORKDIR" -type f -name '*.mmdb' | head -n 1)"
  if [ -z "$MMDB_PATH" ]; then
    echo "No .mmdb found in archive for $EDITION" 1>&2
    exit 1
  fi

  if [ ! -s "$MMDB_PATH" ]; then
    echo "Downloaded mmdb is empty for $EDITION" 1>&2
    exit 1
  fi

  install -d -m 0755 "$DBDIR"
  install -m 0644 "$MMDB_PATH" "$DBDIR/${TARGET}.new"
  mv -f "$DBDIR/${TARGET}.new" "$DBDIR/${TARGET}"
}

download_and_install "GeoLite2-Country" "GeoLite2-Country.mmdb"
download_and_install "GeoLite2-City" "GeoLite2-City.mmdb"
download_and_install "GeoLite2-ASN" "GeoLite2-ASN.mmdb"

nginx -t
systemctl reload nginx
EOF
chmod 0755 /usr/local/sbin/update-geolite2-mmdb

Reload перечитывает конфиг и переинициализирует модуль. Новые воркеры начнут использовать обновлённые базы, а старые доработают текущие соединения на прежней версии — это ожидаемое поведение graceful reload без простоя.

Подключаем GeoIP2 в конфиг Nginx

Дальше подключаем базы и заводим переменные (страна/город/ASN), а затем добавляем их в access_log. Удобнее вынести это в отдельный файл в вашей схеме (conf.d, modules-enabled и т. п.).

Если модуль динамический: load_module

Если GeoIP2 установлен как динамический модуль, добавьте строку в начале основного конфига (main context, до events/http):

load_module modules/ngx_http_geoip2_module.so;

Если модуль статический, эта строка не нужна.

HTTP-конфигурация: базы и переменные

http {
  geoip2 /var/lib/geoip/GeoLite2-Country.mmdb {
    $geoip2_country_code country iso_code;
    $geoip2_country_name country names en;
  }

  geoip2 /var/lib/geoip/GeoLite2-City.mmdb {
    $geoip2_city_name city names en;
    $geoip2_region_name subdivisions 0 names en;
  }

  geoip2 /var/lib/geoip/GeoLite2-ASN.mmdb {
    $geoip2_asn_number autonomous_system_number;
    $geoip2_asn_org autonomous_system_organization;
  }

  log_format main_geoip '$remote_addr - $remote_user [$time_local] '
                       '"$request" $status $body_bytes_sent '
                       '"$http_referer" "$http_user_agent" '
                       'cc=$geoip2_country_code city="$geoip2_city_name" '
                       'asn=$geoip2_asn_number asn_org="$geoip2_asn_org"';

  access_log /var/log/nginx/access.log main_geoip;
}

Что стоит учитывать в проде:

  • Локали: в примере names en. Можно выбрать другой язык, но покрытие по полям/локалям различается.

  • subdivisions 0 — «первый уровень региона» (область/штат), часто бывает пустым.

  • Переменные могут быть пустыми для части IP (частные диапазоны, некоторые новые сети) — это нормально.

Если вы параллельно оптимизируете отдачу больших ответов и кэширование, полезно свериться с материалом про диапазонные запросы: как работает HTTP Range в Nginx/Apache и почему это важно для кэша.

Практические сценарии: ограничения и маршрутизация

GeoIP2 удобно использовать вместе с map, чтобы не плодить if в server/location. Ниже два типовых сценария.

Мягкое ограничение по странам (без жёсткой блокировки)

map $geoip2_country_code $geo_soft_limit {
  default 0;
  RU 0;
  KZ 0;
  BY 0;
  CN 1;
  BR 1;
}

server {
  location / {
    if ($geo_soft_limit) {
      add_header Retry-After "3600" always;
      return 429;
    }

    proxy_pass http://app_backend;
  }
}

Почему часто выбирают 429 вместо 403: вы не создаёте «вечный бан» на уровне кэшей/прокси и оставляете легитимным пользователям шанс повторить запрос позже. Для API это обычно сочетают с rate limiting.

Маршрутизация на разные upstream по гео

map $geoip2_country_code $geo_upstream {
  default app_default;
  US app_us;
  CA app_us;
  DE app_eu;
  FR app_eu;
}

upstream app_default { server 10.0.0.10:8080; }
upstream app_us { server 10.0.1.10:8080; }
upstream app_eu { server 10.0.2.10:8080; }

server {
  location / {
    proxy_pass http://$geo_upstream;
  }
}

Механизм простой, но учитывайте «скачки» геоданных и погрешности. Если решение критично для бизнеса, логируйте $geoip2_country_code и сверяйте логику на реальном трафике.

Виртуальный хостинг FastFox
Виртуальный хостинг для сайтов
Универсальное решение для создания и размещения сайтов любой сложности в Интернете от 95₽ / мес

Автообновление через systemd timer (вместо cron)

На серверах с systemd таймер обычно удобнее cron: логирование в journal, понятное управление через systemctl, и возможность «догонять» пропущенные запуски.

Юнит сервиса обновления

cat > /etc/systemd/system/geolite2-update.service <<'EOF'
[Unit]
Description=Update MaxMind GeoLite2 mmdb databases and reload nginx
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/update-geolite2-mmdb
EOF

Таймер раз в сутки

cat > /etc/systemd/system/geolite2-update.timer <<'EOF'
[Unit]
Description=Daily GeoLite2 update

[Timer] 
Persistent=true
RandomizedDelaySec=1h

[Install]
WantedBy=timers.target
EOF
systemctl daemon-reload
systemctl enable --now geolite2-update.timer
systemctl list-timers --all | grep -i geolite2

RandomizedDelaySec полезен, если у вас много серверов: они не будут одновременно «ломиться» за обновлениями. Persistent=true означает, что пропущенный запуск выполнится после включения сервера.

Настройка systemd timer для автоматического обновления GeoLite2 и reload Nginx

Диагностика: как понять, что GeoIP2 реально работает

Быстрый набор проверок:

  • Проверить конфиг: nginx -t.

  • Посмотреть error_log после старта/reload: нет ли ошибок чтения mmdb.

  • Сделать запрос и увидеть поля в access_log (например, cc=, asn=).

nginx -t
systemctl reload nginx
tail -n 50 /var/log/nginx/error.log
tail -n 5 /var/log/nginx/access.log

Если переменные пустые для вашего IP — это может быть нормой. Для теста удобно сделать запрос с VPS в другой стране или с мобильной сети.

Типовые ошибки и как их избежать

1) Nginx не стартует: «unknown directive geoip2»

GeoIP2-модуль не загружен. Проверьте:

  • правильность load_module и наличие файла .so;

  • совместимость модуля с вашей версией Nginx (ABI);

  • что это именно GeoIP2, а не legacy GeoIP.

2) Reload проходит, но данные «не обновились»

Частые причины: обновление пишет файл «поверх» (неатомарно), Nginx держит старый дескриптор, или вы обновили базу не по тому пути, который указан в директиве geoip2. Решение — атомарная замена через mv в том же каталоге и затем reload (как в скрипте).

3) Обновление ломается из-за ключа/сети

Скрипт должен быть «безопасным по умолчанию»: если скачивание не удалось, не трогайте старые базы и не делайте reload. При set -e и корректных проверках существующие .mmdb останутся на месте.

Когда имеет смысл выносить GeoIP2 на отдельный слой

Если у вас несколько фронтендов/балансировщиков и много правил, GeoIP2 часто выгоднее держать на edge-узле (первый Nginx/Ingress), а дальше прокидывать нормализованный результат в заголовках к приложению.

Важно: заголовки от клиента нужно игнорировать/перезаписывать, иначе получите spoofing (клиент подменит «свою страну»).

proxy_set_header X-Geo-Country $geoip2_country_code;
proxy_set_header X-Geo-ASN $geoip2_asn_number;

Если гео-логика завязана на домены и вы параллельно планируете переносы/склейки, полезно заранее продумать редиректы и HSTS: миграция домена, 301 и HSTS: как не потерять SEO и не сломать HTTPS.

Итог

Связка nginx + geoip2 + maxmind geolite2 стабильно работает, если выстроить три вещи: правильный модуль, понятную конфигурацию переменных и регулярные обновления баз. Самый «админский» вариант — обновлять через systemd timer и делать безопасный reload: атомарно заменили .mmdb, проверили nginx -t, сделали reload без разрыва соединений.

Дальше можно наращивать пользу: добавлять геометки в логи, вводить мягкие лимиты по странам/ASN, маршрутизировать часть трафика на отдельные upstream и измерять эффект по метрикам.

Для проектов на Fastfox это обычно ставят на фронтенд-Nginx на VDS, а небольшие сайты и панели можно держать на виртуальном хостинге, если GeoIP2 не нужен на уровне веб-сервера.

Поделиться статьей

Вам будет интересно

Debian/Ubuntu: nginx bind() to 0.0.0.0:80 failed (98: Address already in use) — как найти и устранить конфликт порта OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: nginx bind() to 0.0.0.0:80 failed (98: Address already in use) — как найти и устранить конфликт порта

Ошибка nginx bind() to 0.0.0.0:80 failed (98: Address already in use) в Debian/Ubuntu почти всегда означает конфликт за 80 или 443 ...
Debian/Ubuntu: как исправить Nginx no live upstreams while connecting to upstream OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: как исправить Nginx no live upstreams while connecting to upstream

Ошибка Nginx no live upstreams while connecting to upstream означает, что веб-сервер не видит доступных backend-процессов. Ниже — ...
ERR_TOO_MANY_REDIRECTS в Nginx и Apache за reverse proxy на Debian/Ubuntu: где искать цикл редиректов OpenAI Статья написана AI (GPT 5)

ERR_TOO_MANY_REDIRECTS в Nginx и Apache за reverse proxy на Debian/Ubuntu: где искать цикл редиректов

Если сайт уходит в ERR_TOO_MANY_REDIRECTS, причина обычно в конфликте редиректов между Nginx, Apache, приложением, CDN или reverse ...