Что именно проверяет Let’s Encrypt в HTTP-01
При проверке домена методом HTTP-01 центр сертификации ожидает, что ваш сервер отдаст «токен» по URL вида http://example.com/.well-known/acme-challenge/XYZ. ACME-клиент (Certbot, acme.sh, lego и другие) либо создаёт файл в каталоге сайта (webroot), либо временно поднимает обработчик, а Let’s Encrypt делает обычный HTTP-запрос по порту 80.
Ключевой момент: проверка начинается именно по HTTP. Редирект на HTTPS допустим, но он не должен ломать путь, уводить на другой хост/порт, попадать под авторизацию или правила WAF/CDN.
Почти все проблемы 403/404/timeout сводятся к одному: запрос к
/.well-known/acme-challenge/не попадает туда, где лежит токен, или вообще не доходит до вашего Nginx (DNS, IPv6, firewall, прокси).
Симптомы: 403, 404 и timeout — в чём разница
403 Forbidden
Запрос дошёл до сервера (или до внешнего прокси/WAF), но доступ запрещён. Типовые причины:
- на
/.well-knownили на весь сайт висит запрет (deny all) или базовая авторизация; - проблемы с правами/доступом по пути до каталога webroot;
- путь попал в другой
location, который намеренно возвращает 403; - CDN/WAF блокирует запросы к
/.well-known.
404 Not Found
Запрос дошёл, но файл не найден. Почти всегда это «ошибка маршрутизации»:
- на HTTP (порт 80) используется не тот
root; - ACME-клиент в режиме webroot пишет токен в другой каталог;
- запрос перехватывает другой
server_name(дефолтный сервер, соседний сайт); - приложение/фреймворк перехватывает
/.well-knownи отдаёт свой 404.
Timeout (Connection timed out / Timeout during connect)
Let’s Encrypt не смог подключиться к вашему серверу по HTTP. На практике это:
- порт 80 закрыт в firewall (на сервере или в панели провайдера);
- Nginx не слушает 80/tcp;
- DNS указывает на другой IP;
- есть запись
AAAA, но IPv6 не настроен или по IPv6 закрыт порт 80; - порт 80 занят другим сервисом или проброшен «не туда» (NAT, балансировщик, контейнеры).

Если нужен предсказуемый контроль сети и конфигурации (включая IPv4/IPv6, firewall и Nginx), удобнее выпускать и продлевать сертификаты на VDS.
Быстрая проверка «снаружи»: что видит Let’s Encrypt
Задача диагностики — пройти цепочку «DNS → IP → порт 80 → правильный server block → правильный файл». Лучше сначала добиться, чтобы тестовый файл стабильно отдавался по HTTP, и только потом гонять ACME-клиента.
1) Проверяем A/AAAA и куда реально резолвится домен
dig +short A example.com
dig +short AAAA example.com
Если есть AAAA, Let’s Encrypt может пойти по IPv6. Тогда Nginx должен слушать [::]:80, а firewall должен пропускать 80/tcp по IPv6. Если вы не используете IPv6 осознанно, лучше привести запись AAAA и IPv6-настройки в соответствие (или временно убрать AAAA на время выпуска).
2) Проверяем, что порт 80 доступен
ss -lntp | grep ':80 '
Проверка с внешней стороны (с другого сервера/домашнего интернета):
nc -vz example.com 80
Если тут таймаут, это ещё не история про location: сначала чинится сеть, DNS, порт и маршрутизация.
3) Проверяем конкретно ACME URL тестовым файлом
Создайте файл, который вы точно сможете запросить:
mkdir -p /var/www/example.com/.well-known/acme-challenge
printf 'acme-test' > /var/www/example.com/.well-known/acme-challenge/ping
И проверьте отдачу по HTTP:
curl -i http://example.com/.well-known/acme-challenge/ping
Ожидаемо: HTTP/1.1 200 OK и тело acme-test. Если видите 301/302 — нормально, но проверьте, что редирект сохраняет путь и ведёт на доступный адрес.
Правильный Nginx-конфиг для HTTP-01: минимально надёжный вариант
Цель простая: гарантированно отдать /.well-known/acme-challenge/ по HTTP, даже если весь сайт принудительно уходит на HTTPS. Для этого location под challenge должен стоять выше общего редиректа.
Вариант 1: отдельный server на 80 с webroot и редиректом всего остального
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com;
location ^~ /.well-known/acme-challenge/ {
default_type text/plain;
try_files $uri =404;
allow all;
}
location / {
return 301 https://$host$request_uri;
}
}
Почему так:
^~повышает приоритет и не даёт regex-location «перехватить» challenge.try_files $uri =404;заставляет Nginx искать реальный файл и не проксировать запрос в приложение.listen [::]:80;закрывает типовой кейс с AAAA/IPv6.
Вариант 2: alias вместо root (когда webroot сложный)
Если основной root у сайта другой (например, фронтенд и бэкенд разнесены) или управляется приложением, удобнее выделить отдельный каталог под challenge:
location ^~ /.well-known/acme-challenge/ {
alias /var/lib/acme-challenges/;
default_type text/plain;
try_files $uri =404;
allow all;
}
Важно: при alias путь на диске считается без префикса location. То есть файл для URL /.well-known/acme-challenge/XYZ должен лежать как /var/lib/acme-challenges/XYZ.
Если сертификат нужен для продакшена (магазин, кабинет, API), не откладывайте нормальный выпуск и продление: на странице SSL-сертификаты можно подобрать сертификат под задачу и уровень валидации.
Почему получаем 404: типовые ловушки с webroot и server_name
Неверный webroot у клиента
В режиме webroot ACME-клиент кладёт токен строго в каталог, который вы ему указали. Если в Nginx на 80 стоит другой root (или у вас несколько server blocks), Let’s Encrypt увидит 404.
Практическое правило: сначала добейтесь, чтобы тестовый файл ping отдавался по HTTP. Если ping отдаётся, а проверка Let’s Encrypt — нет, значит клиент пишет токен в другое место (проверьте параметры webroot, путь в конфиге клиента и права записи).
Запрос попадает не в тот server block
Симптом: через curl по HTTP открывается «чужой» сайт или дефолтная страница. Причины обычно такие:
- не совпадает
server_name(особенно если проверяете иexample.com, иwww.example.com); - есть дефолтный сервер на 80, который перехватывает запросы;
- конфиг изменили, но Nginx не перезагрузили.
Подсмотреть, какие блоки реально слушают 80 и где объявлен домен:
nginx -T | grep -n "listen 80"
nginx -T | grep -n "server_name" | grep example.com
Если домен неожиданно резолвится не на тот IP, начните с ревизии DNS. В сложных случаях помогает аккуратная инвентаризация доменной зоны и записей — см. материал про перенос домена и настройку DNS (EPP, NS и типовые ошибки).
Почему получаем 403: права, запреты и «безопасные» правила
Права на каталог и выполнение по пути
Nginx должен иметь возможность пройти по всем каталогам до файла. Даже если сам файл имеет 644, но какой-то каталог выше без execute-бита, будет 403.
Быстрая диагностика прав по всему пути:
namei -l /var/www/example.com/.well-known/acme-challenge/ping
Обычно каталоги должны быть 755 (или эквивалентно по ACL), а владелец/группа — соответствовать пользователю, от которого работает Nginx (часто www-data).
Случайный запрет на /.well-known
Иногда в конфиге есть «жёсткие» блокировки скрытых файлов/каталогов. Например:
location ~ /\. {
deny all;
}
Само по себе правило полезное, но неаккуратные regex или дополнительные ограничения могут задеть /.well-known. Проверьте, нет ли запретов на well-known и нет ли «глобальных» deny/return 403, которые срабатывают раньше нужного location.
Basic auth на весь сайт
Если на HTTP включена авторизация, Let’s Encrypt не сможет пройти. Решение — исключить challenge из auth:
location ^~ /.well-known/acme-challenge/ {
auth_basic off;
try_files $uri =404;
allow all;
}
Почему timeout: firewall, порт 80 и IPv6 AAAA
Открыт ли 80/tcp в firewall
Проверьте правила на сервере (что именно актуально — зависит от вашей системы):
nft list ruleset
ufw status verbose
Не забывайте про внешние политики: в панели провайдера или в облачном firewall входящий 80/tcp тоже должен быть разрешён. Классический кейс: «HTTPS работает, а HTTP нет», потому что открыт только 443.
IPv6: есть AAAA, но сервиса нет
Очень частая история: в DNS добавили AAAA, но:
- Nginx слушает только
listen 80;безlisten [::]:80;; - по IPv6 закрыт 80/tcp;
- IPv6-адрес на сервере не поднят или отличается от DNS.
Если IPv6 пока не нужен, быстрый способ убрать таймаут на валидации — временно удалить AAAA запись. Если IPv6 нужен, доведите конфиг и firewall до рабочего состояния и проверьте отдачу тестового файла по IPv6.
NAT/проброс портов и контейнеры
Если вы за reverse proxy, балансировщиком или публикуете сайт из контейнера, убедитесь, что 80/tcp приходит именно на тот Nginx (или сервис), где настроен location для challenge. Важно: Let’s Encrypt проверяет публичный endpoint, а не ваш внутренний контейнерный адрес.

Редирект на HTTPS: как сделать правильно, чтобы HTTP-01 не ломался
Типовой редирект «всё на HTTPS» выглядит так:
location / {
return 301 https://$host$request_uri;
}
Ломается HTTP-01, когда редирект объявлен так, что перехватывает /.well-known/acme-challenge/. Правило простое: сначала отдельный location под challenge, потом общий редирект.
Ещё одна ловушка — редирект на другой хост (например, на www), где challenge не настроен. Если вы подтверждаете и «голый» домен, и www, держите одинаковую обработку challenge для обоих имён в server_name.
Диагностика по логам Nginx: что смотреть в первую очередь
Во время запуска получения/обновления сертификата откройте логи и смотрите запросы в реальном времени:
tail -f /var/log/nginx/access.log
tail -f /var/log/nginx/error.log
Что проверять по факту:
- Есть ли запросы к
/.well-known/acme-challenge/именно в момент валидации. - Какой код ответа возвращается (200/301/403/404).
- Какой
Hostпришёл (example.com или www) и соответствует ли он вашемуserver_name. - Есть ли в
error.logподсказки про права, отсутствие файлов, переполнение лимитов и т.п.
Если в логах нет никаких запросов, а клиент сообщает timeout, проблема находится до Nginx: DNS, внешний firewall, IPv6/AAAA, маршрутизация.
Чек-лист «починить за 10 минут»
Проверьте, что домен резолвится на правильные IP: A и (если используется) AAAA.
Убедитесь, что Nginx слушает
80и (при наличии AAAA)[::]:80.Откройте входящий 80/tcp на сервере и во внешнем firewall/панели.
Создайте тестовый файл
/.well-known/acme-challenge/pingи добейтесь200по HTTP.Добавьте отдельный
location ^~ /.well-known/acme-challenge/перед редиректом на HTTPS.Сверьте webroot у ACME-клиента с
rootна 80 (или используйтеaliasдля challenge).Во время проверки смотрите
access.logиerror.log, чтобы увидеть реальный код и куда попал запрос.
Когда HTTP-01 лучше не мучить
HTTP-01 объективно неудобен, если порт 80 закрыт по политике, домен живёт за сложным CDN/WAF, много фронтов и нестандартная маршрутизация. Тогда чаще выбирают подтверждение через DNS. Если вы автоматизируете выпуск wildcard-сертификатов или хотите полностью убрать зависимость от 80/tcp, пригодится статья про автоматизацию DNS-01 для wildcard-сертификатов.
Итоги
Ошибки 403/404/timeout при Let’s Encrypt HTTP-01 в Nginx почти всегда чинятся «по слоям»: DNS и доступность 80 порта, затем попадание в правильный server block, затем корректный location для /.well-known/acme-challenge/, и только потом права/ограничения. Как только у вас получается стабильно отдавать тестовый файл по HTTP через curl, выпуск сертификата перестаёт быть «магией certbot» и становится обычной задачей маршрутизации запросов.


