Больше 100 имён в одном сертификате mailcow: включаю SNI и не теряю клиентов без его поддержки
Ещё один клиентский домен на 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 перестаёт быть узким местом даже при тысяче доменов на сервере — просто вместо одного сертификата с тысячами имён получается много сертификатов по два-три имени в каждом.
- лимит 100 SAN-имён — ограничение Let's Encrypt, а не mailcow
- ENABLE_SSL_SNI=y переключает mailcow с одного общего сертификата на много отдельных
- основной сертификат: MAILCOW_HOSTNAME + полные имена из ADDITIONAL_SAN
- отдельный сертификат на каждый домен базы, включая его autodiscover/autoconfig
- шаблон ADDITIONAL_SAN=mail.* при 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 у наших клиентов невелика, но некритичной её не назовёшь: это как раз то оборудование, которое нельзя обновить прошивкой и которое годами работает без внимания администратора именно потому, что «просто отправляет уведомления и не требует ухода». Из-за этого оно легко выпадает из поля зрения при плановых работах — про него вспоминают, только когда оно перестаёт присылать письма, а к этому моменту причину уже сложнее связать с изменением конфигурации почтового сервера, случившимся неделю назад.
Как «Цветной тираж» включал 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-валидацией именно этого домена), остальные сто с лишним доменов это никак не затронет — но и заметить проблему именно этого одного домена без централизованного мониторинга становится сложнее.
Итог: 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, начнёт отдавать сертификат без клиентских имён.
Источники
- mailcow docs: Advanced SSL (SNI certificates) — Механика ENABLE_SSL_SNI: генерация основного сертификата (MAILCOW_HOSTNAME + полные имена ADDITIONAL_SAN) и отдельных сертификатов на каждый домен с autodiscover/autoconfig, поведение шаблона ADDITIONAL_SAN=mail.*, рекомендация клиентам без SNI подключаться по MAILCOW_HOSTNAME, переменная ADDITIONAL_SERVER_NAMES. https://docs.mailcow.email/post_installation/firststeps-ssl/
- Let's Encrypt: Rate Limits — Проверено: один сертификат может содержать до 100 идентификаторов (DNS-имён или IP-адресов). https://letsencrypt.org/docs/rate-limits/
- mailcow community: Separate certificates for added domains — Обсуждение на форуме mailcow практических случаев разделения сертификатов при включении ENABLE_SSL_SNI и типичных вопросов о том, какие домены получают отдельный сертификат. https://community.mailcow.email/d/4598-separate-certificates-for-added-domains
- GitHub mailcow-dockerized: Issue #461 — Let's Encrypt subjectAltName limit of 100 domains — Исходное обсуждение проблемы лимита 100 SAN-имён в mailcow и предложенного решения через постдоменные сертификаты (SNI). https://github.com/mailcow/mailcow-dockerized/issues/461
- mailcow-dockerized на GitHub: acme.sh — Проверено: без SNI в сертификат идут MAILCOW_HOSTNAME, все проверенные имена доменов и ADDITIONAL_SAN; при SNI — основной сертификат и отдельный на каждый домен, основной копируется в data/assets/ssl/cert.pem; продление при сроке < 30 дней. https://github.com/mailcow/mailcow-dockerized/blob/master/data/Dockerfiles/acme/acme.sh



