Администрирование

Настройка nginx reverse proxy с автообновлением Let’s Encrypt сертификатов

Дмитрий Потоцкий 1 мин чтения

3:00 ночи. PagerDuty будит алертом SSL_EXPIRED. Ты открываешь терминал и видишь, что сертификат на продакшене истёк три дня назад. Причина проста: полгода назад ты настроил Certbot вручную, а потом забыл про него. Или ситуация хуже: ты деплоишь новый микросервис, браузеры блокируют запросы из-за mixed content, потому что в конфиге Nginx для нового домена нет HTTPS.

Ручное управление сертификатами в среде, где контейнеры живут днями или часами, — это технический долг. Он копится незаметно, а взрывается в момент пиковой нагрузки или при аудите безопасности.

Твоя задача: любой новый контейнер, поднятый через docker-compose, должен автоматически получать валидный Let’s Encrypt сертификат и становиться доступен по HTTPS без твоего участия. Не править конфиги, не перезагружать Nginx, не бегать с cron-скриптами.

Эта статья показывает, как настроить связку nginx-proxy и letsencrypt-companion. После настройки добавление нового сервиса сводится к двум переменным окружения в файле compose.

Почему классический Nginx не подходит для Docker

В классической установке Nginx конфигурационные файлы хранятся в /etc/nginx/sites-available [18]. При изменении настроек нужно обновлять файлы и применять изменения. В статичной инфраструктуре это норма. В Docker-среде, где сервисы могут подниматься и падать динамически, ручное редактирование конфигов становится узким горлом.

Образ jwilder/nginx-proxy:alpine решает эту проблему иначе. При запуске ему передают доступ к Docker Socket (/var/run/docker.sock) в режиме только для чтения через volume [3]. Прокси считывает события API Docker. Когда появляется новый контейнер с определёнными метками, он генерирует нужный конфигурационный файл и применяет его. Тебе не нужно заходить внутрь контейнера или трогать файлы на хосте.

Для корректной работы прокси и подключаемых сервисов используют внешнюю Docker-сеть с именем nginx-proxy [2]. Это разделяет инфраструктурный слой (проксирование, SSL) и бизнес-логику (твои приложения). Контейнеры общаются между собой по внутренним именам сервисов, внешние порты открыты только у самого прокси.

Как работает автоматический SSL внутри оркестрации

Чтобы получить сертификат для сервиса внутри сети, нужен агент, который живёт рядом с прокси. Образ jrcs/letsencrypt-nginx-proxy-companion выполняет эту роль [1]. Он следит за событиями в Docker API. Когда появляется контейнер с переменной LETSENCRYPT_HOST, компаньон инициирует процесс проверки владения доменом через HTTP-01 challenge [12].

Для работы компаньону нужны две вещи:

  1. Переменная окружения NGINX_PROXY_CONTAINER со значением nginx-proxy (имя контейнера прокси) [4].
  2. Доступ на запись в том certs, который смонтирован в /etc/nginx/certs внутри контейнера прокси [5]. Именно здесь хранятся полученные ключи и цепочки сертификатов [14].

После получения сертификата трафик с порта 80 автоматически перенаправляется на 443 [13].

Подключение сервиса: две переменные вместо конфига

Бизнес-контейнер (твой сайт, API или админка) не должен знать о существовании Nginx или SSL. Его задача — слушать свой порт и отвечать на запросы. Вся магия происходит на уровне сети и переменных окружения.

Чтобы сервис попал за прокси, выполни три условия:

  1. Подключи сервис к внешней сети. В docker-compose.yml твоего сервиса укажи сеть nginx-proxy [6]. Если этой сети нет, создай её командой docker network create nginx-proxy [2].
  2. Скрой прямые порты. Используй директиву expose вместо ports [11]. expose делает порт видимым для других контейнеров в той же сети, но не пробрасывает его на хост. Это предотвращает доступ к сервису в обход прокси и SSL.
  3. Добавь переменные окружения. Это единственный конфиг, который тебе нужно менять для каждого нового домена:
    • VIRTUAL_HOST=example.com — указывает, по какому домену прокси должен направлять запросы [7].
    • LETSENCRYPT_HOST=example.com — запускает процесс получения сертификата для этого домена [9].
    • LETSENCRYPT_EMAIL=admin@example.com — адрес для уведомлений от Let’s Encrypt (просрочка, ошибки) [10].

Если сервису нужно слушать нестандартный порт (не 80), добавь переменную VIRTUAL_PORT [8]. Например, если твоё приложение внутри контейнера слушает 3000, укажи VIRTUAL_PORT=3000.

Чек-лист «Zero-Touch SSL» на сегодня

Не откладывай настройку инфраструктуры. Потрать 15 минут сейчас, чтобы не чинить продакшен ночью.

  1. Создай изолированную сеть:
  2. Запусти прокси и компаньона. Сохрани этот блок как infra/docker-compose.yml:
    Запусти: docker compose up -d.
  3. Подключи свой сервис. В docker-compose.yml твоего приложения добавь:
  4. Проверь результат. Выполни команду:
    Ответ должен содержать HTTP/2 200 и заголовки с валидным сертификатом. Если видишь 502 Bad Gateway — проверь, что приложение внутри контейнера действительно слушает порт 8080 и доступно из сети nginx-proxy.

Правило: Если контейнера нет в сети nginx-proxy или у него нет переменных VIRTUAL_HOST и LETSENCRYPT_HOST, он не получит сертификат и не будет доступен извне. Никаких исключений.