MAATRIX / Блог / Очередь Postfix на 40 тысяч писем: как остановить и не потерять свои

Очередь Postfix на 40 тысяч писем: как остановить и не потерять свои

MAATRIX

Утро начинается с алерта: диск на почтовом сервере забит под завязку, mailq | tail -1 показывает что-то вроде «41328 Requests», и почти все они в статусе deferred. Кто-то — или что-то — уже час-другой гонит рассылку через ваш Postfix, и с каждой минутой растёт риск, что IP улетит сразу во все чёрные списки, а заодно в этой же очереди застрянут ваши реальные письма: уведомления, счета, переписка с клиентами. Разберём, как остановить рассылку в первые пять минут, отделить спам от легитимной почты и вычистить только лишнее, не потеряв то, что действительно должно уйти.

Первым делом: не `postfix flush`, а пауза

Инстинктивная реакция при виде забитой очереди — «разгрести её побыстрее», и первое, что приходит в голову, это postfix flush (он же postqueue -f). Это худшее, что можно сделать во время инцидента: команда форсирует немедленную попытку доставки для всей очереди разом, включая спам. Вместо того чтобы притормозить рассылку, вы её ускоряете — сервер долбит десятками параллельных соединений по чужим MX, ваш IP моментально попадает в списки за подозрительный паттерн трафика, а легитимные письма летят вперемешку со спамом теми же самыми потоками.

Правильная первая команда — не «ускорить», а «остановить, не потеряв данные». Есть три рабочих варианта, и выбор зависит от того, где источник утечки.

Вариант 1. Мягкая остановка исходящей доставки без остановки приёма. Если новая легитимная почта продолжает поступать (например, транзакционные письма от вашего приложения) и вы не хотите её терять или откатывать, но нужно прямо сейчас перестать слать что-либо наружу:

postconf -e "defer_transports = smtp"
postfix reload

Это переводит транспорт smtp в режим «отложено»: Postfix продолжает принимать почту (и локальную, и по SMTP на 25/587), кладёт её в очередь как обычно, но ни одной попытки исходящей доставки не делает — все новые письма сразу уходят в deferred. Ничего не теряется, рассылка наружу физически останавливается за секунды.

Вариант 2. Заморозить то, что уже в очереди. Если хочется зафиксировать текущее состояние очереди как есть — для последующего разбора — держите все письма:

postsuper -h ALL

Это ставит все сообщения (incoming, active, deferred) в hold — Postfix не будет их трогать, пока вы явно не снимете hold командой postsuper -H. Минус: новые письма, попадающие в очередь после этой команды, holdа не наследуют, так что для полной остановки её обычно комбинируют с вариантом 1.

Вариант 3. Полная остановка службы. Если рассылка идёт через локальный скрипт (взломанная CMS, вебшелл, cron), а не через SMTP-подключение снаружи, самый надёжный способ — остановить Postfix целиком:

systemctl stop postfix

Файлы очереди на диске не трогаются, ничего не удаляется и не отправляется — просто останавливается master-процесс. Минус: перестают приниматься и легитимные новые письма (SMTP-порты просто закрываются), поэтому это вариант «для аварийной паузы на 10-15 минут, пока разбираетесь», а не «на весь день».

На практике для типичной ситуации со скомпрометированным сервером лучшая последовательность — вариант 3 (мгновенно вырубить и приём, и отправку), затем спокойный разбор, и только после чистки — контролируемый запуск обратно.

Быстрая оценка масштаба: что и куда летит

Прежде чем удалять хоть одно письмо, поймите картину целиком. Начните с общих цифр:

mailq | tail -1
postqueue -p | wc -l

Дальше нужен разбор по отправителям и получателям. Современный Postfix (начиная с ветки 3.1, то есть практически любая актуальная установка) умеет отдавать очередь в JSON — это на порядок удобнее парсить, чем текстовый вывод mailq:

postqueue -j > /root/queue-dump.jsonl

# топ отправителей
jq -r '.sender' /root/queue-dump.jsonl | sort | uniq -c | sort -rn | head -20

# топ доменов-получателей
jq -r '.recipients[].address' /root/queue-dump.jsonl \
  | awk -F@ '{print $2}' | sort | uniq -c | sort -rn | head -20

# распределение по времени постановки в очередь
jq -r '.arrival_time' /root/queue-dump.jsonl | cut -c1-13 | sort | uniq -c

Если jq в системе нет, поставьте (apt install jq / dnf install jq) — он пригодится ещё не раз при разборе инцидента. Смотрите на то, что получилось: если 38 000 из 41 000 писем — от одного отправителя или с одним и тем же паттерном темы, летящие на случайные домены без всякой системы, это почти наверняка рассылка. Если очередь равномерно размазана по десяткам ваших обычных отправителей и адресована реальным доменам-партнёрам — вероятнее легитимный затор, а не компрометация (например, у получателя лежит greylisting или временно недоступен его MX). У этой ситуации своя логика разбора — если дело не в атаке, а в обычном застревании из-за DNS, репутации или ограничений получателя, это отдельная тема, которую подробно разбирает статья про переполненную очередь Postfix.

Также взгляните на sasl_username в логе — если рассылка идёт через один и тот же авторизованный аккаунт, это прямое указание на утечку пароля:

grep "sasl_username" /var/log/mail.log | awk -F'sasl_username=' '{print $2}' | awk '{print $1}' | sort | uniq -c | sort -rn

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Спам или всё-таки своё: как читать содержимое очереди

Цифры дают направление, но решение «удалить или оставить» принимается по содержимому. Для конкретного письма смотрите заголовки и тело:

postcat -q QUEUE_ID          # заголовки + тело как есть
postcat -vq QUEUE_ID | less  # то же, но с подробным выводом envelope

Признаки, что перед вами спам, а не ваша почта:

  • Received-заголовки показывают, что письмо ушло в очередь не через ваш обычный путь отправки (не через ваше приложение или почтовый клиент), а напрямую с локального сокета от www-data или nobody — типичный след взломанного PHP-скрипта, вызывающего mail().
  • From — либо случайный набор символов в вашем домене (x7f2k9@yourdomain.com), либо подделанный сторонний адрес (спуфинг From для обхода простых фильтров).
  • Subject повторяется тысячами копий с минимальными вариациями либо явно рекламный (казино, фарма, поддельные «уведомления» от банков).
  • Recipients — список явно скрейпленный или сгенерированный: случайные локальные части на случайных доменах, много попаданий на несуществующие адреса (это же вызывает волну bounce-писем обратно на ваш сервер — «backscatter», который довешивает нагрузку сверху).
  • Reason в postqueue -p для таких писем чаще всего 550 5.1.1 User unknown или блокировка получателем по репутации — то есть они и так обречены, просто ещё не успели окончательно отбраковаться.

Признаки легитимного письма: конкретный человек-отправитель или узнаваемый сервис (биллинг, CRM, транзакционные уведомления), адресат — реальный контрагент или клиент, reason вида Connection timed out / Host or domain name not found / временный greylisting-дефер, а не массовый 550 от разных доменов одновременно.

Не спешите с выводами по одному письму — просмотрите выборку в 15-20 штук из разных частей очереди командой postqueue -p | head -100 и точечными postcat, чтобы понять пропорцию и найти чёткий отличительный паттерн (общий отправитель, общий диапазон ID очереди по времени, общий шаблон темы) — именно по этому паттерну дальше будет строиться избирательное удаление.

Избирательная чистка: `postsuper` вместо огня по площадям

postsuper -d ALL — соблазнительная кнопка «удалить всё и не думать», но она же убивает вперемешку и спам, и ваши легитимные письма. Используйте её только если по итогам предыдущего шага вы на 100% уверены, что очередь целиком — мусор (например, только что установленный сервер, который ещё не успел отправить ничего вашего, но уже скомпрометирован).

В подавляющем большинстве случаев нужна выборочная чистка по найденному паттерну. Если паттерн — конкретный отправитель:

postqueue -j | jq -r 'select(.sender=="attacker@yourdomain.com") | .queue_id' > /root/spam-ids.txt
wc -l /root/spam-ids.txt          # проверьте количество перед удалением
postsuper -d - < /root/spam-ids.txt

Если паттерн — причина отказа у получателя (например, массово blocked или spam в тексте ошибки):

postqueue -j \
  | jq -r 'select(.recipients[]?.delay_reason // "" | test("blocked|spam"; "i")) | .queue_id' \
  > /root/blocked-ids.txt

Если единой машинной метки нет, а есть только характерная строка в теме или теле, ищите её напрямую через postcat по каждому ID из общего списка очереди и собирайте совпадения в отдельный файл. Это медленнее, чем фильтр по jq, зато безопаснее на неоднородной очереди: вы удаляете по содержимому, а не по эвристике вроде «всё от этого отправителя», которая может случайно зацепить и что-то легитимное с тем же From (например, письма от вашей CRM, если её адрес подменили при спуфинге).

Промежуточная проверка перед массовым -d обязательна: выведите первые 20 ID из файла и прогоните postcat вручную, убедитесь, что видите однотипный мусор, и только потом запускайте удаление на весь список. Восстановить удалённое из postsuper -d нельзя — файл стирается с диска сразу.

Сохранение и возврат легитимных писем в работу

Пока идёт разбор, оставшиеся легитимные письма лучше явно застраховать, а не полагаться на то, что вы случайно не заденете их при чистке спама. Возьмите список ID, которые точно ваши (по обратному фильтру — «всё, что не попало в spam-ids.txt»), и поставьте им hold отдельно:

postqueue -j | jq -r '.queue_id' | grep -vFf /root/spam-ids.txt > /root/legit-ids.txt
postsuper -h - < /root/legit-ids.txt

Так у вас на руках два чётких списка: то, что будет удалено, и то, что точно останется. После удаления спама снимите hold с легитимных писем и попробуйте доставить их отдельно от общего потока — небольшими порциями, чтобы не спровоцировать повторную блокировку по репутации:

postsuper -H - < /root/legit-ids.txt
postqueue -i $(head -50 /root/legit-ids.txt)   # доставка по конкретным ID, порциями

Когда очередь вычищена от мусора и вы убедились, что источник утечки перекрыт (см. следующий раздел), можно вернуть defer_transports в исходное состояние и снять общий hold:

postconf -e "defer_transports ="
postfix reload
postsuper -H ALL

Именно на этом этапе, и только на нём, уместен postqueue -f — когда очередь уже чистая и вы сознательно хотите форсировать отправку оставшихся легитимных писем, а не наблюдать, как они медленно уходят по стандартному расписанию ретраев.

После чистки: откуда дыра и что с репутацией IP

Вычищенная очередь — это устранение симптома, а не причины. Если не найти, как рассылка попала на сервер, через пару часов будет новая партия из 40 000 писем.

Поиск источника. Первым делом посмотрите, был ли это авторизованный релей через SASL — если да, sasl_username в логах уже указал на конкретный аккаунт: смените пароль, проверьте, не используется ли он ещё где-то (переиспользование пароля — самая частая причина утечки), убедитесь, что smtpd_tls_auth_only = yes в main.cf, чтобы креды не гуляли в открытом виде. Если рассылка шла не через SMTP-подключение снаружи, а локально, ищите скрипт: проверьте crontab -l для всех пользователей, свежеизменённые файлы в веб-директории (find /var/www -mtime -3 -type f), процессы, всё ещё генерирующие письма (ps aux | grep -i mail), и логи веб-сервера на предмет POST-запросов к формам обратной связи или к недавно обновлённым плагинам CMS — незащищённая форма без капчи или уязвимый плагин через mail()/sendmail в PHP является одним из самых частых источников такой рассылки. Если ситуация похожа на полноценный взлом сервера, а не на локальную утечку в одном скрипте, отдельно пройдитесь по общему чек-листу реагирования — он есть в статье «Взломали сервер: пошаговый план», там разобрана последовательность действий за пределами одной только очереди Postfix: смена всех паролей и ключей, поиск персистентности, проверка cron и systemd-таймеров.

Заодно перепроверьте postconf -n | grep mynetworks — расширенный по ошибке диапазон превращает сервер в открытый релей для кого угодно снаружи, без всякого пароля.

Репутация IP. Пока чистите причину, IP уже мог попасть в чёрные списки — это происходит быстро, часто в течение первого часа активной рассылки. Проверьте текущий статус через любой публичный blacklist-чекер (например, mxtoolbox.com/blacklists) по нескольким основным спискам, и отдельно убедитесь, что PTR-запись (обратная зона) для IP соответствует имени хоста, который вы указываете в HELO — рассинхрон между ними сам по себе повышает подозрительность даже без рассылки. Если IP уже попал в список, это отдельный процесс восстановления — с разными списками, разными сроками автоматического снятия и разными формами ручного запроса на делистинг, и он подробно разобран в статье «IP попал в чёрные списки: восстановление репутации». Не начинайте делистинг, пока не убедились, что источник рассылки полностью перекрыт — иначе IP просто вернут в список повторно после следующей волны спама с того же сервера.

После делистинга не включайте отправку на полную мощность сразу: почтовые провайдеры оценивают репутацию свежего или недавно «отмытого» IP по объёму и скорости роста трафика, и резкий скачок снова выглядит подозрительно. Если у вас легитимная регулярная рассылка (транзакционная почта, уведомления), стоит на будущее вынести проверку исходящего потока на отдельный слой антиспам-фильтрации — например, настроить rspamd, который умеет ограничивать и подозрительные исходящие паттерны, а не только входящий спам.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

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

Можно ли просто перезапустить Postfix, чтобы очередь очистилась?

Нет, restart не трогает файлы очереди — сообщения остаются на диске и после перезапуска доставка продолжится с того же места. Единственный способ реально убрать письма — postsuper -d по конкретным ID.

Что будет с легитимными письмами, если я использую postsuper -d ALL?

Они удалятся безвозвратно вместе со спамом. Восстановить нельзя — файлы стираются с диска, бэкапа очереди Postfix не хранит.

Почему postqueue -f — это плохая идея во время атаки, если письма всё равно рано или поздно отправятся сами?

Дело не в том, отправятся ли они, а в том, как быстро. flush заставляет Postfix попытаться доставить всю очередь параллельно и немедленно, создавая всплеск однотипного трафика — именно такой паттерн чаще всего и триггерит автоматическое занесение IP в списки. Обычный график ретраев размазывает нагрузку и не создаёт этого всплеска.

Как понять, что источник рассылки действительно перекрыт, а не просто временно затих?

Проверьте, что очередь не растёт после остановки source: postqueue -j | wc -l не должен увеличиваться в течение 15-20 минут наблюдения при снятом hold на приём (или явно оставайтесь в режиме defer_transports/postfix stop, пока не закроете конкретную дыру — аккаунт, скрипт, форму).

Нужно ли менять IP сервера, если он попал в чёрные списки?

Обычно нет — большинство списков снимает блокировку автоматически или по запросу после устранения причины за срок от нескольких часов до нескольких дней. Смена IP оправдана только если старый успел «протухнуть» окончательно (например, попал в списки, требующие ручного разбора с задержкой в недели) и вам критично время.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →