Отказоустойчивая почта с двумя MX: backup-MX, репликация и failover без потери писем
«Поставил второй MX — письма перестанут теряться» — это миф. Нормальный отправитель по RFC 5321 и так держит письмо в очереди 4–5 суток. Разбираем, когда backup-MX реально нужен, как не превратить его в генератор backscatter, и две честные модели отказоустойчивости: store-and-forward на Postfix и горячий standby с репликацией Dovecot.
EvilMail Team27 июля 2026 г.14 мин чтения
Миф о backup-MX: почему второй MX сам по себе не спасает письма
Самый частый запрос от тех, кто только что пережил падение почтового сервера: «поставьте нам второй MX, чтобы письма больше не терялись». Проблема в том, что от коротких падений primary второй MX почти никогда не спасает — а настроенный наспех сам начинает терять письма и репутацию домена, уже по вашей вине.
Разберём механику. Любой вменяемый отправляющий MTA действует по RFC 5321 §4.5.4.1: если получатель недоступен или отвечает временной ошибкой 4xx, письмо не отбрасывается, а кладётся в очередь и ретраится. Типичное окно повторных попыток — 4–5 суток. У Postfix по умолчанию maximal_queue_lifetime = 5d и bounce_queue_lifetime = 5d. Если ваш primary лежит 20 минут, полчаса или даже пару часов — отправитель просто повторит попытку позже, и письмо дойдёт без всякого backup-MX. Второй MX в этом сценарии не делает ничего.
Тогда когда backup реально нужен? Честно — в трёх случаях:
Отправители со слишком короткой очередью.
Backup-MX и failover без потери писем: Postfix, Dovecot, MTA-STS, DANE — EvilMail Blog
Дешёвые рассыльщики, самописные скрипты через
mail()
, некоторые транзакционные шлюзы и «одноразовые» отправители ретраят час-другой и сдаются. Для них наличие второго приёмника — разница между доставкой и потерей.
Плановые окна обслуживания. Вы гасите primary на апгрейд, а чужие очереди в это время не растут — почта тихо буферизуется на backup.
Сглаживание всплесков. Резкий наплыв писем можно распределить на второй узел.
А теперь оборотная сторона, ради которой и написана статья. Наивный backup-MX хуже его отсутствия. Он принимает всё на ваш домен, включая письма на несуществующие адреса, а потом либо генерирует bounce-и на подделанные обратные адреса (backscatter), либо попадает в spam-ловушки. И то, и другое роняет репутацию домена. В итоге письма теряются не во время редких падений primary, а постоянно — потому что провайдеры получателей режут ваш домен.
Две модели отказоустойчивости — не путать их
Прежде чем трогать конфиги, определитесь, какую задачу вы решаете. Моделей ровно две, и они принципиально разные.
Модель A — «очередь-и-пересылка» (store-and-forward). Backup-MX не хранит ящики. Он принимает письмо, кладёт в локальную очередь и пересылает на primary, как только тот вернулся. «Синхронизация» здесь — это зеркалирование списка валидных получателей, чтобы backup знал, какие адреса принимать, а какие отклонять на этапе RCPT.
Модель B — «горячий standby». Два полноценных mail-store. Ящики двусторонне реплицируются между узлами (Dovecot replication), любой узел может и принимать почту по SMTP, и отдавать её по IMAP. «Синхронизация» здесь — это двусторонняя репликация maildir.
Короткий вывод по выбору: большинству нужна модель A. Она проще, безопаснее и закрывает реальные задачи — короткие очереди отправителей и окна обслуживания. Модель B оправдана, только если вам критичен доступ к ящикам по IMAP в момент падения primary: например, поддержка не может ждать. За это вы платите сложностью репликации и риском split-brain.
DNS: приоритеты MX и что реально видит отправитель
Оба MX всегда присутствуют в DNS одновременно. Это не DNS-failover: вы ничего не переключаете, TTL для отказоустойчивости роли не играет. Отправляющий MTA сам сортирует записи по полю preference — меньшее число означает более высокий приоритет — и пробует хосты по порядку. Если TCP/25 к primary отказан или тот отвечает 4xx, MTA переходит к следующему хосту.
dns
evilmail.pro. 3600 IN MX 10 mx1.evilmail.pro.
evilmail.pro. 3600 IN MX 20 mx2.evilmail.pro.
mx1 и mx2 — отдельные хосты с собственными A/AAAA-записями, и у каждого обязателен корректный PTR (обратная зона): 203.0.113.10 → mx1.evilmail.pro и так далее. Без PTR на backup вы гарантированно словите отбраковку на крупных провайдерах ровно тогда, когда backup вступит в работу, — в самый неудачный момент.
Важное предупреждение про равные приоритеты. По RFC 5321 §5.1 записи с одинаковым preference выбираются случайно. Поставите оба MX на 10 — получите не строгий fallback, а случайную балансировку 50/50, и половина почты будет ходить через backup постоянно, со всеми его ограничениями. Нужен строгий приоритет — держите разные числа (10 и 20).
Backup-MX на Postfix: конфигурация store-and-forward
Ядро модели A. Backup должен: принимать почту для ваших доменов, отклонять всё остальное (иначе вы open relay) и пересылать принятое строго на primary.
reject_unauth_destination — критичная строка. Без неё backup принимает почту на любой чужой домен и становится open relay. С ней он принимает только то, что перечислено в relay_domains.
Теперь транспорт. Мы форсируем пересылку прямо на хост primary, минуя MX-lookup — иначе backup, спрашивая MX для evilmail.pro, снова увидит сам себя в списке и может зациклиться:
ini
# /etc/postfix/transport
# Квадратные скобки = НЕ делать MX-lookup, слать прямо на хост
evilmail.pro smtp:[mx1.evilmail.pro]:25
evilmail.cloud smtp:[mx1.evilmail.cloud]:25
Отдельно про smtp_fallback_relay: на backup его не направляют на самого себя. Задача backup — держать письмо в своей очереди и ждать возвращения primary, а не пропихивать его куда-то ещё.
Синхронизация получателей: защита от backscatter и spam-ловушек
Это раздел, в котором теряют репутацию домена. Backup, принимающий всё подряд на @evilmail.pro, позже обнаруживает, что адреса [email protected] не существует, и шлёт bounce на обратный адрес письма. А обратный адрес у спама подделан под чью-то жертву — вы бомбите постороннего человека своими отказами. Это backscatter, и за него домены попадают в чёрные списки.
Правильно — отклонять несуществующий адрес на этапе RCPT, ответом 550, ещё до приёма тела письма. Тогда за отказ отвечает отправитель, а не вы. Два способа.
Способ 1 — статическая карта получателей.relay_recipient_maps — список валидных адресов, который backup сверяет на лету. Генерируем его из основной БД (у evilmail это mailserver.virtual_users) и пересобираем по cron:
bash
#!/bin/bash
# /usr/local/bin/sync-relay-recipients.sh — cron каждые 5–15 минут
mysql -N mailserver -e "SELECT email FROM virtual_users" \
| awk '{print $1" OK"}' > /etc/postfix/relay_recipients
postmap /etc/postfix/relay_recipients
postfix reload
Способ 2 — проверка на лету. Если не хотите держать карту, backup может зондировать primary пробным RCPT-запросом:
Плюс — всегда свежие данные. Минус — зависимость от доступности primary (а он-то как раз может лежать) и кэш address_verify. По умолчанию положительный результат живёт в кэше 31 сутки, отрицательный перепроверяется раз в 3 часа (address_verify_negative_refresh_time); этот интервал стоит подрезать до нескольких минут, иначе временно недоступный адрес будет отклоняться часами. Для стабильной инфраструктуры надёжнее способ 1: карта не зависит от того, жив ли primary.
Горячий standby: репликация ящиков через Dovecot
Переходим к модели B — когда нужен живой доступ к ящикам во время падения primary. Здесь оба узла держат полноценный mail-store, а Dovecot двусторонне реплицирует maildir через dsync.
ini
# dovecot: 10-mail / плагины
mail_plugins = $mail_plugins notify replication
plugin {
mail_replica = tcps:mx2.evilmail.pro:12345
}
service replicator {
process_min_avail = 1
}
service aggregator {
fifo_listener replication-notify-fifo {
user = vmail
}
}
service doveadm {
inet_listener {
port = 12345
}
}
doveadm_password = <общий-секрет>
На приёмной стороне (mx2) поднимается doveadm-сервис на том же порту с тем же doveadm_password. Диагностика и ручной прогон:
bash
doveadm replicator status
doveadm replicator status '*'
doveadm sync -u [email protected] tcps:mx2.evilmail.pro:12345
Три оговорки, за которые бьют по рукам в проде:
Репликация — это не бэкап. Удалил письмо на одном узле — оно улетело на обоих. Держите отдельные снапшоты.
Единый vmail uid/gid на обоих узлах (у evilmail это 5000:5000) и одинаковая схема паролей (PLAIN на обоих). Иначе права на maildir разъедутся.
Split-brain при active-active реален. Если оба узла принимают почту и на короткое время теряют связь друг с другом, вы получите расхождение. Dovecot разрешает конфликты по времени, но риск лучше минимизировать: active-passive, когда почта нормально течёт через один узел, а второй — тёплый резерв.
TLS для двух MX: MTA-STS и DANE
Самая частая дыра при добавлении второго MX — TLS-политика покрывает только primary. Тогда при переключении на backup отправитель с включённым enforce-режимом откажется доставлять письмо, потому что backup «не входит в политику». Failover формально есть, а почта не идёт.
DANE работает иначе: TLSA-запись привязана к конкретному хостнейму и порту, поэтому нужна отдельная запись на каждый MX, и сертификат каждого узла должен матчить свою запись:
3 1 1 — это DANE-EE, SPKI, SHA-256. Забыли TLSA на backup или подставили туда SPKI от primary — доставка через backup ломается ровно в момент, когда он вам нужен.
Проверка failover без потери писем
Теория без теста ничего не стоит. Прогоняем реальный сценарий.
Отправляем письмо напрямую в backup и симулируем падение primary:
bash
# письмо прямо в backup
swaks --server mx2.evilmail.pro --to [email protected] \
--from [email protected] --tls
# симуляция падения primary (на mx1)
iptables -A INPUT -p tcp --dport 25 -j DROP
# либо: systemctl stop postfix
Проверяем, что письмо осело в очереди backup, поднимаем primary и форсируем доставку:
bash
postqueue -p # или mailq — письмо должно висеть в очереди
# ... возвращаем primary ...
postqueue -f # форс-доставка накопленного
grep 'relay=mx1' /var/log/mail.log
grep 'status=sent' /var/log/mail.log
Отдельно проверяем отсутствие backscatter — это половина смысла всей затеи. Шлём через backup письмо на заведомо несуществующий адрес:
Правильный результат — 550 на этапе RCPT. Если вы видите 250 Accepted, а потом bounce — карта получателей не работает, и вы генерируете backscatter. Чините relay_recipient_maps до того, как это увидят спам-ловушки.
Чеклист перед продом
PTR настроен на оба IP (mx1 и mx2), forward и reverse совпадают.
На backup стоит reject_unauth_destination — вы не open relay.
relay_recipient_maps свежий, cron пересобирает карту каждые 5–15 минут.
maximal_queue_lifetime и bounce_queue_lifetime не меньше, чем на primary (≥ 5d).
MTA-STS policy перечисляет оба MX, режим enforce.
TLSA-запись есть на каждый хостнейм, сертификат матчит свою запись.
Сертификаты валидны и не истекают на обоих узлах.
Тест доставки через backup (swaks + падение primary) прошёл.
Тест отклонения несуществующего адреса через backup вернул 550.
Мониторинг размера очереди backup включён; алерт, если очередь > N писем дольше 30 минут.
(Модель B) единый vmail uid/gid 5000 на обоих узлах, doveadm replicator status без застрявших юзеров.
Алерт на застрявшую очередь и на расхождение репликации.
Второй MX — это инструмент, а не оберег. Настроенный по модели A с честной картой получателей он тихо закрывает окна обслуживания и подхватывает почту от кривых отправителей, ничего не ломая. Настроенный наспех — превращает вашу инфраструктуру в источник backscatter. Разница целиком в двух вещах: reject_unauth_destination и синхронной карте валидных адресов. Начните с них.