On this page
  1. How Postfix and OpenDKIM fit together
  2. Generate the key and publish the TXT record
  3. Configure OpenDKIM
  4. Connect Postfix to the milter
  5. Test the signature
  6. Publish DMARC once signing works
  7. Check the published key
  8. Sources

OpenDKIM signs mail for Postfix as a filter that Postfix connects to through its smtpd_milters setting. The steps: generate a key pair with opendkim-genkey, publish the public key as a TXT record at selector._domainkey.example.com, tell OpenDKIM which domain, selector and private key to sign with, point Postfix at OpenDKIM's socket, then test the key with opendkim-testkey and read the Authentication-Results header on a message you sent. OpenDKIM's README (opens in a new tab) walks through those steps, and the commands below follow it.

How Postfix and OpenDKIM fit together

Postfix's milter documentation (opens in a new tab) keeps two lists of mail filters. The SMTP-only filters, set with smtpd_milters, handle mail that arrives through the Postfix SMTP server and are typically used to filter unwanted mail and to sign mail from authorized clients. The non-SMTP filters, set with non_smtpd_milters, handle mail submitted with the sendmail command line or through qmqpd, and are typically used only to sign. Each milter is identified by the name of its listening socket, either unix:pathname or inet:host:port; note that Postfix writes inet:host:port where milter applications write inet:port@host.

What happens when OpenDKIM is down is set by milter_default_action. Postfix's postconf documentation (opens in a new tab) lists the actions: accept proceeds as if the filter were not there, reject refuses with a permanent error, tempfail refuses with a temporary error, quarantine accepts but holds the message, and shutdown closes the SMTP connection with a 421 reply. The default was tempfail up to Postfix 3.10 and is shutdown from Postfix 3.11.

Check your domain now

See the SPF, DKIM and DMARC records your domain publishes and what to fix first. Free, no account needed.

Generate the key and publish the TXT record

OpenDKIM's README (opens in a new tab) starts with a selector, a symbolic name for the key; you are free to choose any name, and two conventions are the short host name of the signing server or the current month and year. Then run opendkim-genkey:

opendkim-genkey -d example.com -s mail2026 -b 2048

The opendkim-genkey manual (opens in a new tab) describes the output: a private key file ending in .private and a DNS TXT record ending in .txt, both named after the selector. When you give no selector the manual's default is default, and the default key size is 1024 bits. RFC 6376 (opens in a new tab) requires signers to use RSA keys of at least 1024 bits for long-lived keys and verifiers to handle keys up to 2048 bits. Keep the private key readable only by the user that runs OpenDKIM; the README asks for mode 0600 on the key file.

Publish the .txt contents as a TXT record at mail2026._domainkey.example.com. RFC 6376 section 3.6.2 (opens in a new tab) defines that name: keys live under _domainkey in the signing domain, at the selector named in the signature. The record format comes from section 3.6.1 (opens in a new tab): v=DKIM1 first, k=rsa (the default key type), and p= with the base64 public key; an empty p= means the key has been revoked. The README explains that each string in a TXT record is at most 255 bytes and that some RSA keys exceed that once base64-encoded, so a long key has to be split into several quoted strings, which DKIM verifiers reassemble; RFC 6376 requires them to be joined with no whitespace. The README also suggests a short TTL while testing, and t=y on the record to tell verifiers you are in test mode, removed once signing works.

Configure OpenDKIM

For one domain and one key, the opendkim.conf manual (opens in a new tab) needs Domain, Selector and KeyFile together, with no KeyTable or SigningTable. For several domains or keys, use KeyTable and SigningTable instead, without Domain, KeyFile or Selector. Mode selects the operating modes, and Socket is the address Postfix connects to; it is mandatory. A single-domain file looks like this:

Domain          example.com
Selector        mail2026
KeyFile         /etc/opendkim/keys/mail2026.private
Mode            sv
Canonicalization relaxed/simple
Socket          inet:8891@localhost

Canonicalization takes one method for the header and one for the body, separated by a slash. RFC 6376 section 3.4 (opens in a new tab) defines the two: simple tolerates almost no modification and relaxed tolerates common ones such as whitespace replacement and header line rewrapping; with nothing specified, simple applies to both. With KeyTable, each key name maps to the domain for d=, the selector for s= and the private key file, and SigningTable maps sender addresses from the From: header to key names, with wildcards when it is a regular expression file (refile:), as the OpenDKIM README (opens in a new tab) shows with *@example.com. The README also notes that only mail from localhost is signed unless you list other sources under InternalHosts.

Connect Postfix to the milter

Add the socket to both lists. The milter settings are Postfix main.cf parameters (opens in a new tab), and OpenDKIM's README (opens in a new tab) gives these two lines for a TCP socket on port 8891:

smtpd_milters = inet:localhost:8891
non_smtpd_milters = inet:localhost:8891

Then run postfix reload. If you use a UNIX socket instead, the same Postfix documentation warns that a chrooted smtpd reads the path relative to the queue directory, so the socket has to be visible inside the chroot. The README also mentions -o receive_override_options=no_milters in master.cf to prevent messages being signed or verified twice. Do not remove Postfix's own Received: header with a header_checks IGNORE action; Postfix's documentation says this causes problems with signing filters.

Test the signature

First test the key. OpenDKIM's README (opens in a new tab) uses dig to confirm the record is published and opendkim-testkey to confirm it matches the private key:

dig -t txt mail2026._domainkey.example.com
opendkim-testkey -d example.com -s mail2026 -k /etc/opendkim/keys/mail2026.private

Then send a message to a mailbox at a provider that verifies DKIM and open the full headers. The message should carry a DKIM-Signature: field from your server and an Authentication-Results: field added by the receiver whose DKIM result is pass; the README notes that a result other than pass means something between the two servers altered the message. RFC 8601 (opens in a new tab) defines the results: pass means the signature verified, fail that it was acceptable but failed verification, none that the message was not signed, and header.d reports the signing domain from d=.

Two failures map to RFC 6376's verifier steps (opens in a new tab). no key for signature means the verifier found no key record at the selector and domain in the signature: check the record name and that it has propagated. A record that does not parse gives key syntax error. body hash did not verify, from section 6.1.3 (opens in a new tab), means the body no longer matches the hash in bh=, so the whole signature fails. For reading the reason in a real header, see DKIM fail.

Publish DMARC once signing works

DMARC uses the DKIM result only when the signing domain aligns with the From domain. RFC 7489 (opens in a new tab) requires the From domain to match an authenticated identifier; in relaxed mode the d= domain and the From domain need the same organizational domain, and a message passes DMARC if any aligned DKIM signature verifies. Start with a monitoring record from the DMARC generator, published as a TXT record:

v=DMARC1; p=none; rua=mailto:dmarc-reports@example.com

At p=none delivery does not change, and the reports show you every service that sends as your domain; move to quarantine and then reject only when your real senders pass. If a checker reports that no record exists, see No DMARC record found.

Check the published key

The free SPF, DKIM and DMARC check reads the public DNS records of a domain and, for DKIM, looks for keys under commonly used selectors, flagging keys that are weak, revoked or of an unknown type. If your selector is not among those, the key is there but the check cannot see it. A DNS finding shows how the domain is set up, not where a particular message landed.

Sources

Tools for this

Frequently asked questions

Which selector name should I use?

Any name you like. OpenDKIM's README says you are free to choose it, and describes two conventions: the short host name of the signing server, or the current month and year. opendkim-genkey uses default when you give no -s option.

Mail is signed but dkim=fail (body hash did not verify): why?

The body changed after OpenDKIM signed it, so the hash in the signature no longer matches and the whole signature fails. Something between your server and the receiver altered the message; relaxed canonicalization tolerates common changes such as whitespace replacement and header line rewrapping.

Does OpenDKIM also verify incoming mail?

It can. Mail from domains you have not configured for signing is verified rather than signed, and the README notes you can skip the signing steps entirely if you only want to verify incoming mail.

Check your domain now

See the SPF, DKIM and DMARC records your domain publishes and what to fix first. Free, no account needed.