Admin
December 15, 2025

Кеширующий прокси-сервер для geoipupdate

Потребность в актуализации GeoIP-баз, как правило, реализуется через установку на каждом сервере утилиты geoipupdate.

Но оказывается, при таком подходе легко столкнуться с суточными ограничениями на скачивание бесплатной версии GeoLite:

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

Обратим внимание, что geoipupdate поддерживает переопределение сервера обновлений:

# The server to use. Defaults to "https://updates.maxmind.com".
# Host https://updates.maxmind.com

и работу через прокси-сервер:

# The proxy host name or IP address. You may optionally specify a
# port number, e.g., 127.0.0.1:8888. If no port number is specified, 1080http,
# will be used.
# Proxy 127.0.0.1:8888

Развёртывание полноценного прокси-сервера (например Squid) затруднительно в Kubernetes, из-за метода CONNECT, которые стандартные Ingress не поддерживают.

Зато, имитация API сервиса обновлений MaxMind, выглядит подходящим решением.

Разберём алгоритм работы geoipupdate:

1. geoipupdate считает контрольную сумму локальной версии базы (допустим Country) и запрашивает у updates.maxmind.com информацию об актуальной версии:

> Host: updates.maxmind.com
> GET /geoip/updates/metadata?edition_id=GeoLite2-Country HTTP/2
> Authorization: Basic <лицензионный ключ>

2. updates.maxmind.com отвечает в следующем формате:

{
  "databases": [
    {
      "date": "2025-11-25",
      "edition_id": "GeoLite2-Country",
      "md5": "7f97170bfe5f384a14ec6cb49a538d33"
    }
  ]
}

3. Если хеш не совпадает — требуется обновление, тогда geoipupdate обращается к download.maxmind.com:

> Host: download.maxmind.com
> GET /geoip/databases/GeoLite2-Country/download?date=20251125&suffix=tar.gz HTTP/2
> Authorization: Basic <лицензионный ключ>

4. download.maxmind.com отвечает редиректом, на скачивание файла напрямую из S3

5. geoipupdate следует по ссылке и таким образом начинается загрузка файла

Всё что нам нужно, это спроксировать запросы от geoipupdate к сервисам updates и download.maxmind.com и закешировать их ответы.

Решение этой задачи уже реализовано в виде сервиса на Go:

однако оставалось предчувствие, что для реализации похожей функциональности достаточно базового Nginx.

Так получилась следующая конфигурация:

# Указание резолвера является требованием Nginx при использовании
# переменной ($follow_upstream_http_location) в proxy_pass
resolver ${NGINX_LOCAL_RESOLVERS};

# Логируем был ли использован кеш ($upstream_cache_status) 
log_format geoipupdate '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" "$upstream_cache_status"';

proxy_cache_path /var/cache/nginx/geoipupdate levels=1:2
    keys_zone=geoipupdate_cache:1m
    max_size=500M
    inactive=14d
    use_temp_path=off;

server {
    listen       8080;
    server_name  _;

    access_log  /var/log/nginx/access.log  geoipupdate;

    # По умолчанию, proxy_cache_key основывается на $proxy_host,
    # т.е. upstream-домене.
    # В результате кеш записывается, но фактически не используется.
    # Запросы продолжают доходить до proxy_pass и проксироваться в maxmind.com.
    # Поэтому, в составе ключа кеширования используется
    # переменная $host (домен самого прокси-сервера).
    # Это позволяет проверить кеш на попадание, до обращения к proxy_pass.
    proxy_cache_key $host$uri$is_args$args;

    # (1) Проксируем запрос на получение метаданных, в сервис updates.maxmind.com.
    #
    # Кешируем его ответ не непродолжительное время.
    location /geoip/updates/ {
        proxy_pass             https://updates.maxmind.com;
        proxy_ssl_server_name  on;

        proxy_cache                    geoipupdate_cache;
        proxy_cache_valid              200 1d;
        proxy_cache_use_stale          error timeout updating http_500 http_502 http_503 http_504;
        proxy_cache_background_update  on;
        proxy_cache_lock               on;
        proxy_cache_lock_timeout       30s;

        # Игнорируем заголовки запрещающие кеширование.
        proxy_ignore_headers Cache-Control Expires Set-Cookie;
        add_header X-Proxy-Cache $upstream_cache_status;
    }

    # (2) Проксируем запрос на скачивание определённой версии базы,
    # в сервис download.maxmind.com.
    #
    # Данный ресурс отвечает 302-редиректом,
    # содержащим динамическую ссылку на S3-хранилище.
    # Nginx перехватывает редирект и передаёт
    # его обработку в internal-location @follow_redirect.
    #
    # То есть, вместо отдачи редиректа как есть, Nginx переходит по нему
    # за нас, чтобы закешировался итоговый файл, а не ссылка на него. 
    location /geoip/databases/ {
        # Отвечаем из кеша при наличии в нём запрашиваемой версии БД.
        proxy_cache geoipupdate_cache;

        proxy_pass             https://download.maxmind.com;
        proxy_ssl_server_name  on;

        # Перехватываем редирект в ответе бекенда и передаём его в internal-location.
        proxy_intercept_errors on;
        error_page 302 = @follow_redirect;
    }

    # (3) Обработчик перехода по ссылке из заголовка Location ответа upstream.
    location @follow_redirect {
        internal;

        # При входе в location, сохраняем текущее
        # значение переменной $upstream_http_location из шага 2.
        # Поскольку после вызова proxy_pass оно будет перезаписано.
        set $follow_upstream_http_location $upstream_http_location;

        # Переходим в S3 по ссылке из редиректа.
        proxy_pass $follow_upstream_http_location;
        proxy_ssl_server_name on;

        # Исключаем проксирование заголовка Authorization в Amazon S3,
        # поскольку в обратном случае он отвечает ошибкой:
        #   > Missing x-amz-content-sha256
        proxy_set_header Authorization "";

        # Сохраняем ответ S3 в кеш.
        proxy_cache                    geoipupdate_cache;
        proxy_cache_valid              200 7d;
        proxy_cache_use_stale          error timeout updating http_500 http_502 http_503 http_504;
        proxy_cache_background_update  on;
        proxy_cache_lock               on;
        proxy_cache_lock_timeout       30s;

        proxy_ignore_headers Cache-Control Expires Set-Cookie;
        add_header X-Proxy-Cache $upstream_cache_status;
    }
}

Остаётся прописать URL-нашего прокси, в параметре Host конфигурации GeoIP.conf.