OpenSMTPD як легка альтернатива Postfix: мінімалістичний smtpd.conf
Postfix — це шість демонів і main.cf з сотнями параметрів, з яких ви чіпаєте двадцять. Для одного-двох доменів OpenSMTPD розв'язує ту саму задачу конфігом на 30 рядків. Робочий smtpd.conf із submission, TLS, LMTP у Dovecot і DKIM — та чесні межі, де варто повернутись на Postfix.
EvilMail Team10 липня 2026 р.13 хв читання
Відкрийте master.cf типової інсталяції Postfix — там понад 130 рядків. Додайте main.cf, де живуть 800+ можливих параметрів, і ви реально редагуєте з них десь двадцять. За цим стоять щонайменше шість процесів: master, pickup, cleanup, qmgr, smtpd, smtp, local. Плюс окремий saslauthd для автентифікації і окремий opendkim як milter. Для сервера, який обслуговує 1–3 домени й кілька десятків скриньок, це не потужність — це поверхня, на якій легко зробити помилку конфігурації, що коштує репутації IP.
OpenSMTPD замість Postfix: мінімальний smtpd.conf для малого сервера — EvilMail Blog
OpenSMTPD (демон
smtpd
з проєкту OpenBSD, портований на Linux пакетом
opensmtpd
) розв'язує ту саму задачу однією граматикою:
smtpd.conf
, який вміщується в екран і читається як звичайні речення. Нижче — робочий, безпечний конфіг на ~30 рядків із submission на 587, TLS від Let's Encrypt, LMTP-доставкою в Dovecot і DKIM-підписом. І чесна межа: де OpenSMTPD впирається в стелю й де ви повернетесь на Postfix.
Чому smtpd, а не postfix — і коли навпаки
Різниця не в швидкості й не в «сучасності». Різниця у площі складності.
Postfix: master керує сім'єю процесів, дві мови конфігу (main.cf — параметри, master.cf — сервіси), десятки lookup-таблиць, зовнішні saslauthd і milter-и для DKIM/rspamd.
OpenSMTPD: один демон smtpd, одна декларативна граматика «оголоси action → напиши match». Автентифікація вбудована, фільтри — через рідний filter-hook API, без milter-сокетів.
Чесний абзац про межі. Якщо вам потрібні складні transport maps (різні next-hop за одержувачем), уся milter-екосистема, черги на мільйони листів або багатоланкові content-фільтри — лишайтеся на Postfix, він для цього практичніший. Для 1–3 доменів, менш ніж ~500 скриньок і стандартного стеку submission + LMTP smtpd виграє за читабельністю й площею атаки: менше рядків, менше демонів, менше місць, де конфіг мовчки перетвориться на open relay.
Одне попередження, яке зекономить вам годину: граматика конфігу ламано змінилася у версії 6.4 (2018). Старий синтаксис accept ... deliver to ... мертвий; сучасний — це action/match. Практично всі гайди в інтернеті, старші за 2019 рік, некоректні. На 2026-й у Debian/Ubuntu приїжджає портована 7.x гілка — усе нижче написано під неї.
Модель конфігурації: action і match
Уся ментальна модель smtpd.conf тримається на двох половинах.
Спершу ви оголошуєте іменовані дії — action. Дія відповідає на питання «що фізично зробити з листом»: доставити через lmtp у Dovecot, покласти в maildir/mbox, або віддати назовні через relay.
Потім ви пишете правила `match` — вони вирішують, який лист потрапляє в яку дію. Правила проходяться зверху вниз, перший збіг перемагає — рівно як у pf. Ключові предикати: from local, from any, auth, for local, for domain, for rcpt-to.
Найважливіше, що треба усвідомити з першого дня: вихідна пошта (`from local`, `auth`) і вхідна (`for domain`) — це принципово різні гілки. Плутанина між ними — це відкритий релей, через який за добу розсилатимуть спам від вашого імені.
Мінімальний робочий smtpd.conf
Осьцентральний код. Це повний конфіг для одного домену evilmail.pro з віртуальними користувачами, LMTP у Dovecot і DKIM на submission. Файл — /etc/mail/smtpd.conf.
bash
# /etc/mail/smtpd.conf
# TLS-сертифікати з Let's Encrypt
pki mail.evilmail.pro cert "/etc/letsencrypt/live/mail.evilmail.pro/fullchain.pem"
pki mail.evilmail.pro key "/etc/letsencrypt/live/mail.evilmail.pro/privkey.pem"
# Таблиці: аліаси, облікові дані SASL, віртуальні домени й користувачі
table aliases file:/etc/mail/aliases
table creds file:/etc/mail/creds
table vdomains file:/etc/mail/vdomains
table vusers file:/etc/mail/vusers
# DKIM-фільтр (пакет opensmtpd-filter-dkimsign)
filter dkimsign proc-exec \
"filter-dkimsign -d evilmail.pro -s mail -k /etc/mail/dkim/mail.key"
# Вхідний MX на 25 — без auth, TLS опортуністичний
listen on eth0 port 25 tls pki mail.evilmail.pro hostname mail.evilmail.pro
# Submission на 587 — auth обов'язковий, TLS обов'язковий, підпис DKIM
listen on eth0 port 587 tls-require pki mail.evilmail.pro \
auth <creds> filter dkimsign hostname mail.evilmail.pro
# Дії
action "local_mail" lmtp "/var/run/dovecot/lmtp" rcpt-to virtual <vusers>
action "outbound" relay helo mail.evilmail.pro
# Правила (зверху вниз, перший збіг)
match from any for domain <vdomains> action "local_mail"
match from local for any action "outbound"
match auth for any action "outbound"
Розбір по блоках:
`pki` прив'язує ім'я хоста до пари cert/key. Одна декларація обслуговує всі listener-и, що посилаються на mail.evilmail.pro.
`listen on eth0 port 25` — вхідний потік від чужих MX. Тут tls (не tls-require), бо MX-и в дикій природі часто не вміють STARTTLS, і форсувати його на вході — це втрачати пошту.
`listen ... port 587 tls-require ... auth <creds>` — сюди ходять ваші клієнти. tls-require не пускає нікого без шифрування; auth <creds> вмикає SASL проти таблиці. filter dkimsign вішається лише тут — підписуємо тільки те, що вилетіло автентифікованим користувачем.
`action "local_mail" lmtp` віддає лист у Dovecot через unix-сокет; rcpt-to virtual <vusers> мапить адресу одержувача на доставний акаунт.
`match from any for domain <vdomains>` — оце і є запобіжник від open relay. Приймаємо ззовні лист лише якщо одержувач у наших доменах. Заберіть for domain — і ви ретранслюєте на весь інтернет.
Хочете 465 (implicit TLS замість STARTTLS) — додайте listen on eth0 port 465 smtps pki mail.evilmail.pro auth <creds> filter dkimsign.
Автентифікація, віртуальні користувачі й таблиці
smtpd робить SASL без окремого saslauthd. Уся автентифікація — це таблиця, на яку вказує auth <creds>.
Формат /etc/mail/creds — логін:хеш, по рядку на користувача. Хеш генерується вбудованою утилітою:
Логін у creds концептуально окремий від адреси доставки. У /etc/mail/vusers ви мапите одержувача на реальний доставний акаунт — типово це vmail з uid/gid 5000 (той самий, що тримає maildir у Dovecot-стеку):
Для сотень скриньок file-таблиці стають незручними — smtpd вміє table ... db: (Berkeley DB, перебудова через makemap), а в портованій версії й postgres:/ldap: бекенди. Але для десятків адрес плоский файл чесніший: його видно очима, він у git, у ньому нема прихованого стану.
DKIM, SPF і DMARC без окремого milter
Головна прикладна перевага сучасного smtpd — filter-hook API замість milter-сокетів. DKIM підключається пакетом opensmtpd-filter-dkimsign (він же — портований filter-dkimsign з OpenBSD).
Згенеруйте ключ і покладіть його поруч із конфігом:
Ще раз, бо це найчастіша помилка: `filter dkimsign` живе тільки на listener 587/465, ніколи на :25. Якщо повісити його на вхідний потік, ви підпишете своїм ключем чужу пошту, яка транзитом проходить крізь вас, — і своїми руками зіпсуєте власну DKIM-репутацію.
TLS, сертифікати й перевірка ззовні
pki-блок вказує на fullchain.pem і privkey.pem від certbot. Важливий нюанс: smtpdне перечитує сертифікат сам після оновлення — потрібен reload. Прив'яжіть його до renew:
bash
certbot renew --deploy-hook "smtpctl reload"
# або, залежно від дистрибутива:
certbot renew --deploy-hook "systemctl reload opensmtpd"
Забудете deploy-hook — через 90 днів сервер віддаватиме прострочений cert, і чужі MX почнуть відхиляти TLS. Перевірка того, що listener віддає правильний ланцюг:
MTA-STS і DANE/TLSA — окрема історія на боці DNS та /.well-known/. smtpd коректно презентує сертифікат; політику STS ви публікуєте самі, smtpd її не генерує.
Черга, логи й діагностика в проді
Уся експлуатація йде через smtpctl:
bash
smtpctl show queue # що застрягло і чому
smtpctl schedule all # форсувати негайну доставку всієї черги
smtpctl remove <envid> # видалити конкретний конверт
smtpctl show stats # лічильники smtp-in/out, uptime
smtpctl monitor # живий потік подій
Логи традиційно в /var/log/maillog, а на systemd-дистрибутивах — у journald:
bash
journalctl -u opensmtpd -f
Рядки тегуються smtp-in (вхід), smtp-out/mta (вихід), mda (локальна доставка). Три граблі, на які натикаються майже всі:
Права на LMTP-сокет.smtpd має вміти писати в /var/run/dovecot/lmtp. Якщо ні — листи капають у чергу зі статусом Connection refused. Перевірте service auth-userdb/lmtp секцію Dovecot і власника сокета.
Firewall на 587. Клієнт «не може надіслати», але отримує пошту — класична ознака закритого submission-порту. 25 відкритий, 587 — ні.
Рестарт наосліп після правки. Синтаксична помилка валить демон під час reload, і сервер лягає мовчки — завжди спершу smtpd -n, і тільки потім reload.
Чек-лист запуску
1.smtpd -n -f /etc/mail/smtpd.conf повертає configuration OK.
2.PTR/rDNS для 203.0.113.10 вертає mail.evilmail.pro, і це збігається з hostname у listener-ах та HELO.
3.SPF, DKIM (селектор mail) і DMARC опубліковані й проходять зовнішню перевірку.
4.Порт 587 приймає лише auth + tls-require; порт 25 не є open relay — тест from any for domain на свій домен проходить, на чужий — 550 Relaying denied.
5.LMTP-доставка в Dovecot працює: тестовий лист лягає в maildir vmail (5000:5000).
6.certbot renew --deploy-hook "smtpctl reload" налаштований і протестований --dry-run.
7.Після тестового вихідного листа smtpctl show queue порожня.
8.journalctl -u opensmtpd не сипле warn/error.
Коли ловите себе на тому, що дописуєте четверту action ... relay via під різні next-hop, підключаєте rspamd повноцінним фільтром і воюєте з чергою на десятки тисяч листів — це сигнал, що ви переросли зону комфорту smtpd і Postfix знову стане практичнішим. До того моменту тридцять рядків, які видно очима, надійніші за 800 параметрів, з яких ви пам'ятаєте двадцять.