
Most “my emails land in spam” tickets trace back to missing authentication, not message wording. A proper DKIM signature gives receiving servers a cryptographic way to confirm the email left your domain. It also confirms the message wasn’t changed in transit.
This DKIM setup guide tutorial walks through a repeatable DKIM deployment on a Linux VPS using Postfix + OpenDKIM. It includes real file paths, DNS records, and tests you can run right away.
The steps below match a typical VPS that sends mail for WordPress, contact forms, and small apps. The same approach works for higher-volume or multi-domain setups. You just need stricter discipline around key storage, permissions, and DNS records.
If you don’t want to maintain mail daemons, keep the web stack on a managed VPS hosting plan. That way, someone else handles the operational burden.
What you’ll build (and what DKIM actually signs)
DKIM signs selected message headers (for example From:, Subject:, Date:) and optionally the body. It uses a private key stored on your server.
Your DNS publishes the matching public key as a TXT record under a selector such as mail2026._domainkey.example.com. Receivers verify the signature against DNS. If everything matches, DKIM passes.
- Goal: Every outbound message from your domain leaves with a valid DKIM signature.
- Tools: Postfix + OpenDKIM on Ubuntu/Debian (commands included). Notes for RHEL-family included.
- Outcome: Better inbox placement and fewer “spoofing” rejections (especially once you enforce DMARC later).
Prerequisites checklist (do this before touching DKIM)
Don’t skip this. You can configure DKIM “correctly” and still fail if your hostname, DNS, or outbound SMTP basics are messy.
- A VPS with root access (Ubuntu 24.04 LTS / Debian 12 are common in 2026).
- A stable hostname that matches DNS (
mail.example.comis typical). - Working outbound SMTP (port 25 or submission ports as configured).
- Domain DNS access (so you can add TXT records).
If your mail hostname or SMTP banner looks questionable, fix that first: email server hostname setup tutorial.
If you also need reverse DNS for deliverability, follow: PTR record setup tutorial.
Step 1 — Install OpenDKIM packages
On Ubuntu/Debian:
sudo apt update
sudo apt install -y opendkim opendkim-tools
On AlmaLinux/Rocky:
sudo dnf install -y opendkim opendkim-tools
Confirm the service exists:
systemctl status opendkim --no-pager
Step 2 — Create a DKIM key pair (selector-based)
Pick a selector you can keep long-term. Date-based selectors make rotation easier later (example: mail2026).
Create a directory for your domain keys:
sudo install -d -m 0750 -o opendkim -g opendkim /etc/opendkim/keys/example.com
Generate the key, then lock down permissions:
cd /etc/opendkim/keys/example.com
sudo opendkim-genkey -s mail2026 -d example.com
sudo chown opendkim:opendkim mail2026.private mail2026.txt
sudo chmod 0600 mail2026.private
You should now have:
/etc/opendkim/keys/example.com/mail2026.private(private key)/etc/opendkim/keys/example.com/mail2026.txt(DNS TXT record template)
Step 3 — Configure OpenDKIM (KeyTable, SigningTable, TrustedHosts)
This layout is standard and easy to maintain. It works well for one domain or many.
3.1 Create KeyTable
Create /etc/opendkim/KeyTable:
sudo tee /etc/opendkim/KeyTable >/dev/null <<'EOF'
mail2026._domainkey.example.com example.com:mail2026:/etc/opendkim/keys/example.com/mail2026.private
EOF
3.2 Create SigningTable
Create /etc/opendkim/SigningTable:
sudo tee /etc/opendkim/SigningTable >/dev/null <<'EOF'
*@example.com mail2026._domainkey.example.com
EOF
This signs any message with a From: in @example.com.
If you host multiple domains, add one line per domain and selector.
3.3 Create TrustedHosts
Create /etc/opendkim/TrustedHosts:
sudo tee /etc/opendkim/TrustedHosts >/dev/null <<'EOF'
127.0.0.1
localhost
::1
# Your server hostname
mail.example.com
EOF
Common pitfall: OpenDKIM only signs for hosts listed here.
On split-tier setups, your web apps may send from a different internal IP. If that’s your environment, add the app server’s IP to TrustedHosts. Otherwise OpenDKIM may refuse to sign.
3.4 Point OpenDKIM to these tables
Edit /etc/opendkim.conf (same path on most distros):
sudo nano /etc/opendkim.conf
Make sure these lines exist (add them if they’re missing):
Syslog yes
SyslogSuccess yes
LogWhy yes
UMask 002
Canonicalization relaxed/simple
Mode sv
SubDomains no
KeyTable /etc/opendkim/KeyTable
SigningTable refile:/etc/opendkim/SigningTable
ExternalIgnoreList /etc/opendkim/TrustedHosts
InternalHosts /etc/opendkim/TrustedHosts
Socket local:/run/opendkim/opendkim.sock
Why the socket matters: Postfix talks to OpenDKIM through this socket.
If Postfix points to the wrong path, mail will still send. It just won’t be DKIM-signed.
Step 4 — Wire Postfix to OpenDKIM via milter
Now tell Postfix to hand outbound messages to OpenDKIM for signing.
Edit /etc/postfix/main.cf:
sudo postconf -e "milter_default_action=accept"
sudo postconf -e "milter_protocol=6"
sudo postconf -e "smtpd_milters=unix:/run/opendkim/opendkim.sock"
sudo postconf -e "non_smtpd_milters=unix:/run/opendkim/opendkim.sock"
Reload Postfix after OpenDKIM restarts. The next step covers both.
Step 5 — Fix permissions and start services cleanly
On systemd, the most common failure is the runtime directory or socket permissions.
Create a systemd override so the runtime directory exists. Ubuntu/Debian often do this already, but confirm it:
sudo systemctl edit opendkim
Add:
[Service]
RuntimeDirectory=opendkim
RuntimeDirectoryMode=0755
Then restart OpenDKIM and reload Postfix:
sudo systemctl daemon-reload
sudo systemctl restart opendkim
sudo systemctl reload postfix
Quick socket check:
sudo ls -l /run/opendkim/opendkim.sock
You’re looking for an existing socket. If it’s missing, go straight to the logs:
sudo journalctl -u opendkim -n 80 --no-pager
Step 6 — Publish the DKIM DNS TXT record
Open the generated DNS template:
sudo cat /etc/opendkim/keys/example.com/mail2026.txt
You’ll see something like:
mail2026._domainkey IN TXT ( "v=DKIM1; k=rsa; p=MIIBIjANBgkq..." )
Create a DNS TXT record in your DNS zone:
- Name/Host:
mail2026._domainkey - Type: TXT
- Value: the full
v=DKIM1; ...; p=...string (no extra quotes unless your DNS UI requires them)
Practical DNS warning: some DNS panels wrap long TXT records safely. Others mangle spacing or quotes.
If DKIM fails later, verify the published TXT value before you change anything else.
If you’re moving DNS during a hosting migration, lower TTL ahead of time and plan the cutover. This companion guide helps: DNS cutover checklist tutorial.
Step 7 — Verify DNS and confirm your server is signing
7.1 Confirm the DKIM record resolves
dig +short TXT mail2026._domainkey.example.com
You should see the public key returned. If you get nothing, DNS hasn’t propagated yet or the record is wrong.
7.2 Send a test email and inspect headers
Send a message from your domain to a mailbox you control (Gmail, Outlook, Fastmail, etc.). Open the full headers and check for:
DKIM-Signature:header presentAuthentication-Results:showsdkim=pass
From the command line, send a quick test:
printf "Subject: DKIM test\n\nHello from DKIM\n" | sendmail -v you@yourmailbox.tld
7.3 Validate with opendkim-testkey
This confirms the DNS record matches the private key you generated:
sudo opendkim-testkey -d example.com -s mail2026 -vvv
If it’s correct, you’ll see “key OK”. If it fails, the output usually points to DNS lookup problems or a selector/key mismatch.
Step 8 — Troubleshoot common DKIM failures (fast diagnostics)
Most DKIM problems are straightforward once you check the right places. Treat this as a small runbook.
No DKIM-Signature header at all
- Check the milter socket path matches in both
/etc/opendkim.confand Postfixmain.cf. - Confirm Postfix loaded the config:
postconf | egrep 'milter|smtpd_milters|non_smtpd_milters'
- Check OpenDKIM logs:
sudo journalctl -u opendkim -n 120 --no-pager
DKIM fails (dkim=fail) even though the header exists
- DNS record pasted incorrectly (extra spaces, broken quoting, missing chunks of the key).
- Selector mismatch (you signed with
mail2026but publisheddefaultor vice versa). - Wrong domain in SigningTable (signing
@example.combut your From is@www.example.comor a different domain).
Re-run:
sudo opendkim-testkey -d example.com -s mail2026 -vvv
Some mail signs, some doesn’t (WordPress vs SMTP users)
This almost always means you have multiple outbound paths.
A common example is WordPress using local sendmail while users submit via authenticated SMTP.
To cover both paths, you need:
smtpd_milters(SMTP sessions into Postfix)non_smtpd_milters(local submissions likesendmail)
If you’re tracing delivery end-to-end, these two HostMyCode guides pair well with DKIM:
- trace SMTP delivery failures using logs and queue IDs
- deliverability troubleshooting checklist (SPF/DKIM/rDNS/errors)
OpenDKIM says “permission denied” or can’t read the private key
Verify ownership and mode:
sudo ls -l /etc/opendkim/keys/example.com/mail2026.private
Fix if needed:
sudo chown opendkim:opendkim /etc/opendkim/keys/example.com/mail2026.private
sudo chmod 0600 /etc/opendkim/keys/example.com/mail2026.private
Step 9 — Key rotation plan (simple, safe, and realistic)
Don’t treat DKIM keys as “set and forget.” Rotation limits exposure if a key leaks. It also keeps your mail authentication posture current.
- Generate a new selector (example:
mail2027) alongside the existing one. - Publish the new selector TXT record in DNS.
- Switch SigningTable to the new selector.
- Keep the old selector in DNS for at least 7–14 days (some receivers retry or store signatures).
- Remove the old private key and then delete the old DNS record.
For multi-tenant hosting (resellers), rotation gets easier if you standardize selectors per domain.
Keep keys in per-domain directories, exactly as shown above.
Step 10 — Hardening notes for hosting VPS environments
DKIM is only one piece of email authentication. You’ll get the best results when the rest of the server is clean and predictable.
- Lock down SSH and admin access so the DKIM private key can’t be stolen. Use: SSH lockdown tutorial.
- Keep logs from eating your disk; mail + auth logs can spike during bot activity. See: logrotate setup for hosting servers.
- Back up config and keys securely (encrypted, offsite). DKIM private keys matter. A practical approach: 3-2-1 VPS backup strategy with encryption.
Summary: your DKIM deployment checklist
- Installed
opendkimandopendkim-tools - Generated selector-based keys under
/etc/opendkim/keys/<domain>/ - Configured
KeyTable,SigningTable, andTrustedHosts - Connected Postfix to the OpenDKIM milter socket
- Published DNS TXT record and verified with
dig+opendkim-testkey - Confirmed headers show
dkim=pass
If this is going on a production mail VPS, plan for safe edits and quick rollbacks. A HostMyCode VPS is a clean fit for running Postfix/OpenDKIM with full root control.
If you want lower maintenance, managed VPS hosting is the better option. It helps keep the stack stable and updated.
If you want DKIM (and the rest of your mail posture) to stay predictable in 2026, start with a VPS that offers stable networking, clean rDNS support, and enough headroom for spikes. HostMyCode offers both a self-managed HostMyCode VPS for hands-on admins and managed VPS hosting if you’d rather focus on the site than babysit the mail queue.
FAQ
Do I need DKIM if I already have SPF?
Yes. SPF validates the sending server IP for a given envelope sender. DKIM validates the message itself and is required for reliable DMARC enforcement.
What DKIM key size should I use in 2026?
Use 2048-bit RSA unless you have a specific constraint. It’s widely supported by receivers and DNS providers in 2026.
Can I use one DKIM key for multiple domains?
Technically you can, but don’t. Use separate keys per domain so rotation and incident response stay contained.
Why does DKIM pass sometimes and fail sometimes?
It’s usually multiple mail paths (local sendmail vs authenticated SMTP) or a DNS record that’s intermittently misread due to formatting. Check both milter settings and re-verify the TXT record with dig.
After DKIM, what should I do next for deliverability?
Add DMARC with reporting so you can see real-world failures before enforcing a strict policy. Also confirm your PTR/rDNS and HELO are correct and stable.