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

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

ACME и certbot

Коротко

Сертификат живёт 90 дней. Продлевать руками четыре раза в год - гарантия однажды забыть, поэтому выпуск автоматизируют по протоколу ACME.

  • Проверка http-01 идёт снаружи, по 80-му порту, на путь /.well-known/acme-challenge/.
  • Этот путь отдают явным location. return 301 на уровне server срабатывает раньше выбора location и уводит проверку в редирект - замер: 200 против 301.
  • Продление автоматическое, но новые файлы nginx не подхватит сам: сертификат живёт в памяти, нужен reload через --deploy-hook.
  • Эксперименты гоняют с --staging: лимит на боевом сервере расходуется быстро.

Если знаешь, почему челлендж отдают отдельным location, и помнишь про --deploy-hook - листай до «Теперь сам».

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

/etc/nginx/conf.d/shop.local.confhttp
server {
    listen 80;
    server_name shop.local;
    return 301 https://$host$request_uri;

    location /.well-known/acme-challenge/ { root /var/www/certbot; }
}

Location для проверки написан. Отдаст ли этот блок файл челленджа?

↳  выводcurl -o /dev/null -w '%{http_code} %{redirect_url}\n' http://shop.local/.well-known/acme-challenge/abc
301 https://shop.local/.well-known/acme-challenge/abc

Нет. return на уровне server выполняется на фазе перезаписиРазбирается в разделе 13, глава «return против rewrite»: Фазы: кто и когда решает - раннем шаге обработки, до того, как выбран location. Ниже, в «Правиле», редирект перенесён внутрь location /, и тогда тот же путь отдаётся честно - 200 вместо 301. (Два замера сняты с двух блоков стенда на разных портах, поэтому во второй команде другой порт; на боевом порт один.)

Коварство в том, что Let's Encrypt следует за редиректами, и такая схема работает - пока жив https-сертификат. Протухнет он - и продление сломается именно здесь: редирект уводит на https, где сертификат уже недействителен.

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

Заказное письмо с уведомлением. Почтальон приходит по адресу и должен вручить конверт лично, а не оставить записку. Если на двери висит табличка «мы переехали, все вопросы в новый офис», письмо поедет туда - и однажды выяснится, что в новом офисе не работает замок, который открывался как раз этим письмом.

Проверку домена делают ровно там, где её ждут, и не переадресуют.

Механизм: как проходит http-01

1 certbot просит сертификат для shop.local 2 центр отвечает: положи файл с этим содержимым по адресу /.well-known/acme-challenge/токен 3 certbot кладёт файл на диск 4 центр запрашивает адрес ИЗ ИНТЕРНЕТА, по http, порт 80 5 совпало - сертификат выписан закрытый 80-й порт, чужой DNS или редирект ломают шаг 4
Проверку делает не твой сервер, а центр сертификации - снаружи.

Правило

/etc/nginx/conf.d/shop.local.confhttp
server {
    listen 80;
    server_name shop.local www.shop.local;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}
$  команда
certbot certonly --webroot -w /var/www/certbot -d shop.local -d www.shop.local

certonly - только выпустить сертификат, не трогая конфиг; --webroot -w - каталог, из которого nginx отдаёт файлы челленджа; -d - имя (можно повторять). Редирект живёт внутри location /, а не на уровне server. Порядок между двумя блоками решается длиной префикса (четвёртый раздел): путь челленджа длиннее и побеждает.

Второй способ - плагин certbot --nginx: он сам правит конфиг на время проверки и возвращает как было. Быстрее для типового сайта, но правит твои файлы, и это иногда сюрприз.

Разбор: почему продление «прошло», а сертификат старый

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

$  команда
certbot renew --deploy-hook "nginx -s reload"

Пакетные установки часто кладут готовый хук в /etc/letsencrypt/renewal-hooks/deploy/. Проверить стоит: симптом «сертификат продлился, а браузер показывает старый и просроченный» - ровно это.

Сравнить файл и то, что реально отдаётся, - две команды:

$  команда
openssl x509 -in /etc/letsencrypt/live/shop.local/fullchain.pem -noout -enddate
openssl s_client -connect shop.local:443 -servername shop.local </dev/null 2>/dev/null | openssl x509 -noout -enddate

Даты разошлись - не было перезагрузки.

Разбор: что проверять до поломки

$  команда
certbot renew --dry-run

Прогоняет всю процедуру на тестовом сервере ACME, ничего не выпуская. Это единственный способ узнать, что продление сломано, до того, как оно сломается по-настоящему: закрыли 80-й порт файрволом, переписали конфиг, увели DNS на другой сервер - всё это всплывёт здесь, а не за неделю до истечения.

Таймер продления - это системный планировщик задач (управляется командой systemctl): его ставит пакет certbot, он ходит дважды в день и обновляет сертификат, когда до конца остаётся меньше 30 дней.

$  команда
systemctl list-timers | grep certbot

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

Лимиты. Let's Encrypt ограничивает число сертификатов на домен в неделю. Пока эксперименты идут с боевым сервером ACME, лимит расходуется за десяток попыток, и дальше приходится ждать несколько дней. Для отладки берут --staging: сертификат оттуда браузер не примет, но всю процедуру проверки прогонит честно.

Закрытый 80-й порт. Схема «наружу торчит только 443, http закрыт» выглядит разумно и ломает http-01 полностью: центр не сможет забрать файл. Либо открывают 80-й ровно под этот путь, либо переходят на проверку dns-01.

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

Продление ломается не в день установки, а через два-три месяца, когда все уже забыли, как настраивали. Поэтому после первого выпуска стоит сделать три вещи: прогнать --dry-run, убедиться, что хук на месте, и завести мониторинг на срок сертификата - тот, который смотрит на живое соединение, а не на файл.

Проверка снаружи занимает одну строку:

$  команда
echo | openssl s_client -connect shop.local:443 -servername shop.local 2>/dev/null | openssl x509 -noout -enddate

Теперь сам

Сайт переехал на новый сервер, DNS переключили, старый сервер пока жив. Через два месяца certbot на новом сервере не может продлить сертификат: проверка http-01 не проходит, хотя 80-й порт открыт и location на месте. Что случилось?

Скорее всего, DNS указывает не туда, куда ты думаешь: часть записей осталась на старый сервер, и центр сертификации забирает файл именно оттуда, а certbot кладёт его на новый. Проверяется тем же путём, что использует центр, - запросом из интернета: curl http://shop.local/.well-known/acme-challenge/тест с чужой машины, а не с самого сервера.

Главное

Проверка http-01 идёт снаружи, по 80-му порту, на /.well-known/acme-challenge/, и этот путь отдают явным location: return 301 на уровне server выполняется раньше выбора location и уводит проверку в редирект. Продление автоматическое, но nginx держит сертификат в памяти - нужен --deploy-hook "nginx -s reload", иначе браузер продолжит видеть старый. Настройку проверяют certbot renew --dry-run до поломки, а эксперименты гоняют с --staging, чтобы не выбрать недельный лимит.

Комментарии

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

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

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