To set up DKIM, generate a 2048-bit key pair, publish the public key as a TXT record at selector._domainkey.example.com, and have your mail server or provider sign every outgoing message with the private key. If you use a hosted provider, it generates the key and you only add the DNS records it shows you. If you run your own Postfix server, OpenDKIM does the signing. Either way, finish by testing with a real message.

DKIM (DomainKeys Identified Mail, RFC 6376) adds a DKIM-Signature header covering selected headers and the body. The receiver fetches your public key from DNS and checks that the message was signed by someone holding your private key and was not changed on the way. Unlike SPF, it survives forwarding.

We use example.com and the selector mail throughout, which is what we use on our own servers.

How a DKIM record is put together

A signature includes, among other tags:

DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=example.com; s=mail; ...

d= is the signing domain and s= is the selector. The receiver looks up:

mail._domainkey.example.com  TXT  "v=DKIM1; k=rsa; p=MIIBIjANBgkqh..."

p= is the base64 public key. For DMARC to pass on DKIM, the d= domain must align with the visible From domain.

Option A: DKIM with a hosted provider

Every major provider has a "domain authentication" or "sender domain" page. It gives you one to three DNS records, usually TXT or CNAME, at selectors it picks. Add them exactly as shown, wait for the provider to verify them, and make sure signing with your domain is switched on. Some providers sign with their own domain until you finish this step, which passes DKIM but does not align for DMARC.

If you use several providers, each gets its own selector. They do not conflict.

Some providers ask for CNAME records instead of TXT, pointing your selector at a key they host. That lets them rotate keys without asking you to change DNS. A CNAME and a TXT record cannot exist at the same name, so if you already have a TXT record at that selector, remove it first.

Option B: DKIM on your own Postfix server with OpenDKIM

This is our production setup on Ubuntu 26.04 with OpenDKIM 2.11: one 2048-bit key per domain, selector mail, KeyTable and SigningTable so you can add domains later without touching the main config.

Step 1: Install and generate the key

sudo apt install opendkim opendkim-tools
sudo mkdir -p /etc/opendkim/keys/example.com
sudo opendkim-genkey -b 2048 -d example.com -s mail -D /etc/opendkim/keys/example.com
sudo chown -R opendkim:opendkim /etc/opendkim
sudo chmod 600 /etc/opendkim/keys/example.com/mail.private

This creates mail.private (keep it secret) and mail.txt (the DNS record).

Step 2: Configure OpenDKIM

Replace /etc/opendkim.conf with:

Syslog                  yes
SyslogSuccess           yes
UMask                   007
Mode                    sv
Canonicalization        relaxed/simple
OversignHeaders         From
SubDomains              yes
AutoRestart             yes
AutoRestartRate         10/1h
KeyTable                file:/etc/opendkim/key.table
SigningTable            refile:/etc/opendkim/signing.table
ExternalIgnoreList      refile:/etc/opendkim/trusted.hosts
InternalHosts           refile:/etc/opendkim/trusted.hosts
Socket                  local:/var/spool/postfix/opendkim/opendkim.sock
PidFile                 /run/opendkim/opendkim.pid
UserID                  opendkim

Mode sv signs outgoing mail and verifies incoming mail. OversignHeaders From stops anyone from adding a second From header after signing. Canonicalization relaxed/simple tolerates harmless header whitespace changes.

Step 3: Create the KeyTable, SigningTable and trusted hosts

The KeyTable names each key: keyname domain:selector:path.

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

The SigningTable (a refile:, so wildcards work) says which senders use which key:

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

The trusted hosts file lists who may have mail signed. Mail from clients that authenticated over SASL is signed as well.

127.0.0.1
::1
localhost

Save those as /etc/opendkim/key.table, /etc/opendkim/signing.table and /etc/opendkim/trusted.hosts. To add a second domain later, generate another key and add one line to each of the first two files.

Step 4: Connect OpenDKIM to Postfix

Postfix's smtpd runs chrooted in /var/spool/postfix on Ubuntu, so the socket goes there:

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 postconf -e \
  'milter_default_action = accept' \
  'milter_protocol = 6' \
  'smtpd_milters = local:opendkim/opendkim.sock' \
  'non_smtpd_milters = $smtpd_milters'
sudo systemctl restart opendkim postfix

non_smtpd_milters makes sure mail generated on the server itself (cron, local scripts) is signed too.

Step 5: Publish the DNS record

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

The file is in zone-file format with the key split across several quoted strings. In your DNS panel, create one TXT record:

Field Value
Type TXT
Host mail._domainkey
Value v=DKIM1; h=sha256; k=rsa; p=MIIBIjAN... (the strings joined, without the quotes and parentheses)

Some panels want the strings joined into one line; others accept the quoted chunks. Either way, the value must contain no spaces or line breaks inside the p= key.

Adding a second domain to OpenDKIM

The KeyTable and SigningTable layout exists so that adding a domain is three lines, not a new config. For example.net:

sudo mkdir -p /etc/opendkim/keys/example.net
sudo opendkim-genkey -b 2048 -d example.net -s mail -D /etc/opendkim/keys/example.net
sudo chown -R opendkim:opendkim /etc/opendkim/keys/example.net
sudo chmod 600 /etc/opendkim/keys/example.net/mail.private
echo 'mail._domainkey.example.net example.net:mail:/etc/opendkim/keys/example.net/mail.private' | sudo tee -a /etc/opendkim/key.table
echo '*@example.net mail._domainkey.example.net' | sudo tee -a /etc/opendkim/signing.table
sudo systemctl restart opendkim

Then publish /etc/opendkim/keys/example.net/mail.txt as mail._domainkey in the example.net zone. We run one key per domain; sharing a key across domains works but means one leak affects them all.

Rotating a DKIM key without downtime

Selectors make rotation safe. Use a new selector name, for example one with the year and month:

sudo opendkim-genkey -b 2048 -d example.com -s mail2026b -D /etc/opendkim/keys/example.com
sudo chown opendkim:opendkim /etc/opendkim/keys/example.com/mail2026b.*
sudo chmod 600 /etc/opendkim/keys/example.com/mail2026b.private
  1. Publish mail2026b._domainkey from the new .txt file and confirm it with opendkim-testkey -d example.com -s mail2026b -vvv.
  2. Add a KeyTable line for the new key and point the SigningTable entry for *@example.com at it:
# /etc/opendkim/key.table
mail2026b._domainkey.example.com example.com:mail2026b:/etc/opendkim/keys/example.com/mail2026b.private

# /etc/opendkim/signing.table
*@example.com mail2026b._domainkey.example.com
  1. Restart OpenDKIM and check a test message shows s=mail2026b.
  2. Leave the old record in DNS for a week so mail already queued or in transit still verifies, then delete it.

The order matters: DNS first, signing second. Switching signing before the record is visible fails DKIM for everything sent in between.

What actually gets signed

Knowing this helps when DKIM fails on some messages and not others. OpenDKIM computes a hash of the body and a signature over a list of headers, recorded in the h= tag. By default that includes From, To, Subject, Date and other standard headers. If anything along the way changes one of those headers or the body after signing, verification fails.

Typical culprits are mailing list software that adds a footer or a subject prefix, security gateways that rewrite links, and forwarders that alter headers. If you rewrite headers on your own server at all, limit it to headers outside the h= list, such as internal Received: lines, and leave every signed header alone. Signing should be the last thing that touches a message before it leaves you.

Common mistakes

  • Two records at the same selector. We hit this ourselves: the DKIM record was pasted twice, once with the host name pasted into the value field. Receivers got "multiple DNS replies" and DKIM failed. Exactly one v=DKIM1 record per selector.
  • A truncated key. Some panels silently cut long values at 255 characters. Compare the published p= value with mail.txt character for character, or let opendkim-testkey do it.
  • Host field doubled. Entering mail._domainkey.example.com in a panel that appends the domain creates mail._domainkey.example.com.example.com. Enter mail._domainkey.
  • Wrong permissions. OpenDKIM refuses to use a private key readable by other users, and Postfix cannot reach a socket it has no permission for. Check journalctl -u opendkim and /var/log/mail.log.
  • Signing with the provider's domain. It passes DKIM but not DMARC alignment. Finish the custom domain setup.
  • Selector mismatch. The signing configuration says mail but the record was published as default._domainkey, or the reverse. The s= tag in the signature must match the DNS name exactly.
  • Old record left behind after a provider change. Remove selectors for services you no longer use, so a stale key cannot be abused.
  • Modifying signed messages after signing. Footers, header rewrites and link rewriting after the signature break it. Sign last.

How to verify it worked

Check the record in DNS and confirm OpenDKIM agrees with it:

dig +short TXT mail._domainkey.example.com
sudo opendkim-testkey -d example.com -s mail -vvv

You want one record back and key OK from the second command. (key not secure just means your DNS zone does not use DNSSEC; that is not an error.)

Then send a real signed message. On your own server:

swaks --to check-auth@verifier.port25.com --from hello@example.com \
  --server mail.example.com --port 587 --tls \
  --auth LOGIN --auth-user hello@example.com

The report should show DKIM check: pass. In Gmail, "Show original" should show dkim=pass header.i=@example.com. mail-tester.com also checks the signature. Our email checker shows whether your DKIM record is published and readable.

What a DKIM pass does not tell you

A passing signature proves the message came from someone holding your key and was not altered. It says nothing about whether recipients want it. A new domain with perfect DKIM can still land in spam until it has some sending history; we saw exactly that on our own first day, with all checks passing. If DKIM passes and placement is still poor, look at reputation and volume next, starting with our IP warm-up guide.

For the full server build around this, see the self-hosted email server guide. If you would rather have someone check DKIM, SPF, DMARC and PTR across all your senders, that is our Deliverability Fix, listed on the pricing page.