DKIM e DMARC no Postfix com OpenDKIM e OpenDMARC

Mascote do LinuxPro carimbando um lacre de cera com uma chave nos envelopes que saem do servidor de e-mail, enquanto um portal com escudo confere as mensagens e o cão caramelo observa

Servidor de e-mail sem DKIM e DMARC em 2026 é servidor com mensagem indo para o spam — ou voltando. Desde fevereiro de 2024, Gmail e Yahoo exigem autenticação de todo remetente, e a partir de novembro de 2025 o Gmail passou a recusar, temporária ou definitivamente, o tráfego fora das regras. Abaixo, o caminho completo num Postfix: SPF, assinatura DKIM com OpenDKIM, política DMARC e a verificação do lado de quem recebe com OpenDMARC. Tudo testado em laboratório no Ubuntu 24.04 e no 26.04.

O que os grandes provedores exigem

As regras que empurraram todo mundo para isso:

  • Gmail, desde 1º de fevereiro de 2024: todo remetente precisa de SPF ou DKIM, DNS reverso (PTR) válido, TLS e taxa de spam abaixo de 0,3%. Quem manda mais de 5.000 mensagens por dia para contas Gmail precisa de SPF e DKIM, DMARC publicado (p=none basta), From: alinhado e descadastro em um clique nos e-mails de marketing. Em novembro de 2025 o Google começou a endurecer a aplicação, com recusas temporárias e permanentes.
  • Yahoo: as mesmas exigências desde fevereiro de 2024, sem divulgar um número mínimo de mensagens para ser considerado envio em massa.
  • Outlook.com: desde 5 de maio de 2025, domínios que mandam mais de 5.000 mensagens por dia precisam de SPF, DKIM e DMARC. Quem não tem vai para o lixo eletrônico.

Mesmo quem manda pouco ganha com os três: sem eles, qualquer um pode mandar e-mail com o seu domínio no From:, e o destino não tem como distinguir.

As três peças e o alinhamento

  • SPF (RFC 7208): um registro TXT no domínio lista quais IPs podem entregar correio em nome dele. O destino compara com o IP que abriu a conexão. Vale para o endereço do envelope (MAIL FROM), não para o From: que o leitor vê.
  • DKIM (RFC 6376): o servidor de saída assina cabeçalhos e corpo com uma chave privada; a chave pública fica no DNS. Se alguém mexer na mensagem no caminho, a assinatura quebra. Sobrevive a encaminhamento, o que o SPF não faz.
  • DMARC: amarra os dois ao From:. Exige que SPF ou DKIM passe e que o domínio validado seja o mesmo do From: — isso é o alinhamento. Também diz ao destino o que fazer quando falha (none, quarantine, reject) e para onde mandar relatórios. Em maio de 2026 a especificação virou padrão da IETF como RFC 9989, com os relatórios nas RFCs 9990 e 9991, substituindo a RFC 7489.

Diagrama do fluxo: o Postfix entrega a mensagem ao OpenDKIM, que assina com a chave privada; o servidor de destino verifica SPF, DKIM e DMARC consultando três registros TXT no DNS do domínio e decide entre entregar, mandar para o spam ou recusar

Neste post: domínio example.com, servidor mail.example.com, IP de saída 203.0.113.10. Troque pelos seus.

Versões e o detalhe do chroot

Pacote Ubuntu 24.04 Ubuntu 26.04 Debian 13
postfix 3.8.6 3.10.6 3.10.13
opendkim 2.11.0~beta2 2.11.0~beta2 2.11.0~beta2
opendmarc 1.4.2 1.4.2 1.4.2
postfix-policyd-spf-python 3.0.4 3.1.0 3.1.0

O OpenDKIM nunca teve uma 2.11 final: o upstream parou na beta2 de 2018 e as distribuições empacotam essa, com correções próprias. O OpenDMARC está na 1.4.2 desde 2021. Os dois continuam os milters padrão para Postfix.

A diferença que importa: no Ubuntu 24.04 e no Debian 13 o smtpd do Postfix roda em chroot em /var/spool/postfix; no 26.04 o pacote finalmente desligou o chroot por padrão. Confira no seu:

grep -E '^smtp\s+inet' /etc/postfix/master.cf
# smtp  inet  n  -  y  -  -  smtpd    ← "y" na 5ª coluna = chroot

Por isso os sockets dos milters vão ficar dentro de /var/spool/postfix e o Postfix vai apontar para eles com caminho relativo — funciona com e sem chroot, e foi assim que testei nos dois Ubuntu.

SPF: o primeiro registro

No DNS do domínio, um TXT na raiz:

example.com.   IN TXT   "v=spf1 mx ip4:203.0.113.10 -all"

mx autoriza os servidores do registro MX, ip4: o IP de saída, e -all diz que o resto não pode. Se outro serviço manda em seu nome (newsletter, ERP, Google Workspace), acrescente o include: que ele documenta. Dois cuidados:

  • Um registro SPF só. Dois TXT começando com v=spf1 dão erro permanente.
  • No máximo 10 consultas DNS (include, a, mx, redirect…) na avaliação inteira, contando os include dos outros. Estourou, o SPF dá permerror.

Enquanto não tiver certeza de que listou tudo, use ~all (softfail). Com DMARC funcionando, o -all pesa menos do que parece — já explico por quê.

Aproveite e confira o DNS reverso: o PTR de 203.0.113.10 deve apontar para mail.example.com, e esse nome deve resolver de volta para o mesmo IP. O Gmail exige.

OpenDKIM: gerar a chave

sudo apt install -y opendkim opendkim-tools
sudo mkdir -p /etc/opendkim/keys/example.com
sudo opendkim-genkey -b 2048 -h sha256 -r -s s2026 -d example.com \
  -D /etc/opendkim/keys/example.com

Isso cria s2026.private (a chave privada) e s2026.txt (o registro DNS pronto). -s s2026 é o seletor, o nome da chave; usar o ano facilita a troca depois. -b 2048 segue a RFC 8301, que exige no mínimo 1024 bits e recomenda 2048; o pacote do Debian/Ubuntu já usa 2048 por padrão, mas deixar explícito não custa. -r restringe a chave a e-mail. O opendkim-genkey empacotado só gera RSA — nada de Ed25519 por enquanto.

Permissões — a chave privada só pode ser lida pelo OpenDKIM:

sudo chown -R opendkim:opendkim /etc/opendkim
sudo chmod 700 /etc/opendkim/keys/example.com
sudo chmod 600 /etc/opendkim/keys/example.com/s2026.private

OpenDKIM: tabelas e opendkim.conf

Três arquivos pequenos. /etc/opendkim/key.table diz qual chave cada seletor usa:

s2026._domainkey.example.com example.com:s2026:/etc/opendkim/keys/example.com/s2026.private

/etc/opendkim/signing.table diz quais remetentes assinam com qual chave:

*@example.com s2026._domainkey.example.com

/etc/opendkim/trusted.hosts lista de onde vem correio que deve ser assinado (não verificado):

127.0.0.1
::1
localhost

Se aplicações de outras máquinas usam este Postfix como relay, acrescente os IPs delas. Quem envia autenticado via SMTP AUTH (porta 587, cliente de e-mail, webmail) é assinado automaticamente: o OpenDKIM assina quando o IP é interno ou quando o Postfix informa que a sessão foi autenticada.

Agora substitua o /etc/opendkim.conf:

Syslog                  yes
SyslogSuccess           yes
LogWhy                  yes
Canonicalization        relaxed/simple
Mode                    sv
OversignHeaders         From
UserID                  opendkim
UMask                   007
Socket                  local:/var/spool/postfix/opendkim/opendkim.sock
PidFile                 /run/opendkim/opendkim.pid
TrustAnchorFile         /usr/share/dns/root.key
KeyTable                refile:/etc/opendkim/key.table
SigningTable            refile:/etc/opendkim/signing.table
ExternalIgnoreList      /etc/opendkim/trusted.hosts
InternalHosts           /etc/opendkim/trusted.hosts
  • Mode sv: assina o que sai e verifica o que chega.
  • Canonicalization relaxed/simple: tolera mudanças de espaço nos cabeçalhos no caminho; o corpo tem de chegar idêntico.
  • OversignHeaders From: impede que alguém acrescente um segundo From: sem quebrar a assinatura.
  • refile: no SigningTable é o que faz o *@example.com funcionar como curinga.
  • Socket dentro de /var/spool/postfix, pelo motivo do chroot.

O arquivo /etc/default/opendkim ainda existe, mas o próprio pacote o marca como legado: tudo vai no opendkim.conf. Crie o diretório do socket, ponha o Postfix no grupo do OpenDKIM (o UMask 007 dá acesso ao grupo) e teste a configuração:

sudo mkdir -p /var/spool/postfix/opendkim
sudo chown opendkim:postfix /var/spool/postfix/opendkim
sudo chmod 750 /var/spool/postfix/opendkim
sudo usermod -aG opendkim postfix
sudo opendkim -n -x /etc/opendkim.conf && echo config OK
sudo systemctl restart opendkim

Publicar a chave no DNS

sudo cat /etc/opendkim/keys/example.com/s2026.txt
s2026._domainkey	IN	TXT	( "v=DKIM1; h=sha256; k=rsa; s=email; "
	  "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2pGx9Ms2..."
	  "tyJOiuSbPxdG4hH7kpWfJscarmix9gP6iAVhM+Dieij2..." )

Crie um TXT com nome s2026._domainkey no domínio. Uma chave de 2048 bits não cabe numa única string DNS (limite de 255 caracteres), por isso o arquivo vem quebrado em várias strings entre aspas; o destino junta tudo sem espaços. No BIND, cole o bloco como está. Em painéis de provedor, a maioria aceita o valor inteiro colado numa linha só — v=DKIM1; h=sha256; k=rsa; s=email; p=MIIB...IDAQAB — e divide sozinha.

Quando o DNS propagar, teste:

dig +short TXT s2026._domainkey.example.com
sudo opendkim-testkey -d example.com -s s2026 -vvv
opendkim-testkey: checking key 's2026._domainkey.example.com'
opendkim-testkey: key not secure
opendkim-testkey: key OK

key OK é o que importa: a chave pública no DNS bate com a privada no disco. key not secure só avisa que o domínio não tem DNSSEC — não impede nada. Se aparecer Revoked key, o DNS devolveu um registro com p= vazio: seletor errado, registro ainda não propagado ou um curinga no domínio.

Um detalhe que confunde em rede interna: com TrustAnchorFile configurado, o OpenDKIM resolve DNS sozinho, a partir da raiz, e ignora o /etc/resolv.conf. Se você publicou a chave só num DNS interno, ele não vai achá-la.

Ligar os milters no Postfix

sudo postconf -e \
  'smtpd_milters = local:opendkim/opendkim.sock, local:opendmarc/opendmarc.sock' \
  'non_smtpd_milters = $smtpd_milters' \
  'milter_default_action = accept'
sudo postfix check

O caminho relativo (opendkim/opendkim.sock) é resolvido a partir de /var/spool/postfix, com chroot ou sem. non_smtpd_milters faz assinar também o que entra pelo comando sendmail local (cron, scripts, aplicações PHP). O OpenDMARC entra na próxima seção; se quiser testar só o DKIM agora, deixe apenas o primeiro socket.

milter_default_action decide o que acontece se o milter estiver fora do ar. O padrão é tempfail: o Postfix recusa temporariamente e o remetente tenta de novo depois — nada se perde, mas nada entra nem sai enquanto o OpenDKIM estiver parado. Com accept, o correio continua fluindo, só que sem assinatura. Escolha consciente; eu prefiro accept com monitoramento do serviço.

sudo systemctl restart postfix
echo "teste" | mail -s "teste DKIM" voce@gmail.com   # pacote mailutils
sudo grep opendkim /var/log/mail.log | tail -3
opendkim[4242]: 52C29B42EDE: DKIM-Signature field added (s=s2026, d=example.com)

DMARC: publicar a política

Outro TXT, em _dmarc.example.com. Comece observando:

_dmarc.example.com.   IN TXT   "v=DMARC1; p=none; rua=mailto:dmarc@example.com"
  • p=none: não muda nada na entrega; os provedores só passam a mandar relatórios.
  • rua=: para onde vão os relatórios agregados — um XML por dia, por provedor, listando cada IP que mandou mensagem com o seu domínio e se passou em SPF e DKIM. É ali que aparece o sistema esquecido que também envia em seu nome.
  • adkim e aspf (padrão r, relaxado): no modo relaxado, news.example.com alinha com example.com. Deixe o padrão.
  • sp=: política para subdomínios, se quiser diferente.

Depois de duas a quatro semanas lendo relatórios sem surpresas, suba para p=quarantine (falha vai para o spam) e, por fim, p=reject (falha é recusada). A tag pct=, usada para aplicar a política só a uma porcentagem das mensagens, foi removida na RFC 9989 e substituída por t=y (modo de teste). Muitos receptores ainda seguem a RFC antiga; a subida em degraus de none para reject funciona com os dois.

Se o rua= apontar para outro domínio (um serviço de relatórios, por exemplo), esse domínio precisa autorizar publicando example.com._report._dmarc.relatorios.example.net TXT "v=DMARC1" — sem isso, os provedores não mandam.

Para ler os XML sem sofrimento, o parsedmarc (Apache 2.0) lê a caixa de relatórios por IMAP e gera JSON, CSV ou manda para Elasticsearch/OpenSearch. O próprio OpenDMARC traz opendmarc-import e opendmarc-reports, mas eles servem para gerar relatórios sobre quem manda para você — e exigem um MySQL.

OpenDMARC: verificar o que chega

Até aqui você assina e publica. Do outro lado, o seu servidor também recebe e-mail — e pode recusar quem falsifica domínios com p=reject. É o trabalho do OpenDMARC:

sudo apt install -y opendmarc publicsuffix

Na instalação, o debconf pergunta se deve configurar um banco de dados com dbconfig-common; responda não — ele só serve para gerar relatórios. Substitua o /etc/opendmarc.conf:

AuthservID                 mail.example.com
TrustedAuthservIDs         mail.example.com
IgnoreAuthenticatedClients true
IgnoreHosts                /etc/opendmarc/ignore.hosts
RejectFailures             true
RequiredHeaders            true
SPFSelfValidate            true
FailureReports             false
PidFile                    /run/opendmarc/opendmarc.pid
PublicSuffixList           /usr/share/publicsuffix/public_suffix_list.dat
Socket                     local:/var/spool/postfix/opendmarc/opendmarc.sock
Syslog                     true
UMask                      0002
UserID                     opendmarc
  • AuthservID e TrustedAuthservIDs: o nome que aparece nos cabeçalhos Authentication-Results. O OpenDMARC lê o resultado do DKIM que o OpenDKIM anotou com esse nome.
  • RejectFailures true: recusa quando o domínio do remetente publica p=reject e a mensagem falha. Com false, só anota o cabeçalho.
  • SPFSelfValidate true: o próprio OpenDMARC avalia o SPF, sem depender de outro componente.
  • IgnoreAuthenticatedClients e IgnoreHosts: não verifica o correio dos seus usuários nem o local.
  • RequiredHeaders true: recusa mensagens sem From: válido — um truque comum de falsificação.
  • FailureReports false: não manda relatórios de falha individuais, que carregam trechos da mensagem.
sudo mkdir -p /etc/opendmarc /var/spool/postfix/opendmarc
printf '127.0.0.1\n::1\n' | sudo tee /etc/opendmarc/ignore.hosts
sudo chown opendmarc:postfix /var/spool/postfix/opendmarc
sudo chmod 750 /var/spool/postfix/opendmarc
sudo usermod -aG opendmarc postfix
sudo opendmarc -n -c /etc/opendmarc.conf && echo config OK
sudo systemctl restart opendmarc postfix

O smtpd_milters da seção anterior já lista o socket do OpenDMARC depois do OpenDKIM — a ordem importa, porque o DMARC precisa do resultado do DKIM.

O teste que convence

No laboratório, com os três registros publicados num DNS local e p=reject, mandei de fora duas mensagens com From: ana@example.com, a partir de um IP que o SPF não autoriza. A primeira, com a assinatura DKIM original, foi entregue com estes cabeçalhos:

Authentication-Results: mail.example.com; dmarc=pass (p=reject dis=none) header.from=example.com
Authentication-Results: mail.example.com;
	dkim=pass (2048-bit key; unprotected) header.d=example.com header.i=@example.com header.a=rsa-sha256 header.s=s2026
Received-SPF: Fail (mailfrom) identity=mailfrom; client-ip=172.17.0.2; ...

O SPF falhou, mas o DKIM passou e está alinhado com o From: — então o DMARC passa. É exatamente o caso de uma mensagem encaminhada. A segunda era a mesma mensagem com o corpo trocado por “Pague o boleto anexo.”:

opendkim[5617]: 8F25AB44DDF: bad signature data
opendmarc[5622]: 8F25AB44DDF: example.com fail
postfix/cleanup: 8F25AB44DDF: milter-reject: END-OF-MESSAGE ... 5.7.1 rejected by DMARC policy for example.com

Recusada. O mesmo aconteceu com uma mensagem forjada em nome de paypal.com, que publica p=reject de verdade.

Para testar o seu domínio de verdade, mande uma mensagem para uma conta Gmail e abra Mostrar original: o topo mostra SPF, DKIM e DMARC com PASS. O mail-tester.com dá um endereço descartável e uma nota com o diagnóstico de cada item.

SPF no Postfix: anotar, não recusar

É tentador recusar logo no RCPT TO tudo que falha no SPF, com o postfix-policyd-spf-python. No laboratório, isso fez o Postfix recusar a mensagem legítima do teste acima antes de o DKIM ser olhado: o SPF falhou e acabou ali. Encaminhamentos e listas de discussão caem nesse buraco o tempo todo. Com DMARC, a decisão fica melhor com ele. Se quiser o cabeçalho Received-SPF para o seu antispam, instale o policyd-spf só para anotar:

sudo apt install -y postfix-policyd-spf-python
sudo sed -i 's/^Mail_From_reject = Fail/Mail_From_reject = False/' \
  /etc/postfix-policyd-spf-python/policyd-spf.conf

No /etc/postfix/master.cf:

policyd-spf  unix  -       n       n       -       0       spawn
  user=policyd-spf argv=/usr/bin/policyd-spf

E no main.cf, com o check_policy_service depois do reject_unauth_destination:

sudo postconf -e 'policyd-spf_time_limit = 3600' \
  'smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination, check_policy_service unix:private/policyd-spf'
sudo systemctl reload postfix

Duas pegadinhas. No policyd-spf.conf, TestOnly = 1 é o modo normal, que aplica as recusas; 0 é que desliga — o nome engana. E o processo do policyd-spf fica vivo até uma hora (policyd-spf_time_limit) sem reler a configuração: depois de editar, reinicie o Postfix.

Trocar a chave sem perder mensagens

Troque a chave DKIM uma vez por ano, e imediatamente se a privada vazar. O seletor no nome é o que permite fazer isso sem janela de falha:

  1. Gere a nova com outro seletor: opendkim-genkey -b 2048 -h sha256 -r -s s2027 -d example.com -D /etc/opendkim/keys/example.com, e acerte as permissões.
  2. Publique s2027._domainkey no DNS e espere propagar (opendkim-testkey -s s2027).
  3. Troque s2026 por s2027 no key.table e no signing.table e reinicie o OpenDKIM.
  4. Mantenha o registro s2026 no DNS por uma ou duas semanas — mensagens assinadas com ele ainda estão em filas e caixas sendo verificadas.
  5. Remova o s2026 do DNS e apague a chave antiga.

O que costuma quebrar

  • warning: connect to Milter service local:opendkim/opendkim.sock: No such file or directory — o Socket do opendkim.conf não está dentro de /var/spool/postfix, ou o serviço não subiu.
  • Permission denied no socket — faltou usermod -aG opendkim postfix (e reiniciar o Postfix depois), ou o diretório do socket não é do grupo postfix.
  • Mensagem sai sem assinatura — o remetente não casa com o signing.table (sem refile:, o *@ não funciona) ou o IP de origem não está em InternalHosts e a sessão não foi autenticada. LogWhy yes explica no log.
  • dkim=fail no destino com assinatura presente — algo alterou a mensagem depois de assinada: um filtro de conteúdo, disclaimer ou antivírus reinjetando depois do milter.
  • Registro DKIM colado com espaço ou aspas no meio da chave no painel do provedor. Compare com dig +short TXT s2026._domainkey.example.com.
  • Consultas lentas — o SPF de alguns domínios grandes gera dezenas de consultas DNS. No laboratório, com o DNS lento de um roteador, uma única avaliação levou quase um minuto. Um resolver local com cache no servidor de e-mail resolve.

E o Rspamd?

O Rspamd faz assinatura DKIM, verificação de SPF, DKIM e DMARC, ARC e antispam num só serviço, também como milter do Postfix, e está empacotado em todas as distribuições citadas. Para um servidor novo com volume, é a escolha moderna. OpenDKIM e OpenDMARC continuam fazendo sentido quando você quer só a autenticação, com peças pequenas e configuração em arquivos de texto — que é o que mostrei aqui.

Para montar o resto do servidor: go-postfixadmin para as caixas virtuais, e a história por trás das peças em A história do Postfix e A história do Dovecot. Quem usa painel tem o DMARC integrado no ISPConfig 3.3.2.