Установка Certbot для Nginx на Ubuntu 24.04
Установите Certbot через apt или snap для получения SSL сертификатов Let's Encrypt. Узнайте, как избежать конфликтов версий и решить проблему таймаута обновления на порту 80.
Установка Certbot: apt или snap
В Ubuntu 24.04 пакет sudo apt install certbot python3-certbot-nginx предоставляет рабочую версию Certbot, которая выпускает настоящие, доверенные публичные сертификаты Let's Encrypt. Официальная документация Certbot рекомендует использовать snap; разница невелика: snap отслеживает апстрим-релизы, а пакет из репозитория следует версии, выпущенной вместе с LTS, и получает только обновления безопасности.
Выберите один вариант. Наличие двух копий Certbot означает два таймера обновления, нацеленных на одно и то же дерево /etc/letsencrypt, и та копия, о которой вы забудете, обязательно доставит неприятный сюрприз.
Путь через apt:
sudo apt update
sudo apt install certbot python3-certbot-nginxЭта команда устанавливает /usr/bin/certbot, плагин для nginx, пару certbot.service + certbot.timer и запись /etc/cron.d/certbot, которая не выполняет никаких действий в системе с systemd.
Путь через snap:
sudo apt remove certbot python3-certbot-nginx
sudo snap install core && sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbotSnap поставляется с собственным таймером snap.certbot.renew.timer. Удалите пакет apt перед установкой snap.
После установки обе версии работают одинаково, за исключением времени автоматического обновления (описано в разделе «Обновление» ниже). Certbot 2.x по умолчанию использует ключи ECDSA (P-256); передавайте флаг --key-type rsa только для клиентов, которые не поддерживают ECDSA. Все данные хранятся в /etc/letsencrypt: в archive/ находятся сами файлы ключей и сертификатов, live/ содержит символические ссылки на текущие версии, renewal/ хранит по одному конфигурационному файлу на каждый сертификат, а accounts/ — ключ вашей учетной записи ACME.
Как работает HTTP-01 и почему порт 80 обязателен
Проверка HTTP-01 — это обратный вызов. Вы запрашиваете у Let's Encrypt сертификат для example.com; центр сертификации разрешает имя через публичный DNS, открывает соединение с портом 80 по найденному адресу и запрашивает http://example.com/.well-known/acme-challenge/<token>. Ваш сервер отвечает содержимым токена, который Certbot предварительно записал на диск. Это весь механизм целиком. Из него вытекают три следствия, которые объясняют большинство неудачных попыток выпуска сертификатов.
- Порт 80 должен быть доступен из публичного интернета, а не только с вашего локального компьютера. Правило
ufw, группа безопасности облачного провайдера или брандмауэр в консоли VPS, открывающий только порт 443, блокируют выпуск сертификата и любое последующее обновление. - DNS уже должен указывать на этот сервер. Валидационный сервер выполняет собственный поиск извне; ваши записи
/etc/hostsи кэш браузера для него не имеют значения. - Если вы публикуете запись AAAA, IPv6 проверяется первым. Let's Encrypt повторяет попытку через IPv4 только в том случае, если соединение по IPv6 завершается по таймауту. Если в соединении по IPv6 отказано, повторная попытка не предпринимается, а устаревшая запись AAAA, указывающая на хост, который принимает соединение и отдает что-то другое, приводит к критической ошибке.
Редиректы разрешены: процесс валидации следует за HTTP-редиректом на HTTPS и не обращает внимания на то, что сертификат на конечном узле отсутствует, просрочен или является самоподписанным. Чего он не будет делать, так это начинать проверку где-либо, кроме порта 80. В Certbot нет реализации TLS-ALPN-01, поэтому вариант «просто используйте 443» здесь не сработает.
Выбор метода аутентификации: --nginx, --webroot, --standalone
--nginx — оптимальный выбор по умолчанию, если nginx уже запущен и обслуживает домен. Certbot анализирует конфигурацию, добавляет временную локацию для прохождения проверки (challenge), перезагружает nginx, подтверждает владение доменом, а затем записывает TLS-директивы в блок server. Простой не требуется.
sudo certbot --nginx -d example.com -d www.example.comСкриптовый вариант для «чистой» системы:
sudo certbot --nginx \
-d example.com -d www.example.com \
--agree-tos -m ops@example.com --no-eff-email \
--redirect --non-interactive--webroot подходит, если вы не хотите, чтобы Certbot вносил изменения в конфигурацию nginx. Это актуально, если вы генерируете конфиги из шаблонов, храните их в git или разворачиваете через Ansible. Certbot создает только файл проверки в директории, которую вы уже обслуживаете.
sudo certbot certonly --webroot -w /var/www/example.com \
-d example.com -d www.example.com \
--deploy-hook "systemctl reload nginx"--standalone подходит, если порт 80 свободен: например, для почтового сервера, API, работающего только по 443 порту, или скрипта первичной настройки, выполняемого до установки nginx. Certbot самостоятельно занимает порт 80 на несколько секунд. Если nginx уже запущен, этот метод завершится ошибкой, поэтому остановите его перед запуском:
sudo certbot certonly --standalone -d mail.example.com \
--pre-hook "systemctl stop nginx" \
--post-hook "systemctl start nginx"Эти хуки сохраняются в конфигурации обновления сертификата, поэтому остановка и запуск будут выполняться автоматически при каждом продлении.
Серверный блок, работающий до и после получения сертификата
Проблема «курицы и яйца»: Nginx отказывается запускаться, если в ssl_certificate указан несуществующий файл, а Certbot не может пройти валидацию, пока Nginx выключен. Сначала запустите сайт на 80 порту.
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com;
index index.html;
location ^~ /.well-known/acme-challenge/ {
root /var/www/example.com;
default_type "text/plain";
try_files $uri =404;
}
location / {
try_files $uri $uri/ =404;
}
}Выполните sudo nginx -t && sudo systemctl reload nginx, убедитесь, что curl -I http://example.com/ отвечает извне сервера, а затем выпустите сертификат. После этого:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
location ^~ /.well-known/acme-challenge/ {
root /var/www/example.com;
default_type "text/plain";
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
root /var/www/example.com;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}Более длинный префикс ACME имеет приоритет над location /, поэтому return 301 не перехватывает запрос на проверку. Модификатор ^~ полезен в других случаях: как только префикс совпадает, Nginx полностью пропускает регулярные выражения в location, поэтому правила, добавленные позже, например location ~ /\. { deny all; }, не смогут заблокировать проверку. Сохранение этого location на 80 порту гарантирует, что продление сертификатов будет работать даже после того, как остальная часть сайта перейдет исключительно на HTTPS.
Оба блока выше отдают файлы с диска; если Nginx выступает в роли фронтенда для приложения, location / превращается в блок proxy_pass, а серверный блок reverse proxy, построчно описывает заголовки, необходимые приложению, в то время как location для ACME и директивы TLS остаются без изменений.
Синтаксис HTTP/2 зависит от версии Nginx. В Ubuntu 24.04 поставляется Nginx 1.24, который требует указания параметра внутри listen 443 ssl http2; и не запускается при использовании отдельной директивы http2 on;. В Debian 13 поставляется Nginx 1.26, который требует http2 on; и лишь выдает предупреждение о том, что встроенная форма устарела. Сначала проверьте nginx -v.
Указывайте Nginx путь к live/, а не к archive/. Символические ссылки live/ обновляются при каждом продлении; жестко прописанный путь в archive/ приведет к тому, что вы останетесь с сертификатом, срок действия которого истечет.
Wildcard-сертификаты требуют DNS-01, а DNS-01 требует плагин
Wildcard-сертификат (*.example.com) невозможно подтвердить через HTTP-01, так как не существует единственного имени хоста, с которого можно было бы получить файл. Единственный путь — DNS-01: вы подтверждаете контроль над доменом, публикуя TXT-запись _acme-challenge.example.com. Для автоматизации этого процесса Certbot требуются API-ключи вашего DNS-провайдера, именно для этого и существуют плагины. В полном руководстве по wildcard-сертификатам описаны механизмы работы с TXT-записями и ловушки при ручном продлении; ниже приведена краткая версия для Cloudflare.
sudo snap set certbot trust-plugin-with-root=ok
sudo snap install certbot-dns-cloudflareВ случае использования apt-пакета путь будет выглядеть как sudo apt install python3-certbot-dns-cloudflare. Учётные данные следует разместить в файле, доступном только пользователю root:
# /root/.secrets/cloudflare.ini
# then: sudo chmod 600 /root/.secrets/cloudflare.ini
dns_cloudflare_api_token = your_scoped_token_hereОграничьте права токена только редактированием DNS для конкретной зоны. Это ключ к вашей DNS-инфраструктуре; относитесь к нему соответствующим образом.
sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /root/.secrets/cloudflare.ini \
-d example.com -d '*.example.com'Заключайте wildcard-имя в кавычки, чтобы оболочка (shell) не интерпретировала символ подстановки. DNS-01 также решает задачи, недоступные для HTTP-01: получение сертификатов для хостов без открытого 80-го порта, внутренних сервисов, серверов, доступных только через собственный WireGuard VPN на VPS, или панелей управления в закрытых сетях.
Обновление: 90 дней, таймер и deploy hook
Сертификаты Let's Encrypt действительны в течение 90 дней. Пакет из apt (Certbot 2.9.0) выполняет обновление, когда до истечения срока остается менее 30 дней. Версия из snap отслеживает актуальный Certbot, который начиная с версии 4.1 также запрашивает у Let's Encrypt рекомендуемое время обновления через ACME Renewal Info (ARI) и может обновить сертификат раньше; для сертификата Let's Encrypt это обычно означает примерно две трети срока его действия. В любом случае у вас есть окно примерно в 30 дней, в течение которого сбой обновления — это устранимая неприятность, а не авария. Ожидайте, что обновления станут происходить чаще начиная со следующего года: профиль Let's Encrypt по умолчанию переходит на 64-дневные сертификаты 10 февраля 2027 года и на 45-дневные сертификаты 16 февраля 2028 года. Let's Encrypt больше не рассылает электронные письма с предупреждениями об истечении срока, никто не будет напоминать вам об этом, поэтому мониторинг — ваша задача.
Проверьте таймер, установленный в вашей системе:
systemctl list-timers 'certbot*' 'snap.certbot*'
sudo certbot certificatescertbot renew проходит по всем конфигурациям в /etc/letsencrypt/renewal/, пропускает те, срок которых еще не подошел, и обновляет остальные, используя параметры, сохраненные в этой конфигурации. Это те же флаги, что были при первом запуске, если вы не изменили их с помощью certbot reconfigure. Именно поэтому первый запуск важен: он записывается в конфигурацию.
Обновление файла на диске само по себе ничего не меняет, nginx продолжает отдавать старый сертификат из памяти, пока не получит команду на перезагрузку. Настройте deploy hook один раз:
sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh >/dev/null <<'EOF'
#!/bin/sh
set -e
nginx -t && systemctl reload nginx
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.shЛюбой исполняемый файл в renewal-hooks/deploy/ запускается после каждого успешного обновления. Флаг --deploy-hook выполняет ту же задачу для одного сертификата, сохраняя renew_hook = ... в его конфигурации обновления. certbot --nginx выполняет перезагрузку автоматически; в настройках --webroot и --standalone это не предусмотрено. Отсутствие хука — это именно та причина, по которой сайт отдает просроченный сертификат, в то время как certbot certificates бодро сообщает о наличии свежего. Любое другое приложение, которое считывает сертификат при запуске, требует аналогичного хука; контейнеризированному приложению, такому как установка Nextcloud на VPS с Docker, TLS и резервным копированием, также необходимо прописать здесь свой шаг перезапуска или перезагрузки.
Тестирование реального обновления
sudo certbot renew --dry-runЭта команда запускает полную процедуру проверки в тестовой среде Let's Encrypt: используется тот же путь выполнения кода, те же настройки firewall и DNS. При этом запросы не учитываются в лимитах на выпуск сертификатов (в тестовой среде действуют свои, гораздо более мягкие ограничения), а сами сертификаты не сохраняются на диск. Если проверка проходит успешно, автоматическое обновление через 60 дней также должно пройти без ошибок, при условии, что конфигурация сервера не изменится.
Пробный запуск (dry run) не гарантирует срабатывание хука перезагрузки, так как поведение зависит от версии Certbot. Проверьте этот этап вручную: выполните скрипт хука напрямую, убедитесь, что systemctl reload nginx завершается успешно, и проверьте sudo grep renew_hook /etc/letsencrypt/renewal/example.com.conf.
Ошибки, с которыми вы столкнетесь на практике
Could not bind to IPv4 or IPv6., --standalone возникает, когда nginx уже занимает 80 порт. Используйте --nginx или --webroot, либо остановите nginx на время запуска. Проверьте, какой процесс занимает порт, с помощью sudo ss -lntp | grep ':80'.
Timeout during connect (likely firewall problem), Let's Encrypt не может подключиться к 80 порту. Проверяйте по порядку: sudo ufw status (откройте его через sudo ufw allow 'Nginx Full'), затем собственный файрвол провайдера VPS, затем DNS. Протестируйте доступность с внешнего узла, а не с самого сервера: curl -sSv http://example.com/.well-known/acme-challenge/test. Устаревшая AAAA-запись вызывает такую же ошибку.
unauthorized :: Invalid response from http://example.com/.well-known/acme-challenge/xyz: 404, 80 порт доступен, но токен не отдается. Запрос попал в другой server block (проверьте, какой из них владеет default_server), либо директория, переданная в -w, не совпадает с той, которую обслуживает nginx. Создайте файл в /var/www/example.com/.well-known/acme-challenge/test и попробуйте получить его извне; если сервер возвращает 404, проблема не в сертификате.
DNS problem: NXDOMAIN looking up A for example.com, имя не разрешается публично. Это новые записи, которые еще не распространились, либо запись в зоне, которую ваш регистратор не обслуживает.
too many certificates (5) already issued ..., ограничение по количеству запросов (rate limit); именно с ним сталкиваются при отладке в цикле. Сообщение указывает тип лимита и заканчивается временем retry after. Let's Encrypt ограничивает выпуск дубликатов сертификатов (для одного и того же набора имен) до пяти в неделю, и это ограничение нельзя обойти — поможет только ожидание. Отдельно разрешено 50 новых сертификатов на зарегистрированный домен в неделю; этот лимит можно увеличить по запросу. Запрос на тот же набор имен, что и у существующего сертификата, считается продлением и не учитывается в этом лимите, а продления, выполненные через ARI, освобождены от всех ограничений. Для отладки используйте тестовую среду с флагом --dry-run.
nginx: [emerg] cannot load certificate "/etc/letsencrypt/live/example.com/fullchain.pem": No such file or directory, nginx настроен на сертификат, который не был выпущен или был удален командой certbot delete. Закомментируйте TLS server block, запустите nginx, выпустите сертификат, затем верните блок в конфигурацию.
open() "/etc/letsencrypt/options-ssl-nginx.conf" failed, этот файл поставляется вместе с пакетом плагина nginx. На системе certonly без python3-certbot-nginx либо установите плагин, либо замените строку include на собственные настройки ssl_protocols и ssl_ciphers.
Масштабирование управления сертификатами
Один сертификат может содержать до 100 имен, и использование одного certbot --nginx -d a.example.com -d b.example.com ... кажется заманчивым, пока одна устаревшая DNS-запись не провалит валидацию и не выведет из строя все остальные имена в этом сертификате. Раздельные сертификаты для каждого сайта выходят из строя независимо, что и требуется на сервере, где размещено более двух-трех ресурсов. Если сайтов много, использование reverse proxy с поддержкой ACME оправдывает себя: Traefik reverse proxy, работающий с несколькими приложениями через Docker Compose, самостоятельно запрашивает и обновляет сертификаты, а Certbot полностью исключается из процесса. Выбор конкретного прокси-сервера остается за вами, а сравнение Nginx, Caddy и Traefik в основном сводится к тому, какую часть работы по настройке сертификатов и параметров приложений вы хотите делегировать прокси.
Создавайте резервную копию /etc/letsencrypt целиком, включая sudo tar -czf letsencrypt-$(date +%F).tar.gz -C /etc letsencrypt, с сохранением символических ссылок. В этом дереве каталогов хранится accounts/ — ваш ключ учетной записи ACME, который невозможно восстановить в исходном виде. Переезд на новый VPS сводится к следующему: синхронизируйте дерево каталогов с помощью -a, установите Certbot, обновите DNS-записи и выполните certbot renew --dry-run перед переключением трафика.
При пересборке сервера или переходе на новый релиз LTS таймер обновления не переносится автоматически. После любой миграции, восстановления из снимка (snapshot) или обновления дистрибутива выполните systemctl list-timers 'certbot*' и одну команду --dry-run. Если пропустить этот шаг, сайт станет недоступным через 89 дней, в 3 часа ночи, из-за сертификата, который, как все полагали, обновляется автоматически.
Описанные выше методы HTTP-01 предполагают наличие сервера под вашим контролем с публичным IP-адресом и открытым 80 портом — иными словами, VPS; для метода DNS-01 это не требуется. Механика работы в обоих случаях идентична.
Те же шаги по работе с сертификатами применимы для Apache вместо nginx, а если использование публичного сертификата невозможно, самоподписанный сертификат в Ubuntu подойдет для внутренних сервисов.
FAQ
Нужно ли открывать 80 порт, если сайт работает только по HTTPS?
Да, для прохождения проверки HTTP-01. Let's Encrypt всегда начинает запрос проверки с 80 порта, а в Certbot нет реализации TLS-ALPN-01. Поэтому файрвол, открывающий только 443 порт, заблокирует как первичный выпуск сертификата, так и любое последующее автоматическое обновление. Редирект с 80 порта на HTTPS допустим, проверка проследует по нему. Единственный способ полностью исключить 80 порт — использовать DNS-01 через плагин провайдера или с помощью --manual --preferred-challenges dns в Certbot. В этом случае вы добавляете TXT-запись вручную, и обновление перестает быть автоматическим, если вы не предоставите скрипт --manual-auth-hook.
Что выбрать для nginx на Ubuntu 24.04: apt или snap?
Используйте apt. sudo apt install certbot python3-certbot-nginx предоставляет Certbot 2.9.0 в Ubuntu 24.04, что достаточно для всех задач из этого руководства. Вы будете получать обновления безопасности через unattended-upgrades, при этом snapd не потребуется. Выбирайте snap, только если вам срочно нужна самая новая версия или DNS-плагин, распространяемый исключительно через snap. В любом случае выберите что-то одно: две установки означают два таймера обновления, указывающих на один и тот же каталог /etc/letsencrypt, и именно тот, о котором вы забудете, доставит проблемы.
Может ли Certbot выпустить wildcard-сертификат для nginx?
Только через DNS-01. Для wildcard-домена, такого как *.example.com, нет конкретного имени хоста, с которого можно было бы загрузить файл проверки, поэтому методы --nginx, --webroot и --standalone не подходят. Установите плагин для вашего DNS-провайдера, поместите ограниченный API-токен в файл с правами доступа только для root и выполните certbot certonly --dns-cloudflare -d example.com -d '*.example.com', взяв wildcard в кавычки, чтобы оболочка не интерпретировала его как шаблон.
Почему nginx продолжает отдавать старый сертификат после успешного обновления?
nginx хранит сертификат в оперативной памяти и не замечает изменений в файле на диске, пока не выполнит перезагрузку. certbot --nginx выполняет перезагрузку автоматически, но при запуске --webroot и --standalone этого не происходит. В результате обновление может завершиться успешно, но браузер продолжит видеть истекающий сертификат. Поместите исполняемый скрипт в /etc/letsencrypt/renewal-hooks/deploy/, который выполняет nginx -t && systemctl reload nginx, и он будет срабатывать после каждого успешного обновления.
Гарантирует ли certbot renew --dry-run, что обновление сработает?
Почти. Команда выполняет реальную проверку через тестовую среду (staging), используя те же настройки файрвола, DNS и пути выполнения кода. Это не расходует лимиты на выпуск сертификатов и не сохраняет файлы на диск, поэтому успешный результат подтверждает корректность сетевой части. Однако это не гарантирует, что сработает ваш скрипт развертывания (deploy hook). Протестируйте его отдельно: запустите скрипт вручную и проверьте sudo grep renew_hook /etc/letsencrypt/renewal/example.com.conf.