# теория · шаг 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. Сколько из
четырёх типовых упущений ниже он поймал?
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 - это и есть лоток: модуль заведёт его сам, но где именно и с какими
правами - выяснять придётся тебе, если не указал путь.
Механизм: кто и когда ходит в центр сертификации
Правило
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): процедура та же, но сертификат доверенный только у тебя.
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 и неверно для свежего модуля: на нём
данные сохранятся, просто в каталоге, который ты не выбирал.
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 (почтовый сервер, база) или когда инфраструктура уже построена вокруг него.
Что происходит, видно в обычном логе ошибок:
[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 не выпускает сертификат и не проверяет весь сетевой обмен. После перезапуска состояние должно сохраняться, иначе повторные заказы могут исчерпать лимиты центра.
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий