Назад в блог/SSL за вечер: Nginx + Let’s Encrypt без сюрпризов на 80 порту

SSL за вечер: Nginx + Let’s Encrypt без сюрпризов на 80 порту

Как за вечер выпустить Let’s Encrypt на VPS с Nginx: DNS, порт 80, certbot, автопродление и типичные ошибки HTTP-01 без сюрпризов.

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 почти всегда падает с невнятной ошибкой:

  1. Домен указывает A (и при необходимости AAAA) на IP вашего VPS.
  2. В Nginx есть server_name example.com www.example.com;.
  3. Порт 80 доступен снаружи (UFW и панель хостинга), если используете HTTP-01.
  4. Время на сервере синхронизировано (timedatectl) — иначе LE может отклонить запрос.

Проверка DNS и ответа с вашей машины:

BASH
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).

Схема: браузер → 443 Nginx → Certbot обновляет сертификат
HTTPS на VPS через Nginx и Certbot

Про firewall на Ubuntu:

BASH
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 прошёл):

NGINX
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. Проверьте:

BASH
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:

BASH
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 — для публичного сайта обычно соглашайтесь. После успеха:

BASH
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 для отладки (не тратит прод-лимиты):

BASH
sudo certbot --nginx --test-cert -d example.com -d www.example.com

Когда staging проходит — выпустите боевой сертификат без --test-cert. Браузеры staging-сертификат не доверяют — это нормально.

Автопродление

Таймер systemd у Certbot обычно уже есть после установки пакета:

BASH
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:

BASH
sudo ls -la /etc/letsencrypt/renewal-hooks/deploy/ 2>/dev/null
sudo grep -R . /etc/letsencrypt/renewal-hooks/ 2>/dev/null | head

Можно положить простой hook:

BASH
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 правит конфиг сам; логика такая:

NGINX
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.

Проверка:

BASH
sudo ss -tlnp | grep -E ':80|:443'
docker ps --format '{{.Names}} {{.Ports}}' | grep -E '80|443'

Чек-лист на вечер

  1. DNS указывает на VPS (dig совпадает с IP сервера); AAAA либо корректна, либо отсутствует.
  2. curl -I http://домен отвечает с вашего Nginx; UFW и панель пускают 80/443.
  3. certbot --nginx без ошибок, curl -I https://домен — 200/301 с валидным сертификатом.
  4. certbot renew --dry-run ок; есть hook на reload Nginx.
  5. Редирект на 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.

Документация

Нужен быстрый сервер под ваш проект?

Арендуйте производительный KVM VPS в Москве или Германии на скоростных NVMe дисках от 300 ₽/мес. Автовыдача за 60 секунд, выделенный IPv4 и защита от DDoS.

Выбрать тариф