Моніторинг черги Postfix: як читати mailq і розбирати затори
Черга Postfix — не смітник для періодичного flush, а діагностичний прилад. Вчимося читати її як інженер: від postqueue -p і qshape до postcat, пошуку кореневої причини в mail.log і точкового, безпечного видалення через postsuper.
EvilMail Team8 липня 2026 р.10 хв читання
Дзвонить моніторинг: у черзі 40 000 листів, десь застрягла реєстрація клієнта, і перший рефлекс чергового — postfix flush або, ще гірше, postsuper -d ALL. Це найдорожча помилка в адмініструванні пошти. Черга Postfix нічого не «забиває» сама собою — вона рівно настільки чесна, наскільки чесний трафік, що в неї потрапляє. 90% раптових заторів, які ми бачимо на інфраструктурі evilmail, — це або скомпрометований SASL-логін, що вивалює спам, або один недоступний домен-отримувач, який з'їв усю concurrency. Сліпий flush не лікує жодного з цих сценаріїв: у першому він розганяє розсилку повторно, у другому — марно молотить по мертвому next-hop. Черга — це діагностичний прилад. Навчимося знімати з нього показання.
Що насправді лежить у черзі: каталоги spool
Під /var/spool/postfix живе не одна черга, а шість окремих каталогів, і рух листа між ними — це і є його життєвий цикл. Диригент усього — демон qmgr.
maildrop
— те, що прилетіло через локальний
sendmail
, ще не оброблене.
incoming — лист прийнято (smtpd або cleanup), але qmgr ще не взяв його в роботу.
active — те, що qmgr прямо зараз віддає доставним агентам (smtp, local, virtual). Це вузьке горло: qmgr_message_active_limit = 20000. Більше листів одночасно «в роботі» не буде — решта чекає.
deferred — лист отримав тимчасову помилку (4xx) і чекає повтору. Саме тут розказана вся правда про ваш сервер.
hold — ізоляція. Сюди листи потрапляють вручну (postsuper -h) або за дією HOLD у header_checks/access-мапах; сам собою qmgr нічого тут не тримає.
corrupt — биті файли черги, трапляється рідко.
Deferred розкладена по хеш-підкаталогах 0-9, A-F, тому простий ls по ній марний — для читання є окремі інструменти. Ключове, що треба засвоїти: лист не «висить» в active годинами. Він швидко проскакує active → або sent, або назад у deferred з таймером повтору. Затор — це майже завжди роздута deferred, а не active.
Перший погляд: mailq і швидкий підрахунок
mailq — це синонім postqueue -p. Формат виводу читається так:
Перший рядок — queue id, розмір у байтах, час прийому, відправник. Рядок у дужках — причина, чому лист ще не пішов. Далі — отримувачі. Суфікс id теж інформативний: * — лист зараз в active, ! — на hold.
Точну кількість дають підсумковий рядок mailq або порахунок JSON-об'єктів (по одному на лист):
bash
mailq | tail -1 # -- 812 Kbytes in 8421 Requests.
postqueue -j | wc -l # точна кількість листів у черзі
Старий ідіом mailq | grep -c '^[A-F0-9]' рахує коректно лише для коротких, шістнадцяткових queue id. З увімкненими довгими id (enable_long_queue_ids = yes) частина ідентифікаторів починається з літер поза A-F, і лічильник їх недорахує — тому надійніший postqueue -j | wc -l.
Формат -j (Postfix 3.1+) — по одному JSON-об'єкту на лист, ідеально для скриптів і jq. Розклад «скільки в incoming/active/deferred» одним рядком:
Але щоб зрозуміти *природу* затору, postqueue -p незручна — вона лінійна. Потрібен агрегат.
qshape: рентген затору за 5 секунд
qshape — головний інструмент тріажу, і його чомусь мало хто вмикає в м'язову пам'ять. Він групує чергу за доменом-отримувачем і розкладає по вікових корзинах (у хвилинах):
Читається цей вивід як діагностичний алгоритм. Один домен, у якого майже все накопичилося у старших корзинах (320, 640, 1280+), — це один мертвий next-hop. Пошта туди не йде вже годинами, листи старіють гуртом. Лікування — не чищення, а розбір, чому саме цей MX недоступний, і, можливо, обмеження concurrency саме на нього.
Протилежний патерн — рівномірний приріст у *молодших* корзинах одразу по багатьох доменах — це ознака масової розсилки: хтось щойно почав лити тисячі листів на різні домени. Майже завжди це скомпрометований акаунт. Розбивка за відправниками підтвердить:
bash
qshape -s deferred
Потрібна детальніша картина? qshape -b 20 deferred дасть 20 вікових корзин, а qshape -t 1 deferred зсуне першу корзину до 1 хвилини — видно молодий сплеск у динаміці.
Розбір конкретного листа: postcat і mail.log
qshape вказав на підозрілий домен чи відправника — тепер дивимося конкретний лист. postcat читає його прямо зі spool:
bash
postcat -q 3F2A9B4C1D # заголовки + тіло
postcat -qhe 3F2A9B4C1D # тільки заголовки + envelope, без тіла
Envelope покаже реального sender, recipient і — критично для розслідування — чи є sasl_method/sasl_username. Далі зіставляємо queue id з логом:
bash
grep 3F2A9B4C1D /var/log/mail.log
(на RHEL/AlmaLinux — /var/log/maillog). Ключовий рядок:
Раптовий лідер, якого ви не впізнаєте (або звичайний клієнт зі стократним стрибком), — це ваш зламаний логін. Для добового зведення по всьому серверу тримайте під рукою pflogsumm:
bash
pflogsumm -d today /var/log/mail.log | less
Він одразу покаже топ deferred-причин, топ отримувачів і аномалії відправки без ручного grep.
Три сценарії затору і що з ними робити
1. Один поганий next-hop їсть concurrency.default_destination_concurrency_limit = 20 означає, що до одного домену Postfix тримає до 20 паралельних з'єднань. Якщо цей домен «тупить» на таймаутах, усі 20 слотів висять по 30 секунд, і доставка *для цього домену* деградує. Рішення — не чистити, а обмежити його окремо. У main.cf:
text
example_destination_concurrency_limit = 2
...де example — ім'я transport'а з master.cf. Для по-справжньому проблемних отримувачів заводять окремий повільний транспорт і маршрутизують туди через transport_maps.
2. Скомпрометований логін шле спам. Спершу знаходите sasl_username (див. вище), блокуєте акаунт (інвалідуєте пароль у бекенді автентифікації — у нашому випадку хеш у virtual_users), і лише *потім* чистите його чергу. Порядок критичний: почистите чергу до блокування — акаунт наллє нову за хвилини.
3. Відкладені bounce/backscatter. Прострочені звіти про недоставку акумулюються самі. maximal_queue_lifetime = 5d і bounce_queue_lifetime = 5d приберуть їх без вашого втручання — не поспішайте видаляти вручну те, що і так помре за таймером.
Безпечне керування чергою через postsuper
postsuper — єдиний легальний спосіб чіпати файли черги (руками у spool лізти не можна). Точкові операції:
bash
postsuper -h 3F2A9B4C1D # покласти лист на hold
postsuper -H 3F2A9B4C1D # зняти з hold
postsuper -r 3F2A9B4C1D # requeue: перепройти cleanup (після зміни конфігу)
postsuper -d 3F2A9B4C1D # видалити конкретний лист
-r особливо корисний після правки transport_maps чи main.cf — лист перечитається з новими правилами. Видалення за критерієм робиться через JSON і pipe — postsuper -d - читає id зі stdin:
Ось *так* виглядає безпечне чищення: ви видаляєте рівно листи одного зламаного відправника, не чіпаючи легітимну пошту сусідів. postsuper -d ALL deferred — крайній захід, який стирає і чужі, добросовісно відкладені листи. Якщо все ж потрібно масово прибрати — спершу postsuper -h ALL deferred, потім вибірка й видалення підмножини, і тільки решту -H назад.
Керований flush замість сліпого
Коли next-hop *відновився*, не бийте postqueue -f по всій черзі — це висипле весь deferred в active разом і може перевантажити і вас, і приймача, відкотивши репутацію. Точкові команди:
bash
postqueue -s example.net # flush тільки для одного домену
postqueue -i 3F2A9B4C1D # flush одного листа
postqueue -f # весь deferred — тільки якщо ви впевнені
Після інциденту з компрометацією масовий flush особливо небезпечний: ви повторно проштовхуєте те, що вже почали дропати приймачі, і закріплюєте свій IP у їхніх чорних списках.
Чек-лист чергового: тріаж за 60 секунд
postqueue -j | wc -l — оцінити масштаб.
qshape deferred і qshape -s deferred — один домен чи багато? Старі корзини чи молоді?
Один домен у 320+ → мертвий next-hop: перевірити його MX/DNS, обмежити concurrency, не чистити.
Знайшли акаунт → інвалідувати пароль у бекенді → *потім* postqueue -j | jq … | postsuper -d -.
postcat -qhe <id> + grep <id> mail.log — підтвердити причину, перш ніж щось видаляти.
Next-hop живий → postqueue -s domain, а не -f.
Пороги, за якими бити на сполох
deferred > 1000 при звичайному фоні в десятки — розслідування, не чекати.
Стрибок унікальних `sasl_username` або відправка з акаунта в 10× вище норми — майже напевно компрометація.
Один домен > 70% черги — локальна проблема next-hop, ізолювати транспортом.
Зростання частки `4.7.x rate limited` по великих приймачах (Gmail, Outlook) — проблема репутації IP/домену, а не черги; черга тут лише симптом.
active стабільно впирається у `qmgr_message_active_limit` — qmgr не встигає, дивіться в бік повільних транспортів і concurrency, а не в бік «почистити».