АйТи Фреш
Главная / Статьи / Серверы и хостинг
Серверы и хостинг

Как выпустить сертификат Let's Encrypt в mailcow при закрытом порте 80

Автор: Семёнов Евгений Сергеевич, директор ООО «АйТи-Фреш» · · ~18 мин чтения
Как выпустить сертификат Let's Encrypt в mailcow при закрытом порте 80
Иллюстрация к статье «Как выпустить сертификат Let's Encrypt в mailcow при закрытом порте 80».

Если входящий TCP/80 закрыт, больше не нужно прикручивать к mailcow отдельный certbot или acme.sh. Начиная с версии 2026-03, выпущенной 10 марта 2026 года, mailcow умеет проходить DNS-01 штатно. Я, Семёнов Евгений Сергеевич, покажу вариант, который применяю сам: обновляем mailcow, выдаём узкий API-токен для DNS, включаем встроенный ACME-контейнер и обязательно проверяем сертификат на HTTPS, SMTP и IMAP.

Короткий ответ: штатный DNS-01 уже есть

Старые инструкции с отдельным ACME-клиентом не были ошибочными. Они просто описывали mailcow до марта 2026 года. Тогда встроенный acme-mailcow рассчитывал прежде всего на HTTP-01: центр сертификации обращался к специальному файлу по TCP/80. Если оператор связи, межсетевой экран или архитектура с внешним балансировщиком не пропускали этот запрос, администратор ставил certbot, lego либо отдельный acme.sh, копировал сертификаты в data/assets/ssl и добавлял post-hook для перезагрузки сервисов. Работало. Но получалась вторая система автоматизации, о которой через полгода неизбежно забывали.

В mailcow 2026-03 появился встроенный DNS-01. Контейнер создаёт TXT-запись _acme-challenge через API DNS-провайдера, Let's Encrypt проверяет её в публичной DNS и выдаёт сертификат. Входящее соединение на порт 80 при этом не используется. На 8 сентября 2026 года актуальная стабильная редакция — 2026-07b, и я выбираю именно обновление на поддерживаемую stable-ветку, а не остановку на первом релизе 2026-03. После мартовского релиза уже выходили исправления ACME и обновления безопасности.

Позиция у меня простая: если сертификаты выпускает сам mailcow, пусть он же управляет их продлением и перезагрузкой Postfix, Dovecot и Nginx. Отдельный клиент оставляю только там, где сертификатами централизованно управляет внешний reverse proxy или корпоративная PKI. Для обычного почтового узла самостоятельный certbot — теперь лишняя движущаяся часть. Он добавляет секреты, таймер, сценарий копирования и ещё одну точку отказа, но не даёт заметного выигрыша.

Не путайте закрытый порт 80 с полностью закрытым почтовым сервером. Для нормальной работы почты по-прежнему нужны предусмотренные вашей схемой SMTP-, Submission-, IMAP- и HTTPS-порты. DNS-01 решает только задачу подтверждения доменов для сертификата.
Памятка: Короткий ответ: штатный DNS-01 уже есть — схема
Памятка: Короткий ответ: штатный DNS-01 уже есть. Открыть схему в полном размере

Что проверяет DNS-01 и где находятся ограничения

При HTTP-01 Let's Encrypt должен получить файл именно через порт 80. Переназначить проверку на произвольный внешний порт нельзя. При DNS-01 доказательством владения становится TXT-запись под именем _acme-challenge соответствующего домена. Поэтому сервер может находиться за NAT, порт 80 можно фильтровать на периметре, а веб-интерфейс — публиковать только через VPN или внешний прокси. Наличие публичного A- или AAAA-адреса у проверяемого имени само по себе для DNS-01 не является способом проверки.

Есть важное ограничение mailcow: режим выбирается для экземпляра целиком. Если задано ACME_DNS_CHALLENGE=y, все имена сертификата проходят DNS-01; совместить HTTP-01 для одного домена и DNS-01 для другого в штатной конфигурации нельзя. Переменная SKIP_HTTP_VERIFICATION в таком режиме игнорируется, потому что HTTP-проверка уже не выполняется. Это регулярно упускают: администратор добавляет три строки в mailcow.conf, оставляет часть доменов у другого DNS-провайдера и удивляется, почему единый заказ сертификата не проходит.

До изменения я выписываю все имена, которые mailcow собирается включить в сертификат: MAILCOW_HOSTNAME, явно заданные ADDITIONAL_SAN, а также подходящие autodiscover, autoconfig и при соответствующей настройке mta-sts для почтовых доменов. Затем проверяю, что выбранный API-токен способен создать TXT-запись во всех нужных DNS-зонах. Если зоны обслуживаются разными провайдерами, один глобально указанный плагин ACME_DNS_PROVIDER задачу обычно не закроет. Я переношу зоны к одному управляемому провайдеру или делегирую _acme-challenge в отдельную зону. Это уже архитектурное решение, а не ещё один флаг mailcow.

Самый неприятный сценарий — не явная ошибка при первом запуске, а невозможность очередного продления через несколько недель. Поэтому успешное создание TXT-записи и выданный сертификат нужно проверить сразу, а затем контролировать срок действия мониторингом.
Как выпустить сертификат Let's Encrypt в mailcow при закрытом порте 80 — схема
Схема к статье. Открыть схему в полном размере

Подготовка версии и API-токена

Перед настройкой я обновляю mailcow штатным update.sh. Сначала делаю резервную копию и читаю изменения между установленной и текущей stable-версией. Обновление почтового комплекса — не место для импровизации: версии до 2026-03 вообще не знают новых переменных, а первоначальные 2026-03 и 2026-03a получили последующие исправления. Для предварительной проверки и обновления из каталога установки использую:

cd /opt/mailcow-dockerized
./update.sh --check
./update.sh

После запуска проверяю версию в интерфейсе и состояние контейнеров командой docker compose ps. Если сервер давно не обновлялся, сначала отрабатываю переход на копии или тестовом узле.

По ресурсам DNS-01 почти ничего не меняет, но сам mailcow остаётся тяжёлым групповым решением. Официальный минимум — 1 ГГц CPU, 6 ГиБ RAM плюс 1 ГиБ swap и 20 ГиБ диска без учёта почты. Для компании с 15 телефонами на ActiveSync и примерно 50 одновременными IMAP-подключениями документация рекомендует планировать 16 ГиБ RAM. Я не пытаюсь экономить здесь пару гигабайт: нехватка памяти ударит по SOGo, ClamAV и индексации, а администратор по ошибке начнёт связывать эти симптомы с переходом на DNS-01.

Для примера беру Cloudflare, потому что синтаксис хорошо документирован. Создаю не Global API Key, а отдельный API Token по шаблону редактирования DNS. Разрешение — Zone > DNS > Edit, область — только конкретная зона kardan-opt.example. Идентификатор аккаунта сохраняю отдельно. Токен не отправляю в мессенджер, не кладу в систему резервного копирования без шифрования и не вставляю в заявку технической поддержки. Компрометация DNS-токена опаснее, чем многие думают: злоумышленник сможет влиять не только на выпуск сертификата, если права выданы на весь аккаунт.

API-секрет хранится на том же узле, который обслуживает почту. Риск реальный, но не повод возвращаться к ручному выпуску: используйте токен с минимальными правами, ограничьте доступ к серверу и включите резервное копирование конфигурации с шифрованием.
Порядок действий: Подготовка версии и API-токена — схема
Порядок действий: Подготовка версии и API-токена. Открыть схему в полном размере

Рабочая конфигурация mailcow

В mailcow.conf включаю DNS-проверку, выбираю плагин Cloudflare и задаю адрес ACME-аккаунта. Адрес должен быть действующим: на него могут приходить уведомления, связанные с учётной записью центра сертификации. В нашем условном примере базовая часть файла выглядит так:

MAILCOW_HOSTNAME=mail.kardan-opt.example
ADDITIONAL_SAN=imap.kardan-opt.example,smtp.kardan-opt.example

ACME_DNS_CHALLENGE=y
ACME_DNS_PROVIDER=dns_cf
ACME_ACCOUNT_EMAIL=postmaster@kardan-opt.example

Не добавляйте wildcard просто потому, что DNS-01 его поддерживает. Если пользователи обращаются только к перечисленным именам, обычный SAN-сертификат понятнее и ограничивает область возможной ошибки.

Реквизиты провайдера размещаю не в mailcow.conf, а в data/conf/acme/dns-01.conf. Для Cloudflare официальный пример использует именно CF_Token и CF_Account_ID:

CF_Token="значение_выданного_API_токена"
CF_Account_ID="идентификатор_аккаунта_Cloudflare"

Файл не должен быть доступен обычным пользователям хоста. После создания проверяю владельца и ставлю режим 600 командой chmod 600 data/conf/acme/dns-01.conf. Это моя эксплуатационная гигиена: mailcow должен прочитать секрет, но локальным пользователям он не нужен.

Применяю конфигурацию штатным Compose, затем смотрю журнал ACME-контейнера:

cd /opt/mailcow-dockerized
docker compose up -d
docker compose logs --tail=200 -f acme-mailcow

Если действующий сертификат ещё не требует обновления, одного up -d может быть недостаточно для наглядного теста. Тогда инициирую принудительный выпуск поддерживаемым способом:

cd /opt/mailcow-dockerized
touch data/assets/ssl/force_renew
docker compose restart acme-mailcow
docker compose logs --tail=200 -f acme-mailcow

Файл force_renew контейнер удалит автоматически. Многократно повторять эту операцию нельзя: лишние попытки приближают ограничения Let's Encrypt и засоряют диагностику.

Не запускайте параллельно старый certbot или cron-сценарий копирования сертификатов. Два клиента начнут перезаписывать одни и те же файлы, а при расследовании вы не поймёте, какой сертификат реально загрузили сервисы.

Если mailcow старше 2026-03 или сертификат выпускает внешний клиент

Штатный DNS-01 доступен не всегда: узел может сидеть на старой версии, которую пока нельзя обновить, или сертификат уже выпускает корпоративный acme.sh на отдельной машине. Тогда встроенный ACME-клиент нужно выключить, иначе он будет пытаться пройти HTTP-01 через закрытый порт 80 и засорять журнал. В mailcow.conf задаю SKIP_LETS_ENCRYPT=y и применяю изменение через docker compose up -d. После этого mailcow перестаёт заказывать сертификаты и использует те файлы, которые лежат в data/assets/ssl.

По документации Advanced SSL сервисы mailcow читают сертификат из data/assets/ssl/cert.pem, а закрытый ключ — из data/assets/ssl/key.pem. Файлы нужно именно копировать, а не делать символические ссылки: внутри контейнеров путь за пределами смонтированного каталога не существует. В cert.pem я кладу полную цепочку (fullchain), иначе часть почтовых клиентов и сторонних MTA будет ругаться на неполную цепочку. Пример для acme.sh с тем же плагином Cloudflare:

export CF_Token="значение_API_токена"
export CF_Account_ID="идентификатор_аккаунта"
acme.sh --issue --dns dns_cf -d mail.kardan-opt.example -d autodiscover.kardan-opt.example -d autoconfig.kardan-opt.example
acme.sh --install-cert -d mail.kardan-opt.example \
  --fullchain-file /opt/mailcow-dockerized/data/assets/ssl/cert.pem \
  --key-file /opt/mailcow-dockerized/data/assets/ssl/key.pem \
  --reloadcmd "/usr/local/sbin/mailcow-cert-reload.sh"

Для certbot аналог — пакет certbot-dns-cloudflare и ключ --dns-cloudflare-credentials с файлом прав 600; копирование в data/assets/ssl тогда делает deploy-hook.

Самое важное в этой схеме — перезапуск сервисов после замены файлов. Именно на нём ломалась автоматизация в разобранном выше кейсе. Документация mailcow предписывает перезапустить Postfix, Nginx и Dovecot; сценарий mailcow-cert-reload.sh у меня состоит ровно из этих команд:

#!/bin/sh
docker restart $(docker ps -qaf name=postfix-mailcow)
docker restart $(docker ps -qaf name=nginx-mailcow)
docker restart $(docker ps -qaf name=dovecot-mailcow)

Перезапуск Dovecot рвёт активные IMAP-сессии, клиенты переподключатся сами, но запускать хук лучше ночью, а не в разгар приёма заявок. После первого прогона обязательно проверяю сертификат на 443, 587 и 993 так же, как описано ниже. Когда узел всё-таки обновят до 2026-03 или новее, возвращаю SKIP_LETS_ENCRYPT=n, включаю DNS-01 и только после успешного выпуска удаляю внешний клиент.

Внешний клиент без хука перезапуска хуже, чем ручной выпуск: он создаёт иллюзию автоматизации. Веб-интерфейс покажет новый сертификат, а IMAP и SMTP будут отдавать старый до первого случайного рестарта контейнеров.

Практика: «Кардан-Опт», оптовый склад и 37 рабочих мест

Разберу обезличенный проект; название «Кардан-Опт» условное. Оптовый склад автозапчастей: центральный склад, офис продаж и пункт выдачи, 37 рабочих мест, 44 почтовых ящика (включая общие zakaz@ и sklad@), 19 смартфонов с ActiveSync у менеджеров и экспедиторов и примерно 1900 входящих и исходящих писем в рабочий день — заявки клиентов, прайсы поставщиков, счета. mailcow работал на виртуальной машине Ubuntu 24.04 LTS: 6 vCPU, 16 ГиБ RAM, 200 ГиБ SSD. Узел имел один публичный IPv4. Порты 25, 465, 587, 993 и 443 были опубликованы, а TCP/80 блокировался правилами площадки. Открывать его заказчик не хотел, и для этой схемы это было вполне разумно.

До переделки там оставался mailcow 2025-12 и внешний ACME-клиент. Раз в два месяца администратор вручную проверял сертификат, потому что post-hook после копирования иногда не перезагружал Dovecot. Веб-интерфейс уже показывал новый сертификат, а IMAP продолжал отдавать старый. Outlook предупреждал пользователей, телефоны просили подтвердить недоверенный сервер, и служба поддержки получала шесть-восемь одинаковых обращений за утро — в основном от менеджеров, которые с утра открывают заявки с телефона. Это типичный результат половинчатой автоматизации: файл обновили, жизненный цикл сертификата целиком — нет.

В апреле, в согласованное окно в субботу, когда склад не отгружает, мы сделали резервную копию, обновили систему до mailcow 2026-03b и удалили старый systemd timer только после фиксации его команд и путей. Позже узел штатно обновили до 2026-07b; настройки DNS-01 при этом переносить не пришлось. В сертификат вошли mail.kardan-opt.example, smtp.kardan-opt.example, imap.kardan-opt.example, autodiscover.kardan-opt.example и autoconfig.kardan-opt.example. Все имена относились к одной Cloudflare-зоне. Первый запуск завершился ошибкой API: токен создали с правом просмотра DNS вместо редактирования. Это повторяется из раза в раз — человек видит слово DNS и не проверяет действие. После выпуска токена с Zone > DNS > Edit контейнер создал TXT-записи, дождался их доступности и получил сертификат.

От начала успешной попытки до установки сертификата прошло около трёх минут; конкретное время зависит от DNS-провайдера и распространения записей. Мы проверили HTTPS, SMTP Submission с STARTTLS и IMAPS, затем оставили систему под наблюдением. Через цикл автоматического продления сертификат обновился без открытия порта 80 и без ручного копирования файлов. Старый ACME-клиент, его таймер и скрипт перезагрузки удалили после контрольного периода. С апреля сертификат продлевался уже несколько раз, и обращений из-за него не было ни одного. Для меня это и есть критерий результата: не красивый лог в момент внедрения, а отсутствие ручной процедуры при очередном продлении.

Название организации и доменные имена условные, цифры подогнаны под масштаб типичного оптового склада. Это не публикация сведений о конкретном клиенте.
Цифры и версии: Практика: «Кардан-Опт», оптовый склад и 37 рабочих мест — схема
Цифры и версии: Практика: «Кардан-Опт», оптовый склад и 37 рабочих мест. Открыть схему в полном размере

Как проверить результат и не пропустить следующее продление

Зелёной строки в логе недостаточно. Сначала смотрю, какой сертификат реально отдаёт HTTPS:

openssl s_client -connect mail.kardan-opt.example:443 \
  -servername mail.kardan-opt.example </dev/null 2>/dev/null |
openssl x509 -noout -subject -issuer -dates -ext subjectAltName

Затем тем же принципом проверяю SMTP Submission и IMAPS:

openssl s_client -starttls smtp -connect mail.kardan-opt.example:587 \
  -servername mail.kardan-opt.example </dev/null 2>/dev/null |
openssl x509 -noout -issuer -dates -ext subjectAltName

openssl s_client -connect mail.kardan-opt.example:993 \
  -servername mail.kardan-opt.example </dev/null 2>/dev/null |
openssl x509 -noout -issuer -dates -ext subjectAltName

Сравниваю notAfter и SAN во всех трёх ответах. Разные даты обычно означают, что один из сервисов не перечитал сертификат или трафик пришёл на другой узел.

Если выпуск не проходит, начинаю не с переустановки контейнера, а с журнала acme-mailcow. Ошибка авторизации API указывает на токен, аккаунт или область зоны. Сообщение о невозможности найти TXT чаще означает неверный авторитетный DNS, медленное распространение либо split DNS. Снаружи проверяю запись командами dig TXT _acme-challenge.mail.kardan-opt.example и dig +trace _acme-challenge.mail.kardan-opt.example. Локальный резолвер может уже видеть запись, а авторитетные серверы — отвечать не все. У anycast-провайдеров наблюдение из одной точки также не гарантирует, что тот же ответ увидит Let's Encrypt.

После внедрения ставлю мониторинг срока действия минимум на 443, 587 и 993 с предупреждениями заранее. Отдельно контролирую завершение acme-mailcow без ошибок и документирую ротацию API-токена. Проверку после ротации выполняю принудительным выпуском один раз в согласованное окно. На временные TXT-записи обычно можно не тратить отдельный проект мониторинга: клиент создаёт и удаляет их сам. А вот забытый истекающий сертификат игнорировать нельзя — он одновременно затронет веб-клиент, почтовые программы и мобильные устройства.

Отдельно учитываю сроки жизни сертификатов. Сейчас стандартный профиль Let's Encrypt classic выдаёт сертификаты на 90 дней, но сокращение уже официально объявлено: с 13 мая 2026 года на 45 дней перешёл профиль tlsserver, который включается только по выбору клиента; 10 февраля 2027 года classic перейдёт на 64 дня с 10-дневным повторным использованием авторизаций, а 16 февраля 2028 года — на 45 дней с повторным использованием всего 7 часов. Для DNS-01 последнее означает, что практически каждое продление будет заново создавать TXT-записи через API. Поэтому токен с истёкшим сроком или отозванными правами всплывёт быстро, а порог мониторинга я ставлю не «за 7 дней», а хотя бы за 14–20 дней до окончания действия.

Итоговый выбор такой: для mailcow 2026-03 и новее при закрытом порте 80 я использую штатный DNS-01. Это меньше кода, меньше ручных перезагрузок и один понятный владелец жизненного цикла сертификата. Спорное место одно — DNS-токен хранится на почтовом сервере. Я принимаю этот риск при узких правах токена и нормальной защите узла. Открывать порт 80 только ради ACME или сохранять отдельный клиент теперь считаю менее аккуратным решением.

Не оценивайте успех только по наличию файлов в `data/assets/ssl`. Клиент пользователя видит сертификат, загруженный конкретным сетевым сервисом, поэтому проверять нужно каждый опубликованный протокол.

Частые вопросы

Нужно ли открывать входящий порт 80 для штатного DNS-01 в mailcow?

Нет. DNS-01 подтверждает владение доменом через TXT-запись в публичной DNS. Однако серверу нужен исходящий доступ к Let's Encrypt и API DNS-провайдера.

С какой версии mailcow доступен встроенный DNS-01?

С версии 2026-03, выпущенной 10 марта 2026 года. Для эксплуатации я рекомендую актуальную стабильную ревизию, потому что после первого релиза выходили исправления ACME и безопасности.

Можно ли одному домену назначить HTTP-01, а другому DNS-01?

Не в штатной конфигурации одного экземпляра mailcow. При `ACME_DNS_CHALLENGE=y` DNS-01 применяется ко всем доменам экземпляра.

Нужен ли теперь отдельный certbot или acme.sh?

Обычно нет: mailcow сам использует ACME-компоненты, получает сертификат и обслуживает его жизненный цикл. Внешний клиент оправдан, если сертификатами централизованно управляет reverse proxy или отдельная PKI.

Куда класть сертификат, если его выпускает acme.sh или certbot?

В `data/assets/ssl/cert.pem` (полная цепочка) и `data/assets/ssl/key.pem` каталога mailcow, копированием, а не символической ссылкой. Встроенный клиент отключается `SKIP_LETS_ENCRYPT=y`, а после замены файлов нужно перезапустить контейнеры postfix-mailcow, nginx-mailcow и dovecot-mailcow.

Какие параметры нужны для Cloudflare?

В `mailcow.conf` задаются `ACME_DNS_CHALLENGE=y`, `ACME_DNS_PROVIDER=dns_cf` и `ACME_ACCOUNT_EMAIL`. В `data/conf/acme/dns-01.conf` размещаются `CF_Token` и `CF_Account_ID`.

Почему TXT виден через dig, но Let's Encrypt не принимает проверку?

Проверьте ответы всех авторитетных NS, а не только локального кеширующего резолвера. Причиной бывают задержка распространения, split DNS, неверная делегация или разные ответы anycast-узлов.

Сертификаты Let's Encrypt станут короче — нужно ли что-то менять?

По официальному объявлению Let's Encrypt профиль classic перейдёт на 64 дня 10 февраля 2027 года и на 45 дней 16 февраля 2028 года. При работающем автоматическом продлении менять конфигурацию не требуется, но стоит проверить мониторинг срока действия и срок жизни DNS API-токена.

Столкнулись с похожей задачей? Обращайтесь — решим

Если у вас происходит что-то из описанного в этой статье — или любая другая проблема с ИТ-инфраструктурой, — обращайтесь в любое время. Мои специалисты и я лично разберём ситуацию, найдём настоящую причину и доведём до решения.

Возьмёмся и за разовую задачу, и за постоянное обслуживание. Первичная консультация — бесплатно и без обязательств.

📞 +7 903 729-62-41 💬 MAX: +7 903 729-62-41 ✈ Telegram @ITfresh_Boss

С уважением, Семёнов Евгений Сергеевич, директор «АйТи Фреш» — IT-аутсорсинг для компаний до 50 рабочих мест, 15+ лет практики

Источники

© ООО «АйТи-Фреш» · Москва · Все статьи