ACME + Nginx: настройка Let’s Encrypt вручную и через Nginx Proxy Manager

Настройка SSL через ACME в Nginx и Nginx Proxy Manager: пошаговый гайд, готовые конфиги, лечение ошибок 404 и certbot. Let's Encrypt без боли

Браузер выдаёт «Ваше соединение не защищено», клиент звонит в панике, а сертификат истёк ровно в пятницу вечером. Знакомо? Или другой классический сценарий: вы поднимаете новый сервис, идёте выпускать 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» — речь идёт именно об этой связке: веб-сервер + протокол + центр сертификации.

Как это работает в двух словах:

  1. Ваш ACME-клиент (certbot, acme.sh и др.) обращается к серверам Let’s Encrypt.
  2. Let’s Encrypt говорит: «Докажи, что ты владеешь доменом example.com».
  3. Клиент проходит challenge проверку владения доменом.
  4. После успешной проверки — выдаётся сертификат на 90 дней.

Почему 90 дней? Это сознательное решение: короткий срок стимулирует автоматизацию и снижает риски при компрометации ключа. Если настроить автообновление — сертификат живёт вечно без вашего участия.


Как работает ACME challenge

Самый распространённый тип проверки — HTTP-01 challenge. Именно его используют в большинстве стандартных сценариев с acme nginx acme-challenge.

Алгоритм такой:

  1. ACME-клиент генерирует уникальный токен (например, evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA).
  2. Создаёт файл по пути: /.well-known/acme-challenge/<TOKEN>
  3. Сообщает Let’s Encrypt: «Иди проверь этот URL».
  4. Let’s Encrypt делает HTTP-запрос к http://example.com/.well-known/acme-challenge/<TOKEN>.
  5. Если файл доступен и содержимое совпадает — домен подтверждён, сертификат выпускается.

Важные нюансы, которые топят большинство новичков:

  • Порт 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, без боли, с ещё большим количеством готовых скриптов.

over_dude
Author: over_dude

Оставайтесь на связи

Рецепты от IT-боли. Без воды, без рекламы, без маркетинговой шелухи.

Подписаться на IT-Аптеку →
Поделитесь:

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Прокрутить вверх