Email Authentication: DKIM, SPF, and DMARC

August 22, 2026

Email authentication proves that a message actually came from your server and hasn't been modified in transit. Without it, anyone can forge the From header and send mail as your domain. DKIM signs outgoing messages with a cryptographic key, SPF declares which servers are authorized to send, and DMARC ties them together with a policy for handling failures. This post covers generating DKIM keys, configuring OpenDKIM for signing, and verifying the full authentication chain.

How the Three Protocols Work Together

SPF (Sender Policy Framework) — a DNS TXT record listing which IP addresses can send mail for your domain. Receiving servers check the connecting IP against your SPF record.

DKIM (DomainKeys Identified Mail) — your server signs each outgoing message with a private key. The receiving server retrieves the corresponding public key from your DNS and verifies the signature. If the message was altered in transit, the signature fails.

DMARC (Domain-based Message Authentication, Reporting, and Conformance) — a DNS TXT record that tells receiving servers what to do when both SPF and DKIM fail. It also provides a reporting mechanism so you can see who's sending mail as your domain.

All three are needed for reliable email delivery. Major providers (Gmail, Outlook, Yahoo) increasingly require all three to accept mail.

Generating DKIM Keys

Each domain needs its own DKIM key pair. The private key stays on the server, and the public key is published in DNS.

Installing OpenDKIM

emerge -av mail-filter/opendkim

Creating the Key Directory

mkdir -p /etc/opendkim/keys
chown -R opendkim:opendkim /etc/opendkim
chmod 700 /etc/opendkim/keys

Generating Keys for Each Domain

For each domain you host, generate a key pair:

mkdir -p /etc/opendkim/keys/example.com
opendkim-genkey -b 2048 -d example.com -D /etc/opendkim/keys/example.com -s mail -v
chown opendkim:opendkim /etc/opendkim/keys/example.com/mail.private
chmod 600 /etc/opendkim/keys/example.com/mail.private

Repeat for each additional domain:

mkdir -p /etc/opendkim/keys/example.org
opendkim-genkey -b 2048 -d example.org -D /etc/opendkim/keys/example.org -s mail -v
chown opendkim:opendkim /etc/opendkim/keys/example.org/mail.private
chmod 600 /etc/opendkim/keys/example.org/mail.private

The -s mail flag sets the selector name. The selector identifies which key to use when a domain has multiple active keys (useful during key rotation).

Viewing the Public Key

The public key is stored in a TXT-record-ready format:

cat /etc/opendkim/keys/example.com/mail.txt

This outputs something like:

mail._domainkey IN TXT ( "v=DKIM1; h=sha256; k=rsa; "
  "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..." )

Publishing DKIM Records in DNS

For each domain, create a TXT record with the public key:

mail._domainkey.example.com.  TXT  "v=DKIM1; h=sha256; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."

The record name follows the pattern selector._domainkey.domain. With the selector mail, the record is mail._domainkey.example.com.

Some DNS providers have a 255-character limit per TXT string. If your key is longer, split it across multiple strings:

mail._domainkey.example.com.  TXT  "v=DKIM1; h=sha256; k=rsa; " "p=MIIBIjANBgkqhkiG9w0BAQE..."

DNS automatically concatenates adjacent strings.

Verifying the DKIM Record

dig TXT mail._domainkey.example.com +short

You should see your public key. If the record is empty, wait for DNS propagation or check for typos.

Configuring OpenDKIM

Key Table — /etc/opendkim/KeyTable

Maps selectors to key files:

mail._domainkey.example.com example.com:mail:/etc/opendkim/keys/example.com/mail.private
mail._domainkey.example.org example.org:mail:/etc/opendkim/keys/example.org/mail.private

Signing Table — /etc/opendkim/SigningTable

Maps sender addresses to signing keys:

*@example.com mail._domainkey.example.com
*@example.org mail._domainkey.example.org

The wildcard *@ means all addresses at each domain use the specified key.

Trusted Hosts — /etc/opendkim/TrustedHosts

Lists IPs and domains that are trusted to sign through OpenDKIM:

127.0.0.1
::1
localhost
mail.example.com
example.com
example.org

Main Configuration — /etc/opendkim/opendkim.conf

Syslog          yes
SyslogSuccess   yes

Canonicalization relaxed/simple
Mode            sv
SubDomains      no

AutoRestart         yes
AutoRestartRate     10/1M
Background          yes

DNSTimeout          5
SignatureAlgorithm  rsa-sha256

KeyTable            /etc/opendkim/KeyTable
SigningTable        refile:/etc/opendkim/SigningTable
InternalHosts       /etc/opendkim/TrustedHosts
ExternalIgnoreList  /etc/opendkim/TrustedHosts

Socket  inet:8891@localhost

UserID  opendkim
UMask   007

PidFile /run/opendkim/opendkim.pid

Key Settings

Mode sv — sign outgoing messages and verify incoming messages. Use s for sign-only if you're handling verification through Rspamd instead.

Canonicalization relaxed/simple — the header canonicalization is relaxed (tolerates minor whitespace changes by forwarding servers) and the body canonicalization is simple (requires exact match). This is the standard choice for maximum compatibility.

Socket inet:8891@localhost — OpenDKIM listens on a TCP socket. Postfix connects to it as a milter (mail filter).

SubDomains no — don't sign mail from subdomains unless they have their own entry in the signing table.

Setting Permissions

chown -R opendkim:opendkim /etc/opendkim
chmod 600 /etc/opendkim/KeyTable /etc/opendkim/SigningTable

Integrating with Postfix

Add the milter configuration to Postfix's main.cf:

# DKIM signing via OpenDKIM
milter_default_action = accept
milter_protocol = 6
smtpd_milters = inet:localhost:8891
non_smtpd_milters = $smtpd_milters

milter_default_action = accept ensures mail is delivered even if OpenDKIM is temporarily unavailable. Without this, an OpenDKIM crash would reject all incoming mail.

If also using Rspamd: When you later add Rspamd as a milter, you must chain both milters in a single declaration. Postfix uses the last smtpd_milters line it encounters — if Rspamd and OpenDKIM are configured separately, the second one overwrites the first:

# Both milters in one declaration — Rspamd first (spam check), then OpenDKIM (signing)
smtpd_milters = inet:127.0.0.1:11332, inet:127.0.0.1:8891
non_smtpd_milters = inet:127.0.0.1:11332, inet:127.0.0.1:8891

Starting the Services

rc-update add opendkim default
rc-service opendkim start
rc-service postfix restart

Verifying SPF Records

SPF was configured during DNS setup. Verify it's working:

dig TXT example.com +short

Expected:

"v=spf1 mx -all"

To test from the server itself, send a test email to a Gmail address and check the headers. You should see:

Received-SPF: pass (google.com: domain of user@example.com designates 203.0.113.10 as permitted sender)

Verifying DMARC Records

dig TXT _dmarc.example.com +short

Expected:

"v=DMARC1; p=reject; rua=mailto:postmaster@example.com"

DMARC Policy Progression

Start with p=none during initial setup:

"v=DMARC1; p=none; rua=mailto:postmaster@example.com"

This monitors without rejecting. After a week of reviewing DMARC reports (sent to the rua address), move to p=quarantine:

"v=DMARC1; p=quarantine; rua=mailto:postmaster@example.com"

Once you're confident everything is working, move to p=reject:

"v=DMARC1; p=reject; rua=mailto:postmaster@example.com"

Testing the Full Authentication Chain

Send a Test Email

Send an email from your server to an external address (Gmail works well for testing):

echo "Test email body" | mail -s "DKIM Test" testuser@gmail.com

Check the Headers

In Gmail, open the message and click "Show original" to see the full headers. Look for:

Authentication-Results: mx.google.com;
       dkim=pass header.i=@example.com header.s=mail
       spf=pass (google.com: domain of user@example.com designates 203.0.113.10 as permitted sender)
       dmarc=pass (p=REJECT sp=REJECT) header.from=example.com

All three should show pass. If any show fail:

  • DKIM fail: Check that the DNS record matches the public key, and that OpenDKIM is running
  • SPF fail: Check that the SPF TXT record includes your server's IP
  • DMARC fail: DMARC fails if both SPF and DKIM fail. Fix whichever is broken

Online Testing Tools

Several services let you send a test email and get a full report:

# Send to mail-tester.com (provides a score)
echo "Test" | mail -s "Test" test-address@mail-tester.com

Check the results on the website for a detailed breakdown of SPF, DKIM, DMARC, reverse DNS, and blacklist status.

Key Rotation

DKIM keys should be rotated periodically (annually is common). The process:

  1. Generate a new key pair with a new selector (e.g., mail202701)
  2. Publish the new public key in DNS
  3. Update the KeyTable and SigningTable to use the new key
  4. Wait 48 hours for DNS propagation
  5. Remove the old DNS record

Using date-based selectors makes it clear when each key was deployed.

Summary

After completing these steps:

  • DKIM signs all outgoing mail with a 2048-bit RSA key per domain
  • SPF records authorize your server's IP for each domain
  • DMARC policies tell receivers to reject unauthenticated messages
  • OpenDKIM integrates with Postfix via milter protocol
  • The full authentication chain is verified with external tests

With authentication in place, your mail server passes the checks that major providers require for inbox delivery. The next step is spam filtering with Rspamd to protect incoming mail.