Браузер выдаёт «Ваше соединение не защищено», клиент звонит в панике, а сертификат истёк ровно в пятницу вечером. Знакомо? Или другой классический сценарий: вы поднимаете новый сервис, идёте выпускать SSL — и упираетесь в стену из 404-ошибок на /.well-known/acme-challenge, закрытых портов и загадочных отказов Let’s Encrypt.
В этой статье разберём всё по-честному: что такое ACME-протокол, как настроить acme nginx вручную через certbot, как правильно отдавать well-known acme-challenge nginx, и когда вместо всей этой кухни просто поставить Nginx Proxy Manager и забыть о проблеме за 10 минут.
После прочтения у вас будет: работающий конфиг Nginx с автоматическим обновлением сертификата, понимание типичных ошибок и их лечение, и чёткое понимание — когда брать NPM, а когда руками.
Что такое ACME и Let’s Encrypt
Let’s Encrypt — это бесплатный центр сертификации (Certificate Authority, CA), запущенный в 2016 году некоммерческой организацией ISRG. Их цель была проста и революционна: сделать HTTPS бесплатным и доступным для всех. До этого SSL-сертификат стоил от $10 до $200 в год и требовал ручной возни с CSR-файлами.
ACME (Automatic Certificate Management Environment) — это протокол, который Let’s Encrypt разработали для автоматизации выпуска и обновления сертификатов. Сегодня ACME — стандарт RFC 8555, и его поддерживают уже несколько CA, не только Let’s Encrypt. Когда вы слышите «nginx acme letsencrypt» — речь идёт именно об этой связке: веб-сервер + протокол + центр сертификации.
Как это работает в двух словах:
- Ваш ACME-клиент (certbot, acme.sh и др.) обращается к серверам Let’s Encrypt.
- Let’s Encrypt говорит: «Докажи, что ты владеешь доменом example.com».
- Клиент проходит challenge — проверку владения доменом.
- После успешной проверки — выдаётся сертификат на 90 дней.
Почему 90 дней? Это сознательное решение: короткий срок стимулирует автоматизацию и снижает риски при компрометации ключа. Если настроить автообновление — сертификат живёт вечно без вашего участия.
Как работает ACME challenge
Самый распространённый тип проверки — HTTP-01 challenge. Именно его используют в большинстве стандартных сценариев с acme nginx acme-challenge.
Алгоритм такой:
- ACME-клиент генерирует уникальный токен (например,
evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA). - Создаёт файл по пути:
/.well-known/acme-challenge/<TOKEN> - Сообщает Let’s Encrypt: «Иди проверь этот URL».
- Let’s Encrypt делает HTTP-запрос к
http://example.com/.well-known/acme-challenge/<TOKEN>. - Если файл доступен и содержимое совпадает — домен подтверждён, сертификат выпускается.
Важные нюансы, которые топят большинство новичков:
- Порт 80 должен быть открыт — Let’s Encrypt проверяет именно по HTTP, не HTTPS.
- Нет редиректов на HTTPS во время проверки — точнее, 301 на HTTPS Let’s Encrypt отследит, но это лишний источник проблем.
- DNS должен резолвиться — сервер Let’s Encrypt должен видеть ваш IP по домену.
Существуют и другие типы challenge:
- DNS-01 — добавляете TXT-запись в DNS. Позволяет выпускать wildcard-сертификаты (
*.example.com). Не требует открытого 80 порта. - TLS-ALPN-01 — проверка через специальный TLS-хендшейк на 443 порту. Редко используется.
Для большинства случаев HTTP-01 — ваш рабочий инструмент.
Настройка ACME challenge в Nginx (вручную)
Прежде чем запускать certbot, Nginx должен уметь отдавать файлы challenge. Это то самое well-known acme-challenge nginx, на котором спотыкаются чаще всего.
Шаг 1. Создаём директорию для challenge-файлов
mkdir -p /var/www/acme/.well-known/acme-challenge/ chown -R www-data:www-data /var/www/acme/
Шаг 2. Добавляем location в конфиг Nginx
Открываем конфиг вашего сайта (обычно /etc/nginx/sites-available/example.com) и добавляем блок:
server {
listen 80;
server_name example.com www.example.com;
# Критически важный блок для ACME challenge
location /.well-known/acme-challenge/ {
root /var/www/acme;
allow all;
}
# Остальной трафик можно редиректить на HTTPS
location / {
return 301 https://$host$request_uri;
}
}
Важно: блок location /.well-known/acme-challenge/ должен стоять до любых редиректов на HTTPS. Иначе Let’s Encrypt получит 301 вместо файла и проверка провалится.
Шаг 3. Проверяем конфиг и перезагружаем Nginx
nginx -t && systemctl reload nginx
Шаг 4. Проверяем доступность вручную
Создаём тестовый файл и проверяем, что он отдаётся:
echo "test" > /var/www/acme/.well-known/acme-challenge/test.txt curl http://example.com/.well-known/acme-challenge/test.txt # Должно вернуть: test rm /var/www/acme/.well-known/acme-challenge/test.txt
Если curl вернул «test» — всё готово, можно выпускать сертификат. Если получили 404 — смотрите раздел с ошибками ниже.
💡 Лайфхак: можно вынести блок challenge в отдельный файл /etc/nginx/snippets/acme-challenge.conf и инклудить его во все серверные блоки — тогда не придётся копипастить.
# /etc/nginx/snippets/acme-challenge.conf
location /.well-known/acme-challenge/ {
root /var/www/acme;
allow all;
}
# В конфиге сайта
server {
listen 80;
server_name example.com;
include snippets/acme-challenge.conf;
...
}
Получение сертификата Let’s Encrypt через Certbot
Установка Certbot
# Ubuntu/Debian apt update && apt install certbot -y # CentOS/RHEL yum install certbot -y # Или через snap (рекомендуется EFF) snap install --classic certbot ln -s /snap/bin/certbot /usr/bin/certbot
Выпуск сертификата (webroot метод)
Webroot — это метод, при котором certbot сам кладёт файлы в директорию, а Nginx их отдаёт. Именно тот случай, который мы настроили выше:
certbot certonly \ --webroot \ -w /var/www/acme \ -d example.com \ -d www.example.com \ --email admin@example.com \ --agree-tos \ --non-interactive
Флаги:
certonly— только выпустить сертификат, не трогать конфиги Nginx.--webroot— использовать webroot challenge.-w— путь к webroot директории.-d— домен (можно указывать несколько раз для SAN-сертификата).
После успешного выпуска сертификаты лежат тут:
/etc/letsencrypt/live/example.com/ ├── cert.pem # Сертификат домена ├── chain.pem # Промежуточная цепочка ├── fullchain.pem # cert.pem + chain.pem (нужен для Nginx) └── privkey.pem # Приватный ключ
Настройка SSL в Nginx
Теперь подключаем сертификат к Nginx. Полный конфиг с правильными настройками безопасности:
# HTTP -> HTTPS редирект
server {
listen 80;
server_name example.com www.example.com;
include snippets/acme-challenge.conf;
location / {
return 301 https://$host$request_uri;
}
}
# HTTPS сервер
server {
listen 443 ssl http2;
server_name example.com www.example.com;
# Сертификаты Let's Encrypt
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Рекомендуемые параметры SSL (от Mozilla)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# HSTS (раскомментировать после того, как убедились что всё работает)
# add_header Strict-Transport-Security "max-age=63072000" always;
# Оптимизация
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
root /var/www/example.com;
index index.html index.php;
location / {
try_files $uri $uri/ =404;
}
}
Проверяем и перезагружаем:
nginx -t && systemctl reload nginx
📌 Рекомендую использовать Mozilla SSL Configuration Generator — там можно выбрать уровень совместимости (Modern / Intermediate / Old) и получить готовый конфиг под вашу версию Nginx.
Автообновление сертификатов
Сертификаты Let’s Encrypt живут 90 дней. Certbot рекомендует обновлять, когда до истечения осталось менее 30 дней. Всё это нужно автоматизировать — иначе однажды в самый неподходящий момент сертификат протухнет.
Проверка автообновления (dry-run)
certbot renew --dry-run
Если команда прошла без ошибок — всё настроено правильно.
Настройка cron
crontab -e
Добавляем строку:
0 3 * * * certbot renew --quiet --post-hook "systemctl reload nginx"
Расшифровка:
0 3 * * *— каждый день в 3:00 ночи.--quiet— не шуметь, если обновлять нечего.--post-hook— перезагрузить Nginx только если сертификат был обновлён.
Альтернатива: systemd timer
В современных системах certbot при установке через snap или пакетный менеджер уже создаёт systemd-таймер. Проверить:
systemctl status certbot.timer systemctl list-timers | grep certbot
Если таймер есть и активен — ручной cron не нужен.
Проверка статуса сертификатов
certbot certificates
Покажет все сертификаты, их домены и дату истечения.
Nginx Proxy Manager (NPM): простой способ получить SSL
Теперь поговорим о том, что экономит часы жизни при работе с Docker и self-hosted сервисами. Nginx Proxy Manager (часто сокращают до NPM, но не путать с Node Package Manager) — это веб-интерфейс поверх Nginx, который берёт на себя всю рутину: проксирование, SSL, редиректы, базовую аутентификацию.
Когда NPM — ваш лучший друг:
- У вас несколько Docker-контейнеров и вы устали писать Nginx-конфиги руками.
- Нужно быстро выдать SSL нескольким сервисам без погружения в certbot.
- Команда не очень дружит с Linux CLI.
- Self-hosted стек: Nextcloud, Gitea, Portainer, Vaultwarden — и всё это нужно завернуть в HTTPS.
NPM написан поверх nginx и certbot, но прячет всю сложность за красивый UI. Под капотом — та же nginx acme letsencrypt связка, только кнопочная.
Установка Nginx Proxy Manager через Docker
Самый правильный способ — docker-compose. Создаём файл docker-compose.yml:
version: '3.8'
services:
npm:
image: jc21/nginx-proxy-manager:latest
container_name: nginx-proxy-manager
restart: unless-stopped
ports:
- "80:80" # HTTP (нужен для ACME challenge!)
- "443:443" # HTTPS
- "81:81" # Web UI
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
environment:
DB_SQLITE_FILE: "/data/database.sqlite"
Запускаем:
docker-compose up -d
Ждём 30–60 секунд и открываем http://YOUR_SERVER_IP:81.
Данные по умолчанию при первом входе:
- Email:
admin@example.com - Password:
changeme
Сразу же поменяйте пароль! Это не шутка — порт 81 с дефолтными кредами индексируется Shodan.
⚠️ Важно: порты 80 и 443 должны быть свободны на хосте. Если у вас уже запущен Nginx или Apache — сначала остановите их.
Получение SSL сертификата в Nginx Proxy Manager
Это та часть, где NPM реально сияет. Весь процесс занимает 2–3 минуты.
Шаг 1. Добавить Proxy Host
Hosts → Proxy Hosts → Add Proxy Host.
- Domain Names: вводите ваш домен (example.com).
- Scheme: http (если бэкенд не SSL) или https.
- Forward Hostname / IP: IP или имя Docker-контейнера.
- Forward Port: порт вашего сервиса.
- Включите Block Common Exploits — это добавит базовые правила безопасности.
Шаг 2. Включить SSL
Переходим на вкладку SSL.
- SSL Certificate: выбираем Request a new SSL Certificate.
- Выбираем Let’s Encrypt.
- Вводим email для уведомлений об истечении.
- Включаем Force SSL — принудительный HTTPS.
- Включаем HTTP/2 Support — быстрее для браузеров.
Шаг 3. Сохранить
Нажимаем Save. NPM сам запустит ACME challenge, получит сертификат и настроит Nginx. Зелёный статус — готово.
NPM сам следит за обновлением сертификатов. По умолчанию проверяет каждые 24 часа и обновляет за 30 дней до истечения.
Типичные ошибки ACME + Nginx и как их лечить
Это самый важный раздел для тех, кто уже застрял.
Ошибка 1: 404 на /.well-known/acme-challenge
Симптом: certbot выдаёт ошибку вроде:
Detail: 404 :: Not Found :: The requested resource was not found.
Причины и лечение:
# 1. Проверяем, что директория существует ls -la /var/www/acme/.well-known/acme-challenge/ # 2. Проверяем права доступа chown -R www-data:www-data /var/www/acme/ # 3. Тестируем вручную echo "test" > /var/www/acme/.well-known/acme-challenge/testfile curl -v http://example.com/.well-known/acme-challenge/testfile # 4. Проверяем конфиг Nginx — location должен быть ДО редиректа на HTTPS nginx -T | grep -A5 "well-known"
Частая ловушка: в конфиге есть глобальный return 301 https://... в самом начале server-блока, который перехватывает все запросы, включая challenge.
Ошибка 2: Порт 80 закрыт или недоступен
# Проверяем, слушает ли Nginx на 80 ss -tlnp | grep :80 # Проверяем firewall ufw status iptables -L -n | grep 80 # Проверяем доступность снаружи (с другого хоста) curl -v http://example.com/ # Или проверяем через онлайн-инструмент # https://www.yougetsignal.com/tools/open-ports/
Ошибка 3: Cloudflare мешает проверке
Если домен за Cloudflare в режиме проксирования (оранжевое облачко) — Let’s Encrypt видит IP Cloudflare, а не ваш сервер. Challenge-файл должен лежать на вашем сервере.
Решения:
- Временно переключить DNS-запись в режим DNS-only (серое облачко) на время выпуска сертификата.
- Использовать DNS-01 challenge — он работает через API Cloudflare и не требует открытого 80 порта. Certbot поддерживает через плагин
certbot-dns-cloudflare. - Использовать Origin Certificate от самого Cloudflare (бесплатно, но только для трафика через CF).
# Установка плагина для Cloudflare DNS challenge pip install certbot-dns-cloudflare # Создаём файл с API токеном cat > /root/.cloudflare.ini << EOF dns_cloudflare_api_token = ВАШ_API_ТОКЕН EOF chmod 600 /root/.cloudflare.ini # Выпуск сертификата через DNS-01 certbot certonly \ --dns-cloudflare \ --dns-cloudflare-credentials /root/.cloudflare.ini \ -d example.com \ -d "*.example.com"
Ошибка 4: Неправильный root в location
# Неправильно:
location /.well-known/acme-challenge/ {
root /var/www/html; # challenge-файлов здесь нет!
}
# Правильно:
location /.well-known/acme-challenge/ {
root /var/www/acme; # именно сюда certbot кладёт файлы
allow all;
}
И убедитесь, что в команде certbot аргумент -w указывает на ту же директорию.
Ошибка 5: Rate Limit от Let’s Encrypt
Let’s Encrypt имеет лимиты: 5 сертификатов на домен в неделю. Если вы упорно дебажили и исчерпали лимит — используйте staging окружение для тестов:
certbot certonly \ --staging \ --webroot \ -w /var/www/acme \ -d example.com
Staging выдаёт «ненастоящие» сертификаты (браузеры их не доверяют), но лимиты там намного выше.
Когда использовать NPM, а когда чистый Nginx
Честное сравнение без маркетинга:
| Критерий | Чистый Nginx + Certbot | Nginx Proxy Manager |
|---|---|---|
| Сложность настройки | Средняя — нужно знать Nginx | Низкая — всё через UI |
| Гибкость конфигурации | Максимальная | Ограничена UI + advanced config |
| Продакшн-сервер | ✅ Отличный выбор | ⚠️ Возможно, но с оговорками |
| Docker/self-hosted | ⚠️ Требует настройки | ✅ Создан для этого |
| Wildcard SSL | ✅ Через DNS-01 плагины | ✅ Встроено в UI |
| Несколько доменов/сервисов | Много файлов конфигурации | Легко через UI |
| Автообновление SSL | Systemd timer / cron | Встроено и автоматически |
| Мониторинг и логи | Нативные Nginx-логи | UI + логи в интерфейсе |
| Version control (Git) | ✅ Конфиги как код | ❌ Хранится в SQLite |
| Когда использовать | Production, тонкая настройка, IaC | Быстрый старт, homelabs, SMB |
Мой вывод: для серьёзного продакшна с Infrastructure as Code — чистый Nginx. Для домашнего сервера, стартапа с десятком Docker-сервисов или когда коллеги не хотят учить Nginx — NPM сэкономит вам часы.
Профилактика: лучшие практики SSL в Nginx
- Мониторинг сертификатов. Настройте уведомление за 14–30 дней до истечения. Можно через UptimeRobot или скрипт на основе
certbot certificates. - Тест SSL-конфигурации. После настройки проверьте через SSL Labs — добивайтесь оценки A или A+.
- HSTS после стабилизации. Включайте Strict-Transport-Security только когда уверены, что HTTP больше не нужен — отключить потом непросто.
- Резервируйте /etc/letsencrypt. При переносе сервера проще перевыпустить сертификат, но иметь backup директории не помешает.
- Тестируйте обновление заранее. Запускайте
certbot renew --dry-runпосле любых изменений в конфиге Nginx.
FAQ: часто задаваемые вопросы
Почему не работает ACME challenge?
90% случаев — одна из четырёх причин: порт 80 закрыт файрволом, редирект на HTTPS перехватывает challenge-запрос, неправильный root в location-блоке, или Cloudflare в режиме прокси. Проверяйте в таком порядке: доступность порта снаружи → конфиг Nginx → права на директорию.
Как проверить доступность /.well-known?
# Со своей машины: curl -v http://example.com/.well-known/acme-challenge/test # Проверка DNS резолвинга: dig example.com +short # Проверка через онлайн-инструменты (если нет доступа снаружи): # https://check-host.net/
Можно ли использовать HTTPS-only (без порта 80)?
Для HTTP-01 challenge — нет, порт 80 нужен. Но можно использовать DNS-01 challenge — он полностью работает без HTTP. Также это единственный способ получить wildcard-сертификаты.
Можно ли использовать Let’s Encrypt с локальными/внутренними доменами?
Нет. Let’s Encrypt выдаёт сертификаты только для публично резолвящихся доменов. Для внутренних сетей используйте Step CA или самоподписанные сертификаты с корневым CA.
Как выпустить wildcard сертификат?
# Только через DNS-01 challenge # Пример с Cloudflare: certbot certonly \ --dns-cloudflare \ --dns-cloudflare-credentials /root/.cloudflare.ini \ -d "*.example.com" \ -d "example.com"
В certbot написано «Congratulations», но сайт всё равно без SSL — почему?
Certbot с флагом certonly только выпускает сертификат, но не меняет конфиг Nginx. Нужно вручную прописать пути к сертификату в конфиге и перезагрузить Nginx. Либо используйте certbot --nginx (он сам правит конфиг, но иногда слишком агрессивно).
Заключение
ACME — это стандарт де-факто для автоматического управления SSL-сертификатами. Let’s Encrypt сделал HTTPS бесплатным и доступным для всех, от хоббийного сервера до энтерпрайз-инфраструктуры.
Что мы разобрали в этой статье:
- Как работает HTTP-01 challenge и почему он не работает — если что-то пошло не так.
- Правильный конфиг Nginx для отдачи challenge-файлов (
well-known acme-challenge nginx). - Полный цикл: выпуск → настройка SSL в Nginx → автообновление через cron.
- Как поднять Nginx Proxy Manager за 10 минут и получить SSL через красивый UI.
- Лечение типичных болячек: 404 на challenge, Cloudflare, rate limits.
Если настраиваете SSL первый раз — начните с NPM, поймёте принципы. Потом переходите на чистый Nginx — будете контролировать каждый байт конфигурации.
Возникли проблемы с настройкой? Пишите в комментарии — разберём вашу конкретную ситуацию. Ошибки из логов приветствуются.
В следующей статье разберём, как автоматизировать выпуск SSL для десятков доменов через acme.sh и DNS API — без certbot, без боли, с ещё большим количеством готовых скриптов.
Оставайтесь на связи
Рецепты от IT-боли. Без воды, без рекламы, без маркетинговой шелухи.
Подписаться на IT-Аптеку →



