Квоти в Dovecot без сюрпризів: жорсткі й м'які ліміти, попередження та статистика
Квота — це не одне число, а конвеєр рішень: де перевіряти ємність, коли попереджати користувача й коли жорстко відхиляти доставку. Розбираємо count-backend, чому в Dovecot немає справжнього soft-quota, чому reject на SMTP-час рятує репутацію домену, і як зібрати статистику через doveadm.
EvilMail Team10 липня 2026 р.10 хв читання
Коли скринька повна, винним виглядає сервер
Класична картинка з підтримки: клієнт скаржиться, що йому «не доходять листи», а відправник присягається, що надіслав. Ви лізете в лог і бачите 552 5.2.2 Mailbox is full. Скринька справді переповнена — але поки ви цього не знаєте, здається, що зламався сервер.
Гірший сценарій — коли ваш Postfix спершу приймає лист у чергу, а потім, при спробі покласти його в Maildir, отримує відмову по квоті й генерує bounce. Тепер уже ваш сервер розсилає відбивки на адреси, які часто підроблені (backscatter). Репутація домену просідає через одну переповнену скриньку.
Базовий Dovecot тут не помічник: без quota-плагіна він взагалі не рахує ємність. Скриньки ростуть, поки /var/mail/vhosts не впреться в межу розділу — і тоді доставка ламається одразу для всіх користувачів, а не для одного. Квота — це не одне число в конфізі. Це конвеєр рішень: де перевіряти, коли попереджати, коли жорстко відхиляти й куди писати статистику. Розберемо його по ланках.
Як Dovecot рахує квоту: backend вирішує все
Перше рішення — чим саме рахувати зайнятий об'єм. У Dovecot 2.3 є кілька backend'ів:
`count` — рахує розмір із індексних файлів, нічого не скануючи. Найшвидший.
`maildir` (maildir++) — тримає підсумок у файлі maildirsize усередині Maildir.
`dict` — зберігає лічильники у зовнішньому словнику (SQL/Redis), потрібен для спільної квоти на кілька скриньок або кластера.
`fs` — делегує підрахунок квоті файлової системи.
Для нових інсталяцій у 2026 беріть count з quota_vsizes = yes. Він читає віртуальний розмір повідомлень з індексів, не скануючи весь Maildir на кожну доставку, і коректно переживає перезавантаження. Головна пастка maildir-backend — той самий maildirsize: він розсинхронізується при паралельних доставках чи ручному втручанні в Maildir, і ви отримуєте «примарно повні» скриньки, у яких насправді півобсягу вільно.
Плагін вмикається так:
mail_plugins = $mail_plugins quota imap_quota
imap_quota окремо потрібен, щоб клієнти (Thunderbird, Apple Mail, Roundcube) показували шкалу заповнення через IMAP QUOTA (RFC 2087) — користувач бачить «зайнято 1.8 з 2 ГБ» без дзвінка в підтримку.
Базова конфігурація: 90-quota.conf і per-user ліміти
Мінімальний робочий конфіг у /etc/dovecot/conf.d/90-quota.conf:
quota_rule = *:storage=2G задає загальний ліміт 2 ГБ на всі папки. quota_rule2 для Trash додає +10%% понад базу — щоб видалення листів не впиралося миттєво у ту саму стелю. Подвійний %% тут обов'язковий: у конфігах Dovecot одинарний % — керуючий символ (як у %u), і %% екранує його до літерального відсотка.
Тримати ліміти в статичному файлі погано, щойно у вас більше одного тарифу. У середовищі з virtual users правильніше віддавати ліміт із userdb: додайте в SQL-запит поле quota_rule, наприклад *:storage=5G для платного плану. Тоді per-user override живе в базі поряд із паролем, а не в конфізі, і зміна тарифу — це один UPDATE.
Жорсткий ліміт vs «м'який»: чого Dovecot насправді не має
Скажу прямо, бо на цьому спотикаються: справжнього soft-quota в Dovecot немає. Немає окремого «м'якого числа», після якого доставка ще йде, а якийсь лічильник тікає. Є рівно два механізми, з яких і збирається ілюзія м'якості.
Перший — жорсткий ліміт (quota_rule ... storage). При перевищенні доставка відхиляється, крапка. Це захист диска: він гарантує, що одна скринька не з'їсть увесь розділ.
Другий — грейс (quota_grace = 10%%). Це маленька зона понад жорсткий ліміт, у яку ще пролазять дрібні повідомлення. Її єдине призначення — щоб службові й попереджувальні листи («у вас закінчується місце») самі не вперлися в квоту й дійшли до користувача. Грейс не для того, щоб «дозволити ще трохи попрацювати» — це технічний буфер.
«М'якість», яку відчуває користувач, дає не грейс, а попереджувальні пороги: на 80% і 95% людина отримує лист і встигає прибрати вкладення до того, як доставка обірветься. Розділяйте дві мети: жорсткий ліміт захищає файлову систему, а грейс плюс попередження захищають користувача від раптового обриву. Робочі числа для більшості інсталяцій: грейс 10%, попередження на 80 і 95%.
Правильне місце відхиляти — SMTP-час, а не bounce
Це головна інженерна порада статті. Якщо переповнення виявляється після прийому листа в чергу, у вас лишається два поганих виходи: згенерувати bounce (backscatter, шкода репутації) або залишити лист гнити в deferred-черзі. Правильно ж відхиляти ще на RCPT TO, поки лист тримає сервер-відправник — тоді відбивку робить він, а не ви.
Dovecot дає для цього готовий policy-сервіс quota-status. Опишіть його в 10-master.conf:
service quota-status {
executable = /usr/lib/dovecot/quota-status -p postfix
unix_listener /var/spool/postfix/private/quota-status {
user = postfix
}
client_limit = 1
}
Сокет свідомо кладемо всередину /var/spool/postfix/private/, щоб chroot'нутий Postfix його бачив. У main.cf додаємо перевірку в smtpd_recipient_restrictions — строго післяreject_unauth_destination, інакше рискуєте питати квоту для чужих доменів:
Відповіді сервісу задаються тими самими quota_status_* з 90-quota.conf вище: DUNNO означає «не моя справа, пропускай далі по ланцюжку», а overquota повертає 552 5.2.2 Mailbox is full прямо в SMTP-діалог. Різниця в підсумку проста: reject-до-черги — це відмова, яку бачить і обробляє відправник; bounce-після-черги — це ваш сервер, що розсилає сміття від свого імені.
Попереджувальні листи при заповненні
Порогові події вже оголошені в 90-quota.conf (quota_warning на 95% і quota_warning2 на 80%). Кожна з них викликає сервіс quota-warning, який теж треба описати:
service quota-warning {
executable = script /usr/local/bin/quota-warning.sh
user = dovecot
unix_listener quota-warning {
user = vmail
group = vmail
mode = 0660
}
}
Сам скрипт формує лист і кладе його через dovecot-lda. Одна деталь тут коштує людям годин налагодження — прапорець noenforcing:
bash
#!/bin/sh
PERCENT="$1"; USER="$2"
cat <<EOF | /usr/lib/dovecot/dovecot-lda -d "$USER" \
-o "plugin/quota=count:User quota:noenforcing"
From: [email protected]
Subject: Ваша скринька заповнена на ${PERCENT}%
Content-Type: text/plain; charset=UTF-8
Скринька ${USER} заповнена на ${PERCENT}%. Звільніть місце
або видаліть великі вкладення, інакше нові листи перестануть надходити.
EOF
Логіка проста, але контрінтуїтивна: попередження надсилається саме тому, що скринька майже повна — а без noenforcing цей лист сам упреться в квоту й не дійде. noenforcing каже Dovecot доставити конкретно це повідомлення повз перевірку ліміту.
Дублів боятися не треба. Dovecot надсилає попередження лише при перетині порогу вгору — коли зайнятий об'єм переходить межу знизу вверх. Поки скринька висить на 82%, десятки нових листів не породять десятки попереджень. А коли користувач прибере пошту й потім знову дорасте до 80% — прийде нове. Скрипт має бути chmod +x і належати так, щоб користувач dovecot міг його запускати.
Збір статистики використання
Квоти без спостереження перетворюються на «поставив і забув», поки розділ не заповниться. Точкова перевірка однієї скриньки:
Вивід дає колонки Type (STORAGE / MESSAGE), Value, Limit, % — одразу видно і зайняте, і стелю, і відсоток. Для регулярного зняття по всіх користувачах у cron зручний машинний формат:
bash
doveadm -f tab quota get -A
Такий вивід легко парсити скриптом і писати у Prometheus node_exporter через textfile-collector: тоді у вас з'являється дашборд «топ скриньок за заповненням» і алерт «домен близько до межі диска» ще до того, як щось відмовить. У Dovecot є і власний openmetrics-експортер:
service stats {
inet_listener http {
port = 9900
}
}
Але за квотами він дає небагато — практичніше саме парсити doveadm quota get -A, бо там уже готові ліміти й відсотки на користувача.
Коли цифри в doveadm явно розійшлися з реальністю (наслідок збою, ручного переносу Maildir чи старого maildirsize), лічильник перебудовується: