Email·8 min read·

DKIM Signing With OpenDKIM on a Small MTA

DKIM survives forwarding when SPF does not. Sign with OpenDKIM on Postfix, publish a 2048-bit TXT key, and verify the signature on a received copy.

NB

Netbay Cloud Team

Netbay Engineering

On this page

DKIM attaches a cryptographic signature to headers and a hash of the body. The receiving MX fetches your public key from DNS and checks the signature. Unlike SPF, the check does not depend on the connecting IP, so forwarding and mailing lists do not automatically break it. If you run a small Postfix on a VPS, OpenDKIM is the usual signer. You do not need a full mail suite to do this.

Why the signature has to be yours

Without DKIM, a passing SPF only says the sending IP was allowed. A phisher on a hijacked marketing include, or a lookalike domain, still looks plausible. With DKIM plus DMARC alignment, the visible From domain has to match the domain that signed. That is the property inbox providers actually want.

A VPS that sends password resets should sign as the domain users see, not as a random hostname. Use a dedicated selector (the label before ._domainkey) so you can rotate keys without an outage. s1 and 202604 are both fine names. Avoid using default as the selector; everyone else's tutorial did that.

OpenDKIM sign and verify Postfix milter 8891 OpenDKIM sign DKIM-Signature receiver TXT s1._domainkey 2048-bit key in DNS, selector s1 signature covers From, Subject, Date, Message-ID

Install and generate a 2048-bit key

On Ubuntu 24.04 the packages are opendkim and opendkim-tools. Keep the private key readable only by the opendkim user.

bash
apt-get update
apt-get install -y opendkim opendkim-tools
mkdir -p /etc/opendkim/keys/example.com
opendkim-genkey -b 2048 -s s1 -d example.com -D /etc/opendkim/keys/example.com
chown -R opendkim:opendkim /etc/opendkim/keys
chmod 640 /etc/opendkim/keys/example.com/s1.private
cat /etc/opendkim/keys/example.com/s1.txt

The s1.txt file is the DNS payload. Publish it as a TXT record on s1._domainkey.example.com. Split long strings into quoted 255-character chunks if your DNS panel requires it. Wait for the record to resolve from a public resolver before you send signed mail; a signature whose public key 404s is worse than no signature.

Wire OpenDKIM to Postfix as a milter

Minimal signing-only config for a box that sends as example.com:

ini
# /etc/opendkim.conf
Syslog                  yes
Mode                    s
Canonicalization        relaxed/simple
Domain                  example.com
Selector                s1
KeyFile                 /etc/opendkim/keys/example.com/s1.private
Socket                  inet:8891@localhost
PidFile                 /run/opendkim/opendkim.pid
UserID                  opendkim
UMask                   007
OversignHeaders         From

In Postfix main.cf, milter_default_action = accept so a down signer does not lose mail, and milter_protocol = 6:

smtpd_milters = inet:127.0.0.1:8891 non_smtpd_milters = inet:127.0.0.1:8891

non_smtpd_milters covers locally injected mail (sendmail, pickup), which is how most app servers submit. If you only set smtpd_milters, cron mail leaves unsigned. Restart opendkim then postfix.

SigningTable and KeyTable become worth it when you host more than one domain. For a single domain the Domain / Selector / KeyFile trio is enough and harder to misconfigure.

Body hash pitfalls

relaxed/simple is a conservative pair: header canonicalization tolerates whitespace changes, body canonicalization does not. That is what you want for transactional mail. Do not rewrite the body after OpenDKIM signs. A footer added by the MTA, or a transfer encoding change, invalidates bh=. If you must add a disclaimer, add it before the milter, not after.

Sign at least From, Subject, Date, Message-ID, To, and MIME-Version. OversignHeaders From stops a second From header being appended in transit. Keep the body under the l= limit if you set one; most small MTAs omit l= and sign the whole body.

Verify on a received copy, not on the sender

Send to a Gmail address you control. Open original and look for DKIM-Signature and Authentication-Results: dkim=pass header.d=example.com. If you see dkim=fail, the usual causes are a truncated TXT record, the wrong selector, or the body being altered. opendkim-testkey -d example.com -s s1 -vv prints whether DNS matches the local private key.

Rotate selectors instead of overwriting s1. Publish s2 with a new 2048-bit key, switch OpenDKIM to s2, wait a week so delayed mail still verifies against s1, then delete the old TXT. A 1024-bit key still verifies at some providers and is below what Gmail expects in 2026. Do not store the private key in git or in a world-readable backup of /etc. If you restore a VPS from an image, confirm the live selector still matches the key on disk before you send.

Takeaway

OpenDKIM on a small Postfix is enough to sign transactional mail. Publish a 2048-bit selector, milter both smtpd and local pickup, and confirm dkim=pass on a real inbox. You can spin up an Ubuntu 24.04 instance on Netbay in under 60 seconds and follow this signing path on a static Lucknow IP — netbayhosts.in.

Keep reading

Follow along on a real VPS

Deploy Linux in under 60 seconds

These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.

Deploy an instance