mailcow: больше 100 имён в сертификате — включаем SNI
АйТи Фреш
Linux, Docker и DevOps

Больше 100 имён в одном сертификате mailcow: включаю SNI и не теряю клиентов без его поддержки

Автор: , директор ООО «АйТи-Фреш» · · ~13 мин чтения
Переход mailcow от одного общего сертификата на все домены к отдельным SNI-сертификатам на каждый домен после превышения лимита Let's Encrypt
Один сертификат на всех треснул на сто первом имени — решение в множестве маленьких, а не в одном большом.

Ещё один клиентский домен на mailcow — и acme-mailcow перестаёт обновлять общий сертификат: у Let's Encrypt жёсткий лимит в 100 имён на один сертификат, а mailcow кладёт туда autoconfig, autodiscover и имена из ADDITIONAL_SAN для каждого домена. Решение — ENABLE_SSL_SNI, но включить его не глядя означает рискнуть подключением старых почтовых клиентов, которые SNI не умеют. Разбираю, как устроено разделение сертификатов в mailcow, что уходит в отдельные сертификаты, а что остаётся в основном, и как не потерять доступ у части пользователей.

Кейс: сто третье имя и отказ acme-mailcow

Полиграфический центр «Цветной тираж» (23 рабочих места) держит на своём mailcow не только собственную почту, но и почтовые ящики для десятков небольших клиентов-заказчиков — типографии исторически предлагают такую услугу вместе с версткой и печатью визиток, чтобы клиент получал готовый комплект «сайт плюс почта под свой домен». Мы обслуживаем для них корпоративную почту, и в какой-то момент acme-mailcow — встроенный клиент, который заказывает сертификаты Let's Encrypt, — перестал выпускать обновлённый сертификат вообще, без внятной ошибки в UI, только с записью в логе контейнера (docker compose logs --tail=200 acme-mailcow).

Причина оказалась не в mailcow, а в самом Let's Encrypt: в одном сертификате может быть не больше 100 идентификаторов (DNS-имён). По умолчанию mailcow собирает в один SAN-сертификат MAILCOW_HOSTNAME и для каждого почтового домена — autodiscover. и autoconfig., плюс всё, что задано в ADDITIONAL_SAN. Важная оговорка: в запрос попадают только имена, которые прошли проверку, то есть резолвятся в IP сервера. Поэтому лимит считается не в доменах, а в именах, и упирается в него сервер гораздо раньше, чем на сотом домене.

У «Цветного тиража» на сервере 104 почтовых домена, в mailcow.conf стояло ADDITIONAL_SAN=mail.*, но у большинства клиентов DNS вели их собственные подрядчики, и записи на наш сервер были только у части. Считаю: 33 клиента с записями mail., autoconfig. и autodiscover. — это 99 имён, плюс MAILCOW_HOSTNAME — ровно 100. Тридцать четвёртый клиент, который направил свои записи на сервер, дал 103 имени, и заказ нового сертификата Let's Encrypt отклонил.

Особенно неприятно то, что симптом проявился не сразу и не для всех. Старый общий сертификат никто не отзывал: он продолжал работать до своей даты окончания, и 33 «старых» клиента ничего не заметили. Пострадал только новый домен — его имён в действующем сертификате не было, и его сотрудники получали предупреждение о несовпадении имени в веб-почте и ошибку доверия в десктопных клиентах. Но это была лишь первая ласточка: acme-mailcow продлевает сертификат, когда до окончания остаётся меньше 30 дней, и при 103 именах продление упало бы для всех доменов сразу. У нас оставалось примерно пять недель.

Что делает ENABLE_SSL_SNI на самом деле

Решение, которое документация mailcow предлагает именно для этого случая — параметр ENABLE_SSL_SNI=y в mailcow.conf с последующим docker compose up -d. Смысл не в том, чтобы «увеличить лимит» (это невозможно, лимит выставлен на стороне Let's Encrypt, а не mailcow), а в том, чтобы вместо одного гигантского сертификата на все домены выпускать много маленьких — по отдельному сертификату на каждый почтовый домен, у которого есть проверенные имена, плюс один основной.

После включения SNI обслуживание сертификатов у Postfix, Dovecot и Nginx строится так: генерируется основной сертификат, в который входят MAILCOW_HOSTNAME и полностью заданные (не в формате wildcard-подстановки) имена из ADDITIONAL_SAN, а для каждого домена, заведённого в базе mailcow, дополнительно генерируется собственный сертификат-пара, куда автоматически добавляются его autodiscover- и autoconfig-поддомены. Это ключевое отличие от режима без SNI: там всё перечисленное было бы одним списком SAN внутри одного файла.

Отдельный нюанс с ADDITIONAL_SAN — если там прописано конкретное полное имя вроде test.example.com, документация прямо говорит: оно попадёт как SAN в основной сертификат, но отдельная пара сертификат/ключ под него создана не будет. А вот записи в формате шаблона mail.* при включённом SNI разворачиваются в mail.domain1.tld, mail.domain2.tld и так далее — по одному имени на каждый домен базы, и уже эти имена добавляются в соответствующие персональные сертификаты доменов.

Это разделение логично объясняет, почему сам по себе основной сертификат при включённом SNI больше не растёт пропорционально числу доменов клиентов: в него попадают только явно перечисленные в ADDITIONAL_SAN полные имена (обычно это служебные хосты вроде веб-панели или общего webmail-адреса) плюс MAILCOW_HOSTNAME, а вся клиентская масса доменов уходит в собственные небольшие сертификаты. Именно поэтому лимит в 100 SAN перестаёт быть узким местом даже при тысяче доменов на сервере — просто вместо одного сертификата с тысячами имён получается много сертификатов по два-три имени в каждом.

Сравнение режима одного общего сертификата и режима ENABLE_SSL_SNI с отдельными сертификатами на каждый домен mailcow
Лимит считается в именах, а не в доменах — SNI снимает его, раздавая каждому домену свой сертификат.

Кого SNI может отключить: клиенты без поддержки SNI

Server Name Indication — это расширение TLS, которое клиент использует, чтобы ещё до установления шифрованного соединения сообщить серверу, к какому именно домену он подключается — только тогда сервер понимает, какой из множества сертификатов отдать. Абсолютное большинство современных почтовых клиентов SNI поддерживают: Outlook, Thunderbird, встроенная почта на iOS и Android — все справляются без проблем. Но документация mailcow прямо предупреждает: не все клиенты умеют SNI, и для них нужен путь без потери соединения.

Практически это означает, что у совсем старых или узкоспециализированных клиентов — встроенных почтовых модулей legacy-софта, старых версий корпоративных SMTP/IMAP-библиотек, некоторых сканеров-МФУ со встроенной отправкой почты по SMTP — может не быть поддержки SNI вовсе. Такое устройство при подключении по TLS не сообщает серверу имя домена, и сервер вынужден отдавать какой-то сертификат «по умолчанию» — это как раз основной сертификат сервера с именем MAILCOW_HOSTNAME.

Рекомендация из документации однозначная: клиентам без поддержки SNI нужно подключаться именно по MAILCOW_HOSTNAME, а не по имени своего домена — тогда сертификат совпадёт с именем, к которому идёт подключение, и TLS-хендшейк пройдёт без предупреждений о несовпадении имени. Проблема в том, что многие клиентские настройки на автомате указывают в качестве сервера входящей/исходящей почты собственный домен клиента (по логике «моя почта — мой сервер»), а не общий хост провайдера услуги — и это ровно то, что нужно проверить и, возможно, перенастроить перед включением SNI.

На практике доля устройств без поддержки SNI у наших клиентов невелика, но некритичной её не назовёшь: это как раз то оборудование, которое нельзя обновить прошивкой и которое годами работает без внимания администратора именно потому, что «просто отправляет уведомления и не требует ухода». Из-за этого оно легко выпадает из поля зрения при плановых работах — про него вспоминают, только когда оно перестаёт присылать письма, а к этому моменту причину уже сложнее связать с изменением конфигурации почтового сервера, случившимся неделю назад.

Перед включением ENABLE_SSL_SNI выясните у клиентов с нестандартным оборудованием (МФУ, legacy-CRM с отправкой почты, встроенные интеграции), на какой именно хост у них настроено SMTP/IMAP-подключение. Если это домен клиента, а не MAILCOW_HOSTNAME — после включения SNI такое устройство может начать получать предупреждение о несовпадении сертификата или вовсе отваливаться по TLS.
Дерево диагностики для клиентов почты mailcow без поддержки SNI после включения ENABLE_SSL_SNI
Проблема почти всегда одна: старое устройство настроено на домен клиента вместо MAILCOW_HOSTNAME.

Как «Цветной тираж» включал SNI без простоя

Прежде чем щёлкнуть ENABLE_SSL_SNI=y, мы сначала прошлись по списку из 104 доменов и разослали через личный кабинет клиентов-заказчиков короткое уведомление: если почта настроена в необычном софте (кассовые системы с уведомлениями по email, старые сканеры с функцией «отправить на почту»), стоит либо проверить хост подключения на MAILCOW_HOSTNAME, либо явно сообщить нам, чтобы протестировать точечно. Из 104 доменов откликнулись пять — у всех оказались именно старые МФУ с прошивкой без обновлений, настроенные на mail.<домен клиента>. До SNI это работало только потому, что mail.* сидел в общем сертификате; после включения SNI устройство без SNI получит основной сертификат с MAILCOW_HOSTNAME и увидит несовпадение имени.

Дальше — стандартная последовательность: правка ENABLE_SSL_SNI=y в mailcow.conf, docker compose up -d, и ожидание, пока acme-mailcow перевыпустит сертификаты уже в новом режиме. Он заказывает их последовательно, один за другим, с HTTP-валидацией каждого имени: основной сертификат и 34 доменных заняли около 40 минут. Остальные 70 доменов без записей на наш сервер собственного сертификата не получили — их пользователи и так подключаются по MAILCOW_HOSTNAME. Лимиты Let's Encrypt на число заказов в час нам при таком объёме не грозили, но на сервере с сотнями доменов я бы проверил их заранее.

После завершения перевыпуска (для тех, кто привык к Let's Encrypt на nginx и IIS, логика та же, только клиент встроен в mailcow) я вручную протестировал TLS-подключение к нескольким доменам из середины и конца списка командой openssl s_client -connect mail.example.com:993 -servername mail.example.com, сверяя, что в ответе действительно приходит сертификат именно этого домена, а не общий. Пяти клиентам со старыми МФУ отдельно помогли перенастроить хост подключения на MAILCOW_HOSTNAME — простоя по почте у них не случилось, потому что перенастройка заняла меньше времени, чем сам процесс перевыпуска сертификатов на сервере.

Что проверить после включения SNI

Первое — реверс-прокси, если он есть перед mailcow (например, отдельный nginx или другой обратный прокси в контуре клиента). При включённом SNI сертификаты у mailcow лежат раздельно: каждый в своём каталоге вида data/assets/ssl/<первое имя сертификата>/, а в data/assets/ssl/cert.pem и key.pem копируется только основной. Если внешний реверс-прокси терминирует TLS до mailcow и берёт именно cert.pem, после включения SNI он будет отдавать сертификат без клиентских имён — прокси нужно научить выбирать сертификат по SNI самому или перевести на сквозную передачу TLS.

Второе — веб-интерфейс самого mailcow, если для доступа к нему используется имя, отличное от MAILCOW_HOSTNAME. Для такого сценария в mailcow.conf есть отдельная переменная ADDITIONAL_SERVER_NAMES — она не про почтовые сертификаты SNI, а про то, какие имена веб-панель mailcow готова обслуживать, и её стоит сверить отдельно, чтобы не перепутать с механизмом ADDITIONAL_SAN для почты.

Третье, самое важное для эксплуатации — регулярный мониторинг сроков действия сертификатов. При включённом SNI это уже не один сертификат с одной датой истечения, а больше сотни независимых, каждый со своим циклом продления через Let's Encrypt. Если один конкретный домен по какой-то причине не смог перевыпустить сертификат (например, временная проблема с DNS-валидацией именно этого домена), остальные сто с лишним доменов это никак не затронет — но и заметить проблему именно этого одного домена без централизованного мониторинга становится сложнее.

Итоговые цифры кейса включения SNI-сертификатов в mailcow: 104 домена, 103 имени, 35 сертификатов, без простоя
Сто три имени не влезли в один сертификат — тридцать пять маленьких решили задачу без простоя.

Итог: 35 сертификатов вместо одного, 5 МФУ перенастроены, простоя нет

Через месяц после включения SNI acme-mailcow у «Цветного тиража» обслуживает основной сертификат и 34 доменных без ошибок выпуска, а новые клиенты добавляются без оглядки на счётчик имён — лимит в 100 SAN на сертификат перестал быть проблемой, потому что каждый домен теперь получает собственный маленький сертификат вместо места в одном общем списке. Пять клиентов со старым оборудованием после перенастройки хоста подключения на MAILCOW_HOSTNAME тоже не жалуются — их МФУ и кассовые уведомления продолжают отправлять почту как раньше.

Главный вывод для владельцев мультидоменных инсталляций mailcow: SNI стоит включать не когда лимит уже упёрся и сертификаты перестали выпускаться (как получилось у «Цветного тиража»), а заранее, при подходе к сотне имён — проверить их число можно командой openssl x509 -noout -text -in data/assets/ssl/cert.pem | grep -o 'DNS:' | wc -l, — тогда есть время спокойно опросить клиентов на предмет нестандартного оборудования, вместо того чтобы разбираться с этим в авральном режиме, когда часть доменов уже осталась без действующего сертификата.

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

Почему mailcow перестал выпускать сертификат, хотя доменов меньше сотни?

Это ограничение Let's Encrypt, а не mailcow: один сертификат может содержать не более 100 DNS-имён. По умолчанию mailcow собирает в один сертификат MAILCOW_HOSTNAME, autodiscover и autoconfig каждого домена и имена из ADDITIONAL_SAN, поэтому лимит наступает уже примерно на 33–50 доменах. После превышения acme-mailcow не может ни добавить новый домен, ни продлить общий сертификат.

Что именно делает ENABLE_SSL_SNI в mailcow.conf?

Включает режим, при котором вместо одного общего сертификата на все домены генерируется основной сертификат (MAILCOW_HOSTNAME плюс полные имена из ADDITIONAL_SAN) и отдельный сертификат для каждого почтового домена из базы mailcow, включая его autodiscover/autoconfig-поддомены. Требуется docker compose up -d после правки конфигурации.

Отвалятся ли клиенты, которые не поддерживают SNI, после включения ENABLE_SSL_SNI?

Документация mailcow прямо указывает, что не все клиенты умеют работать с SNI, и таким клиентам нужно подключаться по MAILCOW_HOSTNAME, а не по имени своего домена. Если у клиента почтовый софт настроен по домену клиента напрямую, после включения SNI ему нужно перенастроить хост подключения — иначе сертификат, который сервер отдаст по умолчанию, не совпадёт с ожидаемым именем.

Что происходит с записью ADDITIONAL_SAN вида test.example.com при включённом SNI?

Полное имя без шаблона попадает как дополнительный SAN в основной сертификат сервера — отдельная пара сертификат/ключ под него не создаётся. А вот шаблон вида mail.* при включённом SNI разворачивается по одному имени для каждого почтового домена базы, и эти имена уже добавляются в персональные сертификаты соответствующих доменов.

Нужно ли что-то менять во внешнем реверс-прокси перед mailcow после включения SNI?

Да, если реверс-прокси terminate TLS до mailcow и прописывает путь к конкретному файлу сертификата напрямую. При включённом SNI в data/assets/ssl/cert.pem остаётся только основной сертификат, а доменные лежат в отдельных подкаталогах data/assets/ssl/, и прокси, который берёт cert.pem, начнёт отдавать сертификат без клиентских имён.

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

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

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

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

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

Источники

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