1. 1
  2. 2
  3. 3
  4. 4
  5. 5

# теория · шаг 2 из 5

Нативный ACME-модуль nginx

Коротко

С версии 1.29.0 nginx умеет выпускать и продлевать сертификаты сам: ни certbot, ни таймера, ни перезагрузки после продления.

  • Ставится пакетом nginx-module-acme и подключается директивой load_module в самом начале nginx.conf (вне всех блоков).
  • Обязателен resolver - без него конфиг не соберётся: acme_issuer: "resolver" is not configured.
  • А вот отсутствие ssl_certificate_cache и маска в начале имени (*.shop.local) проверку конфига проходят и ломаются уже в работе.

Если знаешь, что такое acme_issuer и зачем ему state_path - листай до «Теперь сам».

Сначала ответь сам

Модуль подключён, конфиг написан, nginx -t говорит successful. Сколько из четырёх типовых упущений ниже он поймал?

/etc/nginx/nginx.confhttp
acme_issuer le {
    uri https://acme-v02.api.letsencrypt.org/directory;
    contact mailto:admin@shop.local;
    # state_path не указан
}
server {
    server_name *.shop.local;          # маска в начале имени
    acme_certificate le;
    ssl_certificate     $acme_certificate;
    ssl_certificate_key $acme_certificate_key;
    # ssl_certificate_cache не указан
}

Ни одной. Проверка конфига ловит только отсутствие resolver; остальные всплывут в работе - отсутствие ssl_certificate_cache под нагрузкой, маска в начале имени при первом же выпуске. А пропущенный state_path на свежем модуле не сломает ничего: сработает умолчание, и состояние уедет в каталог, который ты не выбирал. Это стоит помнить: у модуля nginx -t ловит далеко не всё.

На что это похоже

Разница между «нанять курьера, который раз в квартал ездит за справкой» и «поставить в офисе аппарат, который печатает справку сам». Курьера надо помнить, оплачивать и проверять; аппарат работает молча, но у него свои требования - и главное, надо знать, в какой лоток он складывает напечатанное и кто имеет к нему доступ.

state_path - это и есть лоток: модуль заведёт его сам, но где именно и с какими правами - выяснять придётся тебе, если не указал путь.

Механизм: кто и когда ходит в центр сертификации

certbot таймер systemd файлы на диске deploy-hook reload модуль ACME nginx следит за сроком сам state_path на диске reload не нужен у модуля на одну движущуюся часть меньше - и на один каталог больше
Обе схемы делают одно и то же. Разница в том, сколько отдельных механизмов должно сработать.

Правило

shop.local - имя учебного стенда, а не домен для Let's Encrypt

Публичный центр на shop.local сертификат не выдаст: зона .local отведена под локальное обнаружение (mDNS), проверить такое имя из интернета нельзя. Для настоящего выпуска в server_name ставят свой публичный домен с записью A или AAAA на этот сервер и открытым снаружи 80-м портом. acme_certificate letsencrypt оставляют как есть: первый аргумент - имя объявленного выше acme_issuer, а не домен. Имена сертификата по умолчанию берутся из server_name; задать их явно можно после имени центра: acme_certificate letsencrypt example.com www.example.com;. Для практики без домена в uri указывают локальный центр ACME (например, Pebble): процедура та же, но сертификат доверенный только у тебя.

/etc/nginx/nginx.confhttp
resolver 127.0.0.53;                 # DNS-сервер: модуль сам ходит в сеть, и ему нужно имя разрешать в адрес

acme_issuer letsencrypt {
    uri                     https://acme-v02.api.letsencrypt.org/directory;
    contact                 mailto:admin@shop.local;
    state_path              /var/cache/nginx/acme-letsencrypt;
    accept_terms_of_service;
}

server {
    listen 443 ssl;
    server_name shop.local www.shop.local;

    acme_certificate letsencrypt;

    ssl_certificate       $acme_certificate;
    ssl_certificate_key   $acme_certificate_key;
    ssl_certificate_cache max=2;     # держать в памяти до 2 сертификатов (RSA и EC), не разбирая файл на каждое соединение

    root /var/www/shop;
}

server {
    listen 80;                        # нужен для проверки http-01
    server_name shop.local www.shop.local;
    return 301 https://$host$request_uri;
}

acme_issuer описывает центр, acme_certificate заказывает сертификат для имён из server_name, переменные подставляют выпущенное. ssl_certificate_cache сокращает повторную загрузку и разбор сертификата из переменной. Без кеша сертификат работает, но эта работа повторяется при новых соединениях.

Обрати внимание на блок на 80-м порту: путь /.well-known/acme-challenge/ модуль обрабатывает сам, отдельный location не нужен. Редирект на https ему тоже не мешает - челлендж модуль перехватывает раньше. Это ровно та ловушка, которая в предыдущем уроке ломала certbot, и здесь её нет.

Разбор: что ловит проверка конфига, а что нет

Прогон по стенду с настоящим модулем, каждый раз убираем одну строку:

что убрали nginx -t когда всплывёт
resolver [emerg] acme_issuer: "resolver" is not configured сразу
state_path successful ничего не ломается: с модуля 0.2.0 работает умолчание acme_<issuer> - замер на 0.4.1 дал /var/cache/nginx/acme_le
ssl_certificate_cache successful под нагрузкой: разбор сертификата на каждое соединение
accept_terms_of_service successful при первом обращении к центру
маска в начале имени, *.shop.local successful при выпуске: имена нужны явные

Про имена стоит знать точнее. Успешно проходит только маска в начале имени. На модуле 0.4.1 маска в конце или в середине (www.shop.*, ww*.shop.local) - уже ошибка invalid server name; регулярка даёт предупреждение unsupported regular expression и пропускается, а если других имён в блоке нет - ошибку no identifiers found in "server_name".

Первая строка - единственная, ради которой стоит помнить точный текст ошибки. Остальные четыре - причина, по которой конфиг модуля стоит один раз прогнать не только через nginx -t, но и через реальный старт со --staging-центром.

Разбор: почему state_path не мелочь

Директива задаёт каталог, где модуль хранит аккаунтный ключ и выпущенные сертификаты, - то, что должно пережить рестарт.

У неё есть умолчание, и его стоит знать точно: state_path acme_<issuer>;, то есть каталог рядом с префиксом nginx, названный по имени издателя. Появилось оно в версии модуля 0.2.0 (октябрь 2025) вместе с параметром off; до неё состояние действительно жило только в памяти. Поэтому «забыл state_path - потерял сертификаты» верно для 0.1.x и неверно для свежего модуля: на нём данные сохранятся, просто в каталоге, который ты не выбирал.

/etc/nginx/nginx.confhttp › acme_issuer
state_path /var/cache/nginx/acme-letsencrypt;   # явный путь - так правильно
# state_path off;                               # только в памяти, всё теряется при рестарте

Зачем тогда писать путь руками, если умолчание работает:

  • Права и владелец. Каталог содержит приватные ключи. Своим путём ты выбираешь, где он лежит и кому доступен, а не узнаёшь это из документации.
  • Относительный путь считается от префикса, а префикс у пакетного nginx, своей сборки и официального образа разный. Абсолютный путь снимает вопрос.
  • Бэкап и перенос. Явный каталог видно в конфиге - его не забудут при переносе на другую машину.

А вот state_path off; - прямая дорога к тому самому сюжету: несколько рестартов подряд (обновление ядра, отладка, автоматический перезапуск по сбою), и упираешься в недельный лимит Let's Encrypt, после чего сайт остаётся без сертификата до конца недели.

Что ломается без этого

Две системы на один домен. Если раньше домен вёл certbot, а теперь его ведёт модуль, старый таймер надо выключить. Иначе обе системы выпускают сертификаты на одни и те же имена, съедая лимит вдвое быстрее, а в конфиге остаются пути к файлам, которые уже никто не обновляет.

Первые секунды после старта. Пока сертификат не выпущен, сайт по https не отвечает. Для боевого сервера это несколько секунд, для тестов - повод не удивляться, что сразу после docker compose up соединение не устанавливается.

Зачем это в работе

Правило выбора получается простое. Модуль - когда сайтов немного, имена известны и хочется убрать из системы одну движущуюся часть: ни таймера, ни хуков, ни отдельного пользователя с правами на конфиг. certbot - когда нужны wildcard (это только dns-01), когда сертификаты используются не одним nginx (почтовый сервер, база) или когда инфраструктура уже построена вокруг него.

Что происходит, видно в обычном логе ошибок:

↳  выводtail -f /var/log/nginx/error.log | grep -i acme (вид строки; имена в ней - твоего домена)
[notice] 914#914: ACME: certificate for shop.local, www.shop.local issued,
expires Nov  5 16:02:10 2026 GMT

Теперь сам

Сервер с модулем перезагружали трижды за вечер во время отладки, и теперь сайт не поднимается: сертификата нет, в логе ошибка от центра сертификации. Что произошло и что делать?

Скорее всего, состояние не сохраняется между рестартами: либо стоит state_path off;, либо модуль старый (до 0.2.0, где умолчания ещё не было), либо каталог по умолчанию недоступен для записи - например, конфиг запускают с другим префиксом или под пользователем без прав на него. Тогда при каждом рестарте модуль выпускал сертификат заново, и три-четыре выпуска подряд выбрали недельный лимит на домен.

Делать: убедиться, что каталог состояния есть и пишется (state_path с явным абсолютным путём), а на время ожидания лимита переключить uri на --staging-адрес центра, чтобы отладку можно было продолжать. Сертификат оттуда браузер не примет, но сайт хотя бы поднимется.

Главное

Для нативного ACME проверь не только загрузку модуля, но и разрешение имён, доступность проверки и сохранение состояния. nginx -t не выпускает сертификат и не проверяет весь сетевой обмен. После перезапуска состояние должно сохраняться, иначе повторные заказы могут исчерпать лимиты центра.

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

Комментарий появится после проверки. Email не публикуется. Войти, чтобы не вводить имя каждый раз.