SSL за вечер: Nginx + Let’s Encrypt без сюрпризов на 80 порту
SSL за вечер: Nginx + Let’s Encrypt без сюрпризов на 80 порту
Выпустить бесплатный сертификат Let’s Encrypt на VPS реально за один вечер — если открыт порт 80 (или настроен DNS-challenge), корректны A-записи и Nginx отдаёт нужный server_name. Ниже — рабочий порядок для Ubuntu с certbot: подготовка DNS, выпуск через плагин Nginx, автопродление и типичные сюрпризы на 80 порту.
Базовый SSH и firewall мы уже разбирали в гайде по настройке Ubuntu. Здесь — только HTTPS. Если после выпуска сайт всё ещё «тормозит», это уже не сертификат — смотрите диагностику CPU/диска/RAM.
Как работает HTTP-01 в двух словах
Let’s Encrypt должен убедиться, что вы контролируете домен. В самом распространённом сценарии (HTTP-01) их бот стучится на http://example.com/.well-known/acme-challenge/<token> и ждёт заранее согласованный ответ. Запрос идёт на порт 80. Если 80 закрыт firewall’ом, слушает другой процесс, или Nginx отдаёт challenge не тому server_name — выпуск падает.
Отсюда три практических вывода:
- до выпуска и для renew по HTTP-01 порт 80 должен быть доступен с интернета хотя бы для ACME-пути;
- DNS обязан указывать на тот VPS, где крутится Certbot;
- «закрыли 80 после первого успеха» — частая причина, почему через 60 дней сайт внезапно остаётся со старым сертификатом.
DNS-01 (TXT-запись) нужен, когда 80 открыть нельзя (корпоративный периметр, только 443 наружу). Он сложнее в ручном режиме из‑за TTL и удобнее через API DNS-провайдера.
Перед Certbot: DNS и Nginx
Без этих четырёх пунктов Certbot почти всегда падает с невнятной ошибкой:
- Домен указывает A (и при необходимости AAAA) на IP вашего VPS.
- В Nginx есть
server_name example.com www.example.com;. - Порт 80 доступен снаружи (UFW и панель хостинга), если используете HTTP-01.
- Время на сервере синхронизировано (
timedatectl) — иначе LE может отклонить запрос.
Проверка DNS и ответа с вашей машины:
dig +short example.com A
dig +short www.example.com A
curl -I http://example.com
sudo ss -tlnp | grep ':80'
timedatectl status
curl должен отвечать именно с вашего VPS (смотрите IP в заголовках/трассировке), а не со старого хостинга. Сверьте вывод dig с curl ifconfig.me или IP из панели. Если A-запись ещё в TTL — подождите или временно снизьте TTL заранее (за сутки до переезда поставьте TTL 300).
Про firewall на Ubuntu:
sudo ufw status
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
В панели хостинга отдельно проверьте security group / firewall: правила UFW на машине не помогут, если снаружи 80 режется на уровне сети.
Минимальный HTTP-vhost до выпуска (чтобы challenge прошёл):
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/html;
location / {
try_files $uri $uri/ =404;
}
}
Не обязательно отдавать прод-сайт на 80: Certbot сам добавит location для /.well-known/acme-challenge/. Важно, чтобы запрос на имя домена попадал в этот server, а не в дефолтный catch-all. Проверьте:
sudo nginx -t
sudo systemctl reload nginx
curl -I http://example.com/
# убедитесь, что Host попадает в нужный vhost:
curl -I http://127.0.0.1/ -H 'Host: example.com'
Если у вас несколько сайтов на одном IP, дефолтный server без нужного server_name часто «съедает» challenge — Certbot пишет про timeout или invalid response, хотя Nginx вроде «работает».
Установка Certbot и выпуск сертификата
На Ubuntu 22.04/24.04:
sudo apt update
sudo apt install -y nginx certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
Certbot предложит указать email (для уведомлений об истечении) и редирект HTTP→HTTPS — для публичного сайта обычно соглашайтесь. После успеха:
sudo nginx -t
sudo systemctl reload nginx
curl -I https://example.com
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -dates -subject -issuer
Сертификаты лежат в /etc/letsencrypt/live/example.com/ (fullchain.pem, privkey.pem). Не копируйте приватный ключ в git, тикеты и чаты. Права на каталог Let’s Encrypt оставляйте как поставило пакетное ПО — не делайте chmod -R 777.
Если используете только apex или только www — укажите одно имя в -d, а второе редиректите отдельно. Для двух имён безопаснее сертифицировать оба сразу: иначе браузер на «неправильном» имени получит чужой сертификат или ошибку имени.
Staging для отладки (не тратит прод-лимиты):
sudo certbot --nginx --test-cert -d example.com -d www.example.com
Когда staging проходит — выпустите боевой сертификат без --test-cert. Браузеры staging-сертификат не доверяют — это нормально.
Автопродление
Таймер systemd у Certbot обычно уже есть после установки пакета:
sudo systemctl list-timers | grep certbot
sudo systemctl status certbot.timer
sudo certbot renew --dry-run
dry-run обязателен после первой установки, после смены firewall и после правок server_name. Если renew падает — чаще всего закрыт 80 порт, сменился IP в DNS или в Nginx остался только HTTPS-блок без возможности ответить на challenge.
Сертификат LE живёт около 90 дней; Certbot обычно обновляет примерно за 30 дней до конца. Именно поэтому «красивый первый выпуск» без dry-run — ловушка: проблема всплывёт через два месяца.
Проверьте, что после успешного renew выполняется reload Nginx:
sudo ls -la /etc/letsencrypt/renewal-hooks/deploy/ 2>/dev/null
sudo grep -R . /etc/letsencrypt/renewal-hooks/ 2>/dev/null | head
Можно положить простой hook:
sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh >/dev/null <<'EOF'
#!/bin/bash
systemctl reload nginx
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
Для чисто DNS-challenge (когда 80 нельзя открыть) используйте плагин вашего DNS-провайдера или ручной DNS. Это дольше из‑за TTL и удобнее автоматизировать через API провайдера, а не руками. Ручной режим (certbot certonly --manual --preferred-challenges dns) для прод-renew почти не подходит — его некому запускать ночью.
Мониторинг: заведите календарное напоминание или внешнюю проверку срока сертификата (openssl выше или любой uptime-монитор с проверкой SSL). Письма от LE на email из Certbot тоже помогают, но ящик должен быть живым.
Типичные ошибки
Connection refused на 80. Firewall или Nginx не слушает. Проверьте sudo ss -tlnp | grep ':80', ufw status и правила в панели хостинга (security group). Иногда 80 слушает другой контейнер Docker — Certbot на хосте challenge не получит.
Certbot не видит домен / timeout. Неверная A-запись, Cloudflare в режиме «Full (strict)» до выпуска первого сертификата (или проксирование orange cloud при HTTP-01 без правильной настройки), опечатка в -d, или запрос уходит на другой server без нужного server_name. Для Cloudflare на время выпуска часто проще временно поставить DNS only (серое облако), выпустить сертификат, потом вернуть прокси.
Сайт открывается по IP, но не по HTTPS-имени. В server нет нужного server_name или остался дефолтный vhost с чужим сертификатом. Браузер ругается на имя в сертификате — смотрите -servername в openssl s_client.
После renew сайт на старом сертификате. Не выполнен nginx reload; смотрите хуки Certbot и nginx -t. Браузер мог закэшировать — проверьте дату через openssl выше с сервера и с внешней машины.
Rate limit Let’s Encrypt. При частых ошибках выпуска подождите (лимиты описаны в документации LE) или используйте staging: certbot --nginx --test-cert ..., затем обычный выпуск. Не крутите цикл «ещё раз нажал Enter» десять раз подряд на проде.
IPv6 (AAAA) указывает «в никуда». Если есть AAAA, LE может ходить по IPv6. Либо почините AAAA на ваш VPS, либо уберите запись, пока не готовы раздавать сайт по IPv6.
Несколько Certbot’ов. Certbot в контейнере и Certbot на хосте дерутся за одни и те же пути/порты. Выберите одну схему и придерживайтесь её.
Минимальный фрагмент Nginx после выпуска
Certbot правит конфиг сам; логика такая:
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
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 / proxy_pass — ваш проект
root /var/www/html;
location / {
try_files $uri $uri/ /index.php?$args;
}
}
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
Важно: даже с редиректом на HTTPS для HTTP-01 renew Certbot должен иметь возможность ответить на /.well-known/acme-challenge/ на порту 80. Плагин Nginx обычно умеет это сам; если вы вручную «заглушили» весь 80 жёстким return 301 в неожиданном месте и отключили плагин — renew сломается. Проверяйте dry-run после ручных правок.
HSTS (Strict-Transport-Security) включайте только когда уверены, что HTTPS стабилен на всех поддоменах, которые открываете пользователям. Ошибочный HSTS на год «запирает» браузеры на битом HTTPS — откатить это сложно. Начинайте с короткого max-age на тестовом окружении.
Docker + Nginx: кто слушает 80/443
Типичные схемы:
- Nginx (или Caddy) на хосте, приложения в контейнерах на localhost — Certbot на хосте, как в этой статье;
- reverse-proxy контейнер (
nginx-proxy, Traefik, Caddy) с ACME внутри Docker-сети; - отдельный контейнер certbot + shared volume с сертификатами.
Главное правило: один слушатель на 80/443 снаружи. Если и хост, и контейнер пытаются занять :80, один из них не поднимется, а Certbot получит connection refused.
Проверка:
sudo ss -tlnp | grep -E ':80|:443'
docker ps --format '{{.Names}} {{.Ports}}' | grep -E '80|443'
Чек-лист на вечер
- DNS указывает на VPS (
digсовпадает с IP сервера); AAAA либо корректна, либо отсутствует. curl -I http://доменотвечает с вашего Nginx; UFW и панель пускают 80/443.certbot --nginxбез ошибок,curl -I https://домен— 200/301 с валидным сертификатом.certbot renew --dry-runок; есть hook на reload Nginx.- Редирект на HTTPS и понятный план: кто чинит, если renew упадёт через 60 дней; email в Certbot живой.
Нужен чистый сервер под сайт или бота — каталог аренды VPS, локация Москва или Германия (Франкфурт) на выбор; выдача до 120 секунд после оплаты. Для экспериментов с DNS и staging удобен тариф с посуточной оплатой.
FAQ
Нужен ли платный SSL? Для большинства сайтов Let’s Encrypt достаточно. Платный имеет смысл при особых требованиях (EV, поддержка древних клиентов, корпоративные политики) — не из‑за мифа про «доверие Google». Поисковики смотрят на валидный HTTPS, а не на цену сертификата.
Можно ли закрыть 80 после выпуска? Для HTTP-01 renew порт 80 должен оставаться доступен хотя бы для /.well-known. Либо переходите на DNS-01 и автоматизируйте его. Полностью закрытый 80 при HTTP-01 = отложенная авария через ~60 дней.
www и apex — два имени? Да, указывайте оба в -d, либо настройте редирект одного на другой и сертифицируйте оба. Иначе часть пользователей попадёт на имя без сертификата.
Docker + Nginx на хосте? Либо Certbot на хосте проксирует в контейнер, либо отдельный контейнер с ACME. Главное — один слушатель на 80/443, без гонки за портом.
Сколько живёт сертификат LE? Около 90 дней; Certbot обычно обновляет около 30 дней до конца. Именно поэтому dry-run и открытый 80 важнее «красивого» первого выпуска.
Что делать, если домен ещё на старом хостинге? Сначала подготовьте Nginx и конфиг на новом VPS, снизьте TTL заранее, переключите A-запись, дождитесь резолва (dig с разных резолверов), затем запускайте Certbot. Параллельный выпуск «на будущее» без вашего IP в DNS не сработает для HTTP-01.