Postfix на каждый домен по-своему: баннеры, лимиты и политики в одном мультидоменном инстансе
Один Postfix обслуживает десятки доменов, а требования у всех разные: свой баннер, свой лимит размера письма, свой набор RBL, своя скорость доставки на внешний релей. Разбираем, почему домен в Postfix виден на трёх разных уровнях, где нужен отдельный smtpd-инстанс, а где хватит access-карты, и даём готовые конфиги под обе задачи.
EvilMail Team11 июля 2026 г.12 мин чтения
Один Postfix, сорок доменов клиентов, один-единственный main.cf — и требования, которые расходятся в стороны. Клиент A хочет баннер со своим брендом и потолок вложений 50 МБ. Клиент B требует жёсткий RBL и не больше сотни писем в час. Клиент C шлёт на капризный B2B-релей, который душит соединения за конкуренцию, и просит доставлять медленно, по одному письму. Наивная реакция — «допишем в main.cf» — разбивается о факт: message_size_limit, smtpd_banner и глобальные smtpd_recipient_restrictions действуют на весь инстанс сразу.
Разгадка проста: «настройка на домен» в Postfix не существует как единый параметр. Домен виден Postfix на трёх разных уровнях, и от того, на каком уровне живёт ваша задача, зависит инструмент: где-то нужен отдельный сетевой инстанс, а где-то — обычная hash-карта, которая подхватывается через postfix reload без даунтайма.
Где Postfix вообще видит домен: три плоскости
SMTP-транзакция — это диалог во времени. Postfix узнаёт про домен постепенно, команда за командой, и параметр можно привязать к домену только если к моменту его применения домен уже известен.
Уровень соединения. Сразу после TCP-connect Postfix отдаёт баннер (строку 220), анонсирует message_size_limit в ответе на EHLO, поднимает inbound TLS. В этот момент клиент ещё не сказал ни MAIL FROM, ни тем более RCPT TO. Postfix физически не знает, какому домену адресовано письмо. Значит всё, что отдаётся здесь, привязывается только к точке входа — IP, порту, smtpd-инстансу.
Уровень конверта. После MAIL FROM и RCPT TO известны и отправитель, и получатель. Здесь работают restriction classes, check_sender_access, check_recipient_access — политика по домену отправителя или получателя, без всяких новых инстансов, через access-карты и reload.
Уровень доставки. Письмо в очереди, qmgr выбирает транспорт. Тут рулят transport_maps, кастомные транспорты в master.cf, smtp_destination_rate_delay, smtp_tls_policy_maps, DKIM-подпись — всё, что касается исходящего трафика на домен-получатель или маршрутизации по домену отправителя.
Практический вывод: баннер и message_size_limitневозможно задать «по получателю», потому что они уходят до RCPT TO. Это всегда отдельный инстанс на своём IP. А политики, лимиты получателя и исходящий rate-limit — наоборот, делаются картами и policy-демоном, без плодения инстансов. Разберём обе группы.
Отдельные smtpd-инстансы: баннер и лимит размера на домен
Раз баннер отдаётся до того, как назван получатель, единственный способ дать домену свой баннер и свой потолок вложений — посадить его на отдельный `smtpd`-инстанс, слушающий отдельный IP. Клиент, подключающийся к этому IP, получает другую 220-строку и другой анонс SIZE.
В master.cf это выглядит так — базовый инстанс на основном IP и второй на выделенном:
text
203.0.113.10:25 inet n - y - - smtpd
10.0.0.55:25 inet n - y - - smtpd
-o smtpd_banner=$myhostname ESMTP brandx
-o message_size_limit=52428800
-o syslog_name=postfix-brandx
-o smtpd_recipient_restrictions=$brandx_recipient_restrictions
message_size_limit по умолчанию 10240000 (около 10 МБ) — здесь мы поднимаем его до 50 МБ только для этого входа. syslog_name обязателен: без него логи двух инстансов слипнутся в одну кашу под префиксом postfix/smtpd, и вы не отличите, кто ловил письмо. С syslog_name=postfix-brandx в логе появляется чёткий тег. Переменную brandx_recipient_restrictions из последней строки нужно объявить в main.cf — Postfix позволяет заводить собственные параметры и подставлять их через $имя.
Две ловушки, на которых спотыкаются. Первая: mynetworks и smtpd_*_restrictions наследуются из main.cf, и если у брендового инстанса своя политика — переопределяйте её через -o явно, не полагайтесь на наследование. Вторая: MX домена должен реально указывать на этот второй IP, иначе никто на новый баннер не попадёт.
Когда инстансов много и хочется полной изоляции — конфиги, очереди, spool раздельно — вместо ручного master.cf берите postmulti:
postmulti даёт каждому инстансу собственный /etc/postfix-brandx и отдельную очередь. Это тяжелее, чем -o в master.cf, зато инстансы не делят вообще ничего, и падение одного не роняет остальные.
На схеме оба входных smtpd сходятся в одинcleanup и одну очередь: различаются они только тем, что отдают клиенту на входе, — всё остальное общее.
Restriction classes и access-карты: политика на уровне получателя
Здесь мы на уровне конверта, где новые инстансы не нужны. Задача — «разным доменам разные правила инбаунда»: одному жёсткий RBL и проверка получателя, другому пропускать всё. Инструмент — smtpd_restriction_classes: вы описываете именованные наборы ограничений и раздаёте их доменам через access-карту.
Порядок в smtpd_recipient_restrictions критичен — правила проверяются сверху вниз, побеждает первое сработавшее (first match wins). permit_mynetworks идёт первым, чтобы свои хосты не спотыкались о RBL. Для правил по домену отправителя используйте зеркальную конструкцию — check_sender_access hash:/etc/postfix/sender_access, а в карте те же классы. Так один инстанс держит десятки доменов с индивидуальной политикой инбаунда, и добавление домена — это строчка в файле плюс postmap && postfix reload, без рестарта и без разрыва соединений.
Лимиты исходящей доставки на домен через transport_maps
Клиент C и его капризный банковский релей: тот принимает медленно и отбивает 421, если ему открыть больше пары соединений. Лечится это на уровне доставки — назначаем проблемному домену-получателю кастомный транспорт со своими лимитами.
Что мы переопределяем относительно дефолтов: default_destination_concurrency_limit = 20, initial_destination_concurrency = 5, default_destination_rate_delay = 0s, default_destination_recipient_limit = 50. Наш slow-транспорт держит максимум 2 параллельных соединения, выжидает 5 секунд между доставками и кладёт по одному получателю в письмо. Важно: эти лимиты per-transport, а не глобальные — весь остальной трафик по-прежнему идёт быстро через дефолтный smtp.
Если задача обратная — маршрутизировать по домену отправителя (разные бренды через разные смартхосты), берите sender_dependent_default_transport_maps и sender_dependent_relayhost_maps. Они смотрят на MAIL FROM и выбирают транспорт или релей под конкретный отправляющий домен.
postfwd: динамические лимиты и политики без рестартов
Restriction classes статичны — они не умеют считать «не больше N писем в час». Когда нужны квоты, временные throttle, скоринг по комбинации признаков, в цепочку ставится policy-демон. postfwd3 — рабочая лошадка для этого.
Сила postfwd в том, что правила перечитываются на лету — postfwd3 --reload, никакого рестарта Postfix. Можно комбинировать sender, recipient, client_address (CIDR), size, rcpt-count в одном условии и лепить политики, которые restriction classes не выразят.
Не путайте это с anvil — встроенным механизмом smtpd_client_connection_count_limit (по умолчанию 50), smtpd_client_connection_rate_limit, smtpd_client_message_rate_limit. Anvil считает per-client, то есть по IP подключающегося, а не по домену отправителя. Он хорош против одного долбящего клиента, но «квота на домен» — это не про него.
Per-domain DKIM и TLS
Короткий, но обязательный хвост. Каждый домен подписывается своим ключом и селектором. В OpenDKIM это две карты: SigningTable сопоставляет отправителя ключу, а KeyTable — ключ файлу приватного ключа.
Госрелею — secure с проверкой имени в сертификате, дряхлому legacy.example — may, чтобы не рвать доставку из-за самоподписанного сертификата. Это про исходящий TLS. Не путайте с inbound TLS — тот снова живёт на уровне соединения, то есть в smtpd-инстансе.
Чеклист перед выкаткой в прод
Держите postconf -n под контролем версий — это эффективная конфигурация без дефолтов, идеальный diff-объект в git.
Различайте, что требует restart, а что reload. Правки master.cf (новые инстансы, транспорты) и postmulti — только postfix restart. Параметры main.cf и hash-карты — postfix reload, без разрыва соединений.
После каждой правки hash-карты — postmap перед reload. Забыли — изменения не подхватятся, а Postfix продолжит читать старый .db.
postfix check перед перезапуском — ловит опечатки в путях и синтаксисе.
Проверяйте фактически применённое: postconf -M показывает master.cf, postconf -P — все переопределения через -o по инстансам.
Мониторьте mailq и лог по syslog_name — каждый инстанс должен писать под своим тегом.
Тестируйте баннер напрямую: openssl s_client -starttls smtp -connect 203.0.113.10:25 — увидите ровно ту 220-строку, что настроили.
Проверяйте лимиты нагрузкой: smtp-source гоняет тестовый поток на транспорт, а лог покажет, соблюдаются ли concurrency и rate_delay.
Держите предыдущие main.cf и master.cf под рукой — откат должен быть в одну команду.
Именно на этой мультидоменной схеме — раздельные smtpd-инстансы для баннеров и лимитов, access-карты для политик, postfwd для квот — построена почтовая инфраструктура evilmail.pro: один Postfix, десятки доменов, у каждого свои правила без даунтайма.