Задача: использовать бесплатные TLS-сертификаты Let's Encrypt для сервисов на персональном сервере Debian/Ubuntu: вебсервер, почта, xmpp. Для операций над сертификатами используем certbot.
Серверный софт: Nginx, Prosody, Exim4.
Дальше все инструкции для моих доменов, это в точности та конфигурация, которая у меня на сервере. Для запросов в Let's Encrypt я буду использовать certbot.
Текст будет обновляться по мере появления новых задач.
Немного теории¶
Сервис Let's Encrypt выдаёт бесплатные TLS-сертификаты всем, кто может подтвердить в автоматическом режиме контроль над доменом или IP-адресом. Выдаваемые сертификаты действуют сравнительно недолго и предполагают регулярный их перевыпуск.
Для всех операций используется специальный протокол ACME (Automatic Certificate Management Environment), он был разработан изначально специально для сервиса Let's Encrypt, однако есть и другие вендоры, которые его поддерживают, однако самым популярным и авторитетным остаётся Let's Encrypt.
Упрощённо систему создания сертификата через ACME можно описать такими шагами:
- указываем список доменов или IP-адресов, которые нужно вписать в сертификат;
- отправляем запрос на сервис выписывания сертификатов;
- сервис выполняет автоматизированную проверку для каждого из указанных адресов, удостоверяясь, что запрашиваемая сторона имеет доступ к соответствующим DNS-записям;
- если проверка для всех записей проходит успешно, создаётся и подписывается новый сертификат.
Для автоматизированной проверки владения ресурсом есть несколько вариантов (они называются ACME Challenge):
- запрос к ресурсу через HTTP;
- запрос к DNS-записи о ресурсе;
- ALPN.
Их объединяет то, что сервер ACME-провайдера использует сторонний канал (не тот, через который вы обращаетесь) для подтверждения контроля над ресурсом.
В рамках этой статьи рассматривается только первый вариант — через HTTP-запрос к ресурсу. Его суть в том, что сервер Let's Encrypt при вашем запросе передаёт вам специальную случайную строку, берёт адрес ресурса и выполняет HTTP-запрос к специально сформированному адресу, ожидая там найти эту строку. Соответственно вы (а точнее, программа ACME-клиент) прописывает эту строку в нужном файле (который и будет показан по специально сформированному адресу). Если все данные совпали, считается, что вы контролируете указанный ресурс (домен или IP-адрес).
В 2025 году Nginx анонсировал встроенную поддержку протокола ACME, но в этой статье я его не рассматриваю, просто имейте в виду.
Шаг 1. Подготовка серверного окружения¶
Ставим certbot:
$ sudo apt install certbot
Certbot хранит свою конфигурацию в каталоге /etc/letsencrypt/ и лучше не лазить туда руками, а пользоваться только командой certbot.
Для корректной работы плагина webroot я решил сделать отдельный каталог, куда бы certbot писал необходимые файлы для подтверждения домена (источник).
$ sudo mkdir -p /var/www/certbot
Дальше создаём сниппет для nginx. Пишем в файл /etc/nginx/snippets/certbot-webroot-acme-challenge.conf следующее:
location ^~ /.well-known/acme-challenge/ {
default_type "text/plain";
root /var/www/certbot;
}
location = /.well-known/acme-challenge/ {
return 404;
}
Или можете скачать готовый так:
$ sudo wget 'https://raw.githubusercontent.com/sigsergv/blog-data/master/letsencrypt/certbot-webroot-acme-challenge.conf' \
-O /etc/nginx/snippets/certbot-webroot-acme-challenge.conf
Дальше во все необходимые конфиги (/etc/nginx/sites-enabled/) добавляем такую строчку в секцию server:
server {
listen 80;
....здесь остальные строки конфига....
include /etc/nginx/snippets/certbot-webroot-acme-challenge.conf;
}
Перезапускаем nginx:
$ sudo systemctl restart nginx
Теперь на всех ресурсах Nginx запросы к URL вида http://domain.tld//.well-known/acme-challenge/xxxxx будут перенаправляться в каталог /var/www/certbot, именно в нём certbot будет создавать нужные файлы для проверки владения ресурсом.
Шаг 2. Создание сертификата¶
Я сделал два сертификата. Один для nginx и доменов regolit.com, blog.regolit.com, www.regolit.com, misc.regolit.com, smtp.regolit.com, второй — специально для домена jabber.regolit.com. Делаются они такими командами:
$ sudo certbot certonly --webroot -d regolit.com,blog.regolit.com,www.regolit.com,misc.regolit.com,smtp.regolit.com --webroot-path /var/www/certbot
$ sudo certbot certonly --webroot -d jabber.regolit.com --webroot-path /var/www/certbot
После успешного завершения скрипта будет показана вся нужная информация: что было создано и куда положено.
Я НЕ пользуюсь фичами certbot по установке сертификатов, вместо этого я их сам раскладываю куда надо.
Шаг 3. Установка сертификатов¶
nginx¶
Для nginx в конфиг (у меня это /etc/nginx/sites-enabled/default) нужно прописать такое:
ssl_certificate_key /etc/letsencrypt/live/regolit.com-0001/privkey.pem;
ssl_certificate /etc/letsencrypt/live/regolit.com-0001/fullchain.pem;
prosody¶
К сожалению, для prosody нельзя указать напрямую путь к закрытому ключу (и сертификату) из хранилища certbot, поскольку программа запускается не от пользователя root, а все файлы в каталоге /etc/letsencrypt/live/ доступны для чтения только root. Поэтому ключ и сертификат сначала копируем в каталог prosody и меняем пермиссии с владельцем. Всё эти команды (они дальше уйдут в hook-файл, запускаемый автоматически после обновления сертификата):
cp /etc/letsencrypt/live/jabber.regolit.com/privkey.pem /etc/prosody/certs/jabber.regolit.key
cp /etc/letsencrypt/live/jabber.regolit.com/fullchain.pem /etc/prosody/certs/jabber.regolit.pem
chown prosody:prosody /etc/prosody/certs/jabber.regolit.{key,pem}
chmod 0600 /etc/prosody/certs/jabber.regolit.{key,pem}
Для Prosody конфиг /etc/prosody/conf.avail/regolit.com.cfg.lua меняется так:
VirtualHost "regolit.com"
ssl = {
key = "/etc/prosody/certs/jabber.regolit.key";
certificate = "/etc/prosody/certs/jabber.regolit.pem";
}
Шаг 4. Настройка обновления сертификата¶
certbot устанавливает cron-скрипт /etc/cron.d/certbot, который автоматически обновляет сертификат, когда в этом возникает необходимость. Вручную можно запустить обновление для всех локальных сертификатов так:
$ sudo certbot renew
Можно протестировать обновление, добавив аргумент --dry-run, в этом случае certbot не будет сохранять на диск новые сертификаты. Однако какие-то новые файлы в /etc/letsencrypt/ всё равно появятся.
После обновления сертификата на диске нужно как минимум перезапустить затронутые серверы. А для prosody нужно ещё и обновить копии сертификата и ключа.
Но моя цель — полностью автоматизировать всё: при появлении нового сертификата должны перезапуститься серверы, а для prosody нужно ещё и скопировать новые сертификаты куда надо и перезапустить.
Также в этом файле мы копируем сертификат и ключ в файлы exim4, он тоже как и prosody не умеет корректно работать с симлинками.
Для этого создаём файл /root/certbot-post-hook:
#!/bin/bash
cp /etc/letsencrypt/live/jabber.regolit.com/privkey.pem /etc/prosody/certs/jabber.regolit.key
cp /etc/letsencrypt/live/jabber.regolit.com/fullchain.pem /etc/prosody/certs/jabber.regolit.pem
chown prosody:prosody /etc/prosody/certs/jabber.regolit.{key,pem}
chmod 0600 /etc/prosody/certs/jabber.regolit.{key,pem}
cat /etc/letsencrypt/live/regolit.com-0001/privkey.pem > /etc/exim4/regolit.com.key
cat /etc/letsencrypt/live/regolit.com-0001/fullchain.pem > /etc/exim4/regolit.com.pem
/bin/systemctl reload prosody # since version 0.10 it will reload certificates
/bin/systemctl reload nginx
Его можно скачать и установить так:
$ wget -O /root/certbot-post-hook https://raw.githubusercontent.com/sigsergv/blog-data/master/letsencrypt/certbot-post-hook
$ chmod +x /root/certbot-post-hook
Дальше нужно отредактировать cron-скрипт /etc/cron.d/certbot:
# /etc/cron.d/certbot: crontab entries for the certbot package
#
# Upstream recommends attempting renewal twice a day
#
# Eventually, this will be an opportunity to validate certificates
# haven't been revoked, etc. Renewal will only occur if expiration
# is within 30 days.
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(43200))' && certbot -q renew --post-hook /root/certbot-post-hook
Рандомная скриптота¶
Список всех сертификатов в локальном хранилище certbot¶
$ sudo certbot certificates
Saving debug log to /var/log/letsencrypt/letsencrypt.log
-------------------------------------------------------------------------------
Found the following certs:
Certificate Name: jabber.regolit.com
Domains: jabber.regolit.com
Expiry Date: XXXX-XX-XX XX:XX:XX+00:00 (VALID: 89 days)
Certificate Path: /etc/letsencrypt/live/jabber.regolit.com/fullchain.pem
Private Key Path: /etc/letsencrypt/live/jabber.regolit.com/privkey.pem
Certificate Name: regolit.com
Domains: regolit.com blog.regolit.com jabber.regolit.com misc.regolit.com www.regolit.com
Expiry Date: XXXX-XX-XX XX:XX:XX+00:00 (VALID: 89 days)
Certificate Path: /etc/letsencrypt/live/regolit.com/fullchain.pem
Private Key Path: /etc/letsencrypt/live/regolit.com/privkey.pem
Certificate Name: regolit.com-0001
Domains: regolit.com blog.regolit.com misc.regolit.com www.regolit.com
Expiry Date: XXXX-XX-XX XX:XX:XX+00:00 (VALID: 89 days)
Certificate Path: /etc/letsencrypt/live/regolit.com-0001/fullchain.pem
Private Key Path: /etc/letsencrypt/live/regolit.com-0001/privkey.pem
-------------------------------------------------------------------------------
Редактирование сертификата¶
Иногда нужно изменить список доменов, например, удалить какой-нибудь из них. Для изменения конфига и перевыпуска сертификата используется такая команда:
$ sudo certbot certonly --cert-name regolit.com-0001 -d regolit.com,misc.regolit.com,smtp.regolit.com,www.regolit.com --allow-subset-of-names
В аргументе -d указываем через запятую новый список доменов. В аргументе --cert-name указываем полное имя сертификата (поле Certificate name из вывода команды certbot certificates).
Отзыв сертификата¶
$ sudo certbot revoke --cert-path /etc/letsencrypt/live/regolit.com/fullchain.pem
Saving debug log to /var/log/letsencrypt/letsencrypt.log
-------------------------------------------------------------------------------
Congratulations! You have successfully revoked the certificate that was located
at /etc/letsencrypt/live/regolit.com/fullchain.pem
-------------------------------------------------------------------------------
После отзыва сертификат остаётся в локальном хранилище. Удалить его оттуда можно так:
$ sudo certbot delete --cert-name regolit.com
Saving debug log to /var/log/letsencrypt/letsencrypt.log
-------------------------------------------------------------------------------
Deleted all files relating to certificate regolit.com.
-------------------------------------------------------------------------------