Письмо уходит с вашего сервера с валидной DKIM-подписью, получатель видит pass, а через неделю жалобы: корреспонденция из общего ящика не доходит. Причина обычно не в ключах и не в DNS, а в пересылке: любой промежуточный хоп, который дописывает заголовок или меняет тело, обнуляет подпись. Catch-all домен делает эту проблему системной, потому что через него проходит трафик на десятки несуществующих адресов, и каждый такой маршрут становится точкой, где аутентификация рвется.
Ниже разберем механику DKIM без иллюзий: подпись покрывает не письмо целиком, а конкретный набор заголовков из тега h= и хеш тела bh=, поэтому достаточно одного Received сверху или подставленного disclaimer, чтобы проверка вернула fail. Покажем, как отличить сбой DKIM от проблем SPF и DMARC, почему catch-all почта с пересылкой на внешний адрес гарантированно ломает подпись отправителя и что дает ARC либо переподпись на лету через фильтр в Exim. В финале будет чек-лист: селекторы, ключи, верификация ARC и чтение Authentication-Results, чтобы вы видели реальную картину, а не гадали по логам.
Материал рассчитан на администратора, который держит почтовый сервер и понимает разницу между настройкой в DNS и настройкой на самом MTA. Если вы еще выбираете, включать ли приём почты на любой адрес, начните с понимания, как работает catch-all почта и какие цепочки она создает, иначе отладка DKIM превратится в перебор гипотез.
Что такое DKIM: подпись выбранных заголовков и тела письма
DKIM (DomainKeys Identified Mail) решает узкую задачу: доказать получателю, что письмо с определённым доменом в From действительно прошло через инфраструктуру, которой владелец домена доверяет. Подпись ставит либо сам отправитель, либо его релей (MTA), а проверяющий узел сверяет её с публичным ключом из DNS. Важно сразу отделить мух от котлет: SPF, DKIM и DMARC влияют только на исходящую почту и на репутацию домена. На приём входящих, на работу catch-all домена и на маршрутизацию почты внутри вашего сервера DKIM никак не влияет.
Заголовки в h=, хеш тела в bh= и канонизация
Подпись DKIM-Signature – это набор тегов. Ключевые из них: v=1 (версия), a= (алгоритм, обычно rsa-sha256), d= (домен), s= (селектор), h= (список подписанных заголовков), bh= (base64-хеш тела), b= (сама подпись) и t= (время).
В h= попадает не всё письмо, а перечень имён заголовков. Типовой набор: From, To, Subject, Date, Message-ID, MIME-Version, Content-Type. From обязателен по RFC 6376, остальное – на усмотрение подписанта. Заголовки хешируются в том порядке, в котором перечислены в h=, причём если имя повторяется, берётся соответствующее вхождение снизу вверх. Заголовки, которых нет в h=, не защищены вообще: их можно дописать, изменить или удалить, и подпись останется валидной.
Тело письма целиком не подписывается – считается хеш, и он кладётся в bh=. Получатель пересчитывает хеш по факту и сравнивает. Любое изменение тела, даже пробел в конце строки, ломает совпадение, если не оговорена канонизация.
Канонизация задаётся тегами c=header/body. Режим simple требует побайтового совпадения, включая переводы строк CRLF. Режим relaxed мягче: схлопывает пробельные последовательности, обрезает хвостовые пробелы в заголовках, игнорирует пустые строки в конце тела. На практике почти все ставят relaxed/relaxed – simple слишком хрупок, потому что любой транзитный MTA или фильтр может переформатировать письмо.
Отсюда главное ограничение: DKIM защищает выборочный срез письма, а не конверт и не весь поток. Добавленный сверху заголовок Received или X-Spam-Score подпись не ломает, если его нет в h=. А вот переписанный Subject или вставленный дисклеймер в тело – ломает, потому что и то и другое покрыто подписью. Именно поэтому пересылка через алиас или catch-all почту, которая принимает письма на любой нераспознанный адрес домена, регулярно приводит к DKIM fail: промежуточный сервер меняет то, что подписчик зафиксировал.
Ещё один нюанс: подпись не привязана к IP. A-запись сопоставляет имя почтового хоста с IP-адресом, MX-запись направляет всю входящую почту домена на один сервер, но DKIM опирается только на d= и s= и на TXT-запись вида selector._domainkey.domain. Поэтому ключ можно ротировать, не трогая DNS-записи маршрутизации, а старый селектор держать живым, пока не истечёт срок жизни подписанных им писем в очередях.
Как проверяется DKIM на стороне получателя: селекторы, DNS и политика
Проверка DKIM начинается с заголовка DKIM-Signature, который добавил отправитель. В нем есть тег s=, это селектор. Получатель берет домен из d=, селектор из s= и строит DNS-запрос вида selector._domainkey.example.com, тип TXT. В ответ приходит строка с версией (v=DKIM1), алгоритмом (k=rsa), флагами и публичным ключом в теге p=. Если TXT-записи нет или ключ отозван (пустой p=), проверка сразу дает permfail.
Дальше сервер восстанавливает подписанные данные. По тегу h= он выбирает перечисленные заголовки в указанном порядке, по тегу bh= сверяет хеш тела, по b= снимает саму подпись. Канонизация задается тегами c=: simple требует побайтового совпадения, relaxed нормализует пробелы и переводы строк. Затем публичный ключ применяется к вычисленному хешу. Совпало, результат pass, нет, fail. Важная деталь: DKIM не покрывает письмо целиком, только то, что перечислено в h= и хеш тела. Заголовки, добавленные по пути, в подпись не входят, и это нормально.
Что происходит при fail, решает не отправитель, а политика получателя. Обычно она задается в DMARC-записи домена через p=none, p=quarantine или p=reject, но крупные провайдеры применяют и собственные правила репутации. При none письмо все равно доставляется, при quarantine уходит в спам, при reject отклоняется на SMTP-уровне. Отличить DKIM-сбой от проблем SPF и DMARC проще всего по агрегированному заголовку Authentication-Results, который получатель добавляет к письму:
- dkim=pass или dkim=fail с указанием домена и селектора, которые проверялись.
- spf=pass или spf=fail с IP-адресом, который сверяли с SPF-записью.
- dmarc=pass или dmarc=fail с указанием, на каком механизме (spf или dkim) держится выравнивание.
- arc=pass или arc=fail, если получатель поддерживает ARC и письмо прошло через пересылку.
Практический вывод: если dkim=fail, а spf=pass и dmarc=pass, домен в порядке, проблема в модификации письма по пути, чаще всего при пересылке. Если spf=fail и dkim=fail одновременно, смотрите на IP отправителя и на то, не переписан ли обратный адрес. Бывает и так, что подпись формально валидна, но не выровнена с доменом From, тогда DMARC упадет при живом DKIM. Проверяйте все три строки сразу, а не только dkim.
Отдельно про селекторы: их можно держать несколько (например, s1 и s2), и это удобно при ротации ключей. Старый селектор оставляют в DNS на время, пока письма в очередях не разойдутся, потом удаляют. Если в панели хостинга ключ сгенерирован, но TXT-запись не опубликована или опубликована с опечаткой в имени, проверка будет стабильно падать с permfail, и никакая настройка catch-all или алиасов этого не исправит. DNS-запись должна быть видна снаружи, проверяйте ее через публичный резолвер, а не только на локальном сервере.
Почему пересылка через алиас ломает DKIM-подпись
DKIM-подпись защищает не всё сообщение, а только перечисленные в теге h= заголовки и тело в той форме, в какой они существовали в момент подписи. Любое вмешательство в этот набор после отправки делает проверку недействительной, и получатель увидит dkim=fail. Пересылка через алиас почти всегда вносит такое вмешательство, поэтому разрыв подписи при форвардинге – норма, а не сбой настройки.
Разберём по пунктам, что именно ломается.
- Добавление заголовков. Пересылающий сервер ставит сверху свой Received, часто дописывает X-Forwarded-To, X-Forwarded-For, X-Original-To, Delivered-To. Если ни один из них не входит в h=, подпись формально не страдает. Но многие подписанты включают Received в h= или используют перекрытие заголовков (тег z=), и тогда новая строка ломает сверку b=.
- Переписывание From и Subject. Алиас может менять конверт, но некоторые пересыльщики правят и заголовок From, добавляя метку вида “через example.net”, а Subject получает префикс “[Fwd]”. From и Subject почти всегда входят в h=, потому что от них зависит DMARC-выравнивание. Одно изменение – и подпись невалидна.
- Изменение тела. Любая правка тела рушит хеш bh=. Типичные виновники: корпоративные дисклеймеры, приписки антивирусных шлюзов, вставка рекламного блока, перекодировка base64 в quoted-printable, переразбивка длинных строк. Даже один добавленный пробел в конце строки при канонизации simple даёт провал.
- Канонизация. Режим relaxed прощает изменения пробелов и регистра заголовков, simple не прощает ничего. Пересыльщик, который “нормализует” письмо, безопасен для relaxed и губителен для simple.
Отдельная история – catch-all почта. Когда на домене включён catch-all, сервер принимает письма на любой нераспознанный локальный адрес: опечатки вроде info@exmaple.ru, адреса из старых баз, случайные комбинации, собранные линкбилдингом. Правило catch-all складывает всё это в единый ящик. что такое catch-all домен даёт больше информации по этому вопросу. Дальше владелец ящика либо читает письма через IMAP, либо настраивает пересылку на внешний адрес, и вот тут начинается цепочка: входящее письмо с валидной DKIM-подписью уходит на gmail.com уже с другого IP и с новыми заголовками. Gmail проверяет DKIM по домену отправителя, видит fail, смотрит SPF – там тоже не совпадает, потому что отправлял ваш сервер, а не исходный. DMARC при этом опирается на выравнивание, и письмо уходит в спам или отклоняется.
Важно понимать, что SPF, DKIM и DMARC влияют только на исходящую почту с точки зрения домена-отправителя. Пересылка превращает входящее письмо в исходящее от вашего имени, но подписать его своим ключом вы не можете, не имея права на домен в From. Отсюда и берётся задача, которую решает ARC: зафиксировать, что письмо пришло с валидной аутентификацией, а испортили её именно вы, пересылая. Об этом в следующем разделе.
ARC и переподпись на лету: как сохранить доставляемость пересылаемых писем
Когда письмо проходит через пересылку, исходная DKIM-подпись чаще всего уже невалидна. Получатель видит dkim=fail и вынужден решать: отбросить письмо или пропустить, рискуя впустить спам. ARC (Authenticated Received Chain) решает эту проблему иначе, чем переподпись: он не восстанавливает подпись, а фиксирует историю проверок. Каждый узел цепочки, принявший письмо, добавляет свой набор заголовков.
В наборе три элемента. ARC-Authentication-Results (AAR) содержит снимок Authentication-Results на момент приёма: что показали SPF, DKIM и DMARC у этого узла. ARC-Message-Signature (AMS) это DKIM-подпись, но покрывает она не только письмо, а и все предыдущие ARC-заголовки. ARC-Seal (AS) криптографически запечатывает всю цепочку: количество пройденных узлов (cv), свой домен и подпись. Получатель проверяет печати по порядку: если цепочка цела, а промежуточные узлы в списке доверенных, то fail исходного DKIM можно трактовать как следствие пересылки, а не подделки.
Ключевая оговорка: ARC не спасает от подделки, он лишь переносит доверие на пересылающий сервер. Если промежуточный узел сам пропускает спам, его печать легитимизирует мусор. Поэтому крупные провайдеры доверяют ARC только от известных пересыльщиков, а для неизвестных цепочка игнорируется. Практический выигрыш в другом: письма из рассылок и линкбилдинг-переписки, идущие через корпоративные алиасы, перестают массово попадать в спам.
Альтернатива, которая работает без поддержки на стороне получателя, это переподпись DKIM на лету. В Exim её делает DKIM-фильтр (например, dkim-filter или встроенный модуль в связке с OpenDKIM в режиме подписи): при пересылке письмо пересобирается, старые DKIM-Signature при необходимости снимаются, ставится новая подпись домена пересыльщика. Получатель видит dkim=pass от вашего домена, и вопрос доверия к чужой подписи снимается. Минус в том, что вы берёте на себя ответственность за содержимое: если через ваш catch-all пройдёт фишинг, репутация упадёт именно у вашего домена, а SPF, DKIM и DMARC будут в глазах получателя вашими собственными.
Критерии выбора простые. Если вы пересылаете преимущественно свою корреспонденцию и готовы отвечать за контент, переподпись даёт предсказуемый результат уже сегодня. Если вы строите пересылку для чужих писем (алиасы, catch-all почта, единый ящик), ставьте ARC-верификацию и ARC-подпись: она не подменяет ответственность, а честно передаёт цепочку дальше. Оптимально держать оба механизма: ARC для входящей пересылки, DKIM-переподпись для собственной исходящей почты и для писем, которые вы принимаете на нераспознанную локальную часть и затем отправляете дальше.
Настройка catch-all и DKIM на практике: чек-лист для администратора
Практика начинается с понимания, где что лежит. Catch-all настраивается на почтовом сервере, а не в DNS: MX-запись лишь направляет всю входящую почту домена на один сервер, а уже он решает, что делать с нераспознанной локальной частью. В панели хостинга (Postfix Admin, DirectAdmin, cPanel, ISPConfig) это обычно галочка “catch-all” у домена или запись вида @domain.tld в списке ящиков. До включения проверьте, что основной ящик существует и квота не нулевая, иначе письма на любой адрес начнут молча отбиваться.
Дальше ключи. Сгенерируйте пару для каждого домена, минимум 2048 бит RSA (Ed25519 быстрее, но не все приёмники умеют проверять):
Селектор mail2024 попадёт в s= подписи, а публичный ключ – в TXT-запись mail2024._domainkey.example.ru. Не забудьте про A-запись: она сопоставляет имя почтового хоста с IP-адресом, и без неё часть приёмников не станет проверять подпись. SPF, DKIM и DMARC влияют только на исходящую почту, поэтому на маршрутизацию входящих и на работу catch-all они никак не действуют – это частая ошибка при диагностике.
ARC-верификация включается на том же MTA. В OpenDKIM это ArcSeal и ArcVerify в конфиге, в Rspamd – модуль arc с режимом add/verify. Логика простая: если вы пересылаете письма дальше, ставьте режим “add” (сервер добавит AAR, AMS и AS), если принимаете – “verify”. Проверка цепочки не отменяет DKIM-fail отправителя, но даёт следующему звену информацию, на основании которой можно принять письмо.
Альтернатива для тех, кто пересылает через Exim: DKIM-фильтр на лету. В маршрутизаторе для алиаса ставится опция dkim_sign с вашим селектором, и письмо уходит уже с вашей подписью. Минус – вы становитесь ответственным за содержимое, плюс – подпись гарантированно валидна для получателя. Выбор зависит от объёма: до нескольких тысяч писем в сутки проще переподпись, при больших потоках дешевле ARC.
Тест занимает пять минут. Отправьте письмо с внешнего ящика (Gmail, Mail.ru) на несуществующий адрес вашего домена, дождитесь попадания в единый ящик и откройте исходник. В заголовках должно быть:
- Authentication-Results с dkim=pass (или fail, если пересылка была внешней) и arc=pass;
- ARC-Authentication-Results с зафиксированным результатом на момент приёма;
- отсутствие 550 no such user в логах – если он есть, catch-all не сработал.
Проверьте и обратный сценарий: напишите с catch-all ящика наружу и убедитесь, что исходящая почта подписана вашим селектором. Если в Authentication-Results стоит dkim=pass, но dmarc=fail, проблема в alignment домена, а не в ключах.
| Шаг | Где настраивается | Признак успеха |
|---|---|---|
| Catch-all | Почтовая панель хостинга | Письмо на любой адрес доставлено |
| DKIM-ключ | TXT в DNS + MTA | dkim=pass в заголовках |
| ARC | OpenDKIM или Rspamd | arc=pass после пересылки |
| Переподпись | Exim-фильтр | Новая подпись при пересылке |
И последнее: включённый catch-all собирает спам и письма с опечаткой в адресе в один ящик. Настройте фильтры по адресу получателя и раз в неделю просматривайте нераспознанную локальную часть – по ней хорошо видно, какие адреса люди реально набирают, и это полезнее любого линкбилдинга.
Практический итог: начните с одного теста, а не с переделки всей инфраструктуры. Возьмите ящик на внешнем сервисе (Gmail или Яндекс), отправьте с него письмо на адрес catch-all вашего домена, затем перешлите это письмо дальше на третий адрес и откройте полный исходник в конечном ящике. Смотрите три блока: Authentication-Results у самого верхнего Received (это вердикт принимающего сервера), строку dkim= с указанием селектора и домена и наличие заголовков ARC-Authentication-Results, ARC-Message-Signature, ARC-Seal. Если после пересылки видите dkim=fail при spf=pass и живую цепочку ARC с arc=pass, доставляемость уже защищена, и трогать переподпись не нужно. Если ARC отсутствует или arc=fail, выбирайте: включить ARC-верификацию на своём MTA (Exim, Postfix с milter, rspamd) либо настроить DKIM-фильтр для переподписи исходящих пересланных писем. Второй путь даёт больше контроля над телом письма, но требует хранения приватного ключа селектора на пересылающем узле и аккуратной ротации: старый TXT с ключом держите в DNS минимум 7 дней после смены, иначе часть получателей увидит dkim=permerror.
Отдельно проверьте, что catch-all не превращается в мусорный ящик. Правило catch-all складывает все письма в единственный ящик, поэтому настройте фильтры по адресу получателя: складывайте письма на нераспознанную локальную часть в отдельную папку, а не в INBOX, и раз в неделю просматривайте её на предмет опечаток в адресе от реальных клиентов. Проверьте, что A-запись почтового хоста указывает на актуальный IP, а MX-запись направляет всю входящую почту домена на один сервер, который отвечает 550 no such user только для адресов вне зоны catch-all. Если домен используется для линкбилдинга и рассылок, держите catch-all выключенным на основном домене и включите его на отдельном поддомене: так спам и ответы на холодные письма не смешаются с перепиской по проектам, а исходящая почта останется чистой с точки зрения SPF, DKIM и DMARC.