
Email deliverability problems often appear before anyone reads your subject line. If your domain doesn’t publish DKIM, Gmail and Microsoft 365 may quarantine, spam, or reject your mail. The same thing happens when your DNS record and server key don’t match.
This DKIM setup tutorial shows how to set up DKIM on a Linux VPS running Postfix with OpenDKIM. You’ll publish the DNS record and verify the full authentication chain end to end.
The steps assume Ubuntu 24.04 LTS or Debian 12/13 on a VPS. Everything uses standard packages and predictable file paths. You don’t need a control panel.
What you’ll build (and what you need before you start)
You’re going to:
- Install OpenDKIM and integrate it with Postfix via milter
- Generate a DKIM key pair for your domain
- Publish the DKIM public key as a DNS TXT record
- Send test mail and confirm DKIM passes
Prerequisites (don’t skip these):
- A VPS where you have root/sudo access
- A domain you control DNS for (A/AAAA and MX already point correctly)
- A proper hostname (FQDN) for the server that resolves forward and reverse (PTR)
If reverse DNS still isn’t set, fix that first. It’s one of the fastest ways to reduce outright rejections.
Use this related guide: PTR record setup tutorial.
Need a clean server for mail + hosting workloads? Start with a HostMyCode VPS. You’ll be able to control rDNS, firewall rules, and mail logs without shared-hosting constraints.
Step 1: Quick preflight checks (DNS, hostname, time)
Run these commands on your VPS:
hostnamectl
hostname -f
# Check time sync (DKIM relies on correct timestamps)
timedatectl
# Confirm Postfix is installed and running
postconf mail_version
systemctl status postfix --no-pager
You want:
hostname -fto return a real FQDN (example:mail.example.com)- NTP active (
System clock synchronized: yes)
Pitfall: If your VPS hostname is localhost or a provider default, some receivers will distrust your mail. This can happen even when DKIM passes.
DKIM setup tutorial: Install OpenDKIM and dependencies
On Ubuntu/Debian:
sudo apt update
sudo apt install -y opendkim opendkim-tools
Confirm the service exists and is running:
systemctl status opendkim --no-pager
If you run a firewall, don’t block outbound SMTP to your relay or recipients.
For common firewall mistakes that break mail delivery, see: VPS firewall troubleshooting tutorial.
Step 3: Create directories and generate a DKIM key
We’ll store keys under /etc/opendkim/keys. We’ll generate a 2048-bit key, which is a common best practice in 2026.
Set variables (replace with your domain and selector):
DOMAIN="example.com"
SELECTOR="s2026"
Create the key directory and generate the key pair:
sudo mkdir -p /etc/opendkim/keys/${DOMAIN}
cd /etc/opendkim/keys/${DOMAIN}
sudo opendkim-genkey -b 2048 -s ${SELECTOR} -d ${DOMAIN}
# Lock down permissions
sudo chown -R opendkim:opendkim /etc/opendkim
sudo chmod 0700 /etc/opendkim/keys
sudo chmod 0700 /etc/opendkim/keys/${DOMAIN}
sudo chmod 0600 /etc/opendkim/keys/${DOMAIN}/${SELECTOR}.private
This creates:
s2026.private(private key, stays on the server)s2026.txt(public key formatted for DNS)
Step 4: Configure OpenDKIM (KeyTable, SigningTable, TrustedHosts)
OpenDKIM uses three small mapping files. Create them under /etc/opendkim.
4.1 TrustedHosts
This file lists sources you trust to submit mail for signing. In most setups, that means localhost plus your server IP/hostname.
sudo nano /etc/opendkim/TrustedHosts
Example contents:
127.0.0.1
localhost
# Your server public IP (replace)
203.0.113.10
# Your server hostname
mail.example.com
4.2 KeyTable
sudo nano /etc/opendkim/KeyTable
Example:
s2026._domainkey.example.com example.com:s2026:/etc/opendkim/keys/example.com/s2026.private
4.3 SigningTable
sudo nano /etc/opendkim/SigningTable
Example (sign all mail from the domain):
*@example.com s2026._domainkey.example.com
If you host multiple domains, add one line per domain. Keep keys in separate domain folders.
Avoid reusing one DKIM key across unrelated brands. If something goes wrong, cleanup and rotation get messy fast.
Step 5: Update OpenDKIM main config and socket
Edit /etc/opendkim.conf (this path is standard on Ubuntu/Debian):
sudo nano /etc/opendkim.conf
Make sure these settings exist (add or adjust them):
# Basic
Syslog yes
SyslogSuccess yes
LogWhy yes
# Sign and verify
Mode sv
# Mappings
KeyTable /etc/opendkim/KeyTable
SigningTable refile:/etc/opendkim/SigningTable
ExternalIgnoreList /etc/opendkim/TrustedHosts
InternalHosts /etc/opendkim/TrustedHosts
# Recommended canonicalization
Canonicalization relaxed/simple
# Use a local socket for Postfix milter
Socket local:/run/opendkim/opendkim.sock
UMask 002
Now confirm the runtime directory exists and has the right ownership. Many systemd installs create it automatically. It’s still worth checking.
sudo mkdir -p /run/opendkim
sudo chown opendkim:opendkim /run/opendkim
On Ubuntu, the socket may also be set in /etc/default/opendkim. If the service won’t start and complains about the socket, check that file.
sudo nano /etc/default/opendkim
Look for a line like:
SOCKET="local:/run/opendkim/opendkim.sock"
Step 6: Connect Postfix to OpenDKIM (milter settings)
Apply the milter settings to Postfix:
sudo postconf -e "milter_default_action=accept"
sudo postconf -e "milter_protocol=6"
sudo postconf -e "smtpd_milters=local:/run/opendkim/opendkim.sock"
sudo postconf -e "non_smtpd_milters=local:/run/opendkim/opendkim.sock"
Restart services:
sudo systemctl restart opendkim
sudo systemctl restart postfix
Then scan logs for anything obviously wrong:
sudo journalctl -u opendkim -n 80 --no-pager
sudo journalctl -u postfix -n 80 --no-pager
Common failure: Postfix can’t access the socket due to permissions.
If you see “permission denied” for the milter socket, confirm:
/run/opendkimownership isopendkim:opendkimopendkim.confusesUMask 002
Step 7: Publish the DKIM DNS TXT record
Open the generated TXT file and copy the record value:
sudo cat /etc/opendkim/keys/${DOMAIN}/${SELECTOR}.txt
You’ll see something like:
s2026._domainkey IN TXT ( "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0B..." )
In your DNS provider (or your authoritative DNS server), create the record:
- Type: TXT
- Name/Host:
s2026._domainkey(some providers require the fulls2026._domainkey.example.com) - Value: the
v=DKIM1; k=rsa; p=...string (without extra quotes/parentheses) - TTL: 300–3600 seconds is fine for new records
DNS formatting tip: Many DNS dashboards wrap long TXT values for display. That’s OK.
DKIM breaks when characters go missing. It also breaks when spaces get inserted into the key or when you paste the surrounding " quotes.
If you’re planning a mail cutover soon, lower TTL ahead of time. This helps changes propagate faster. Use: DNS TTL reduction tutorial.
Step 8: Verify DKIM DNS propagation from the VPS
After you add the TXT record, query it from the server:
sudo apt install -y dnsutils
dig +short TXT ${SELECTOR}._domainkey.${DOMAIN}
You should see the DKIM value returned. If it’s blank:
- Double-check the record name in your DNS UI (relative name vs FQDN)
- Confirm you edited the correct DNS zone (easy mistake with multiple providers)
- Wait for propagation if you set a high TTL
Step 9: Send a real test email and read the headers
Send a message to a mailbox you control. Gmail and Outlook are good choices. They clearly show authentication results.
From the VPS:
echo "DKIM test $(date -Is)" | mail -s "DKIM test ${DOMAIN}" you@gmail.com
If mail isn’t installed:
sudo apt install -y bsd-mailx
Open the received email, view “Original” / “Message source”, and confirm:
DKIM-Signature:header existsAuthentication-Results:showsdkim=pass
Also validate locally by checking Postfix logs:
sudo grep -i opendkim /var/log/mail.log | tail -n 50
If you don’t have /var/log/mail.log (some distros/logging setups differ), use journalctl:
sudo journalctl -u opendkim -u postfix -n 200 --no-pager
Step 10: Add SPF and DMARC (so DKIM actually pays off)
DKIM helps, but most receivers score your domain on SPF + DKIM alignment under DMARC.
For a single sending VPS in 2026, a basic setup often looks like this:
10.1 SPF record (TXT at the root)
Example SPF allowing the VPS IP to send:
v=spf1 ip4:203.0.113.10 -all
10.2 DMARC record (TXT at _dmarc)
Start in monitoring mode. This avoids rejecting legitimate mail while you confirm alignment.
v=DMARC1; p=none; rua=mailto:dmarc@example.com; adkim=s; aspf=s; fo=1
Why “p=none” first: It collects reports without blocking mail while you validate alignment.
After a week or two of clean results, consider quarantine or reject.
If your WordPress site sends mail, avoid PHP mail() directly. Route mail through authenticated SMTP so alignment stays consistent.
Pair this tutorial with: SMTP relay setup guide tutorial.
Troubleshooting: DKIM fails, missing signature, or “body hash did not verify”
These issues are the most common. The checks below usually point to the real cause.
No DKIM-Signature header at all
- Cause: Postfix isn’t using the milter socket.
- Check:
postconf | grep -E 'milter|smtpd_milters|non_smtpd_milters' - Fix: Confirm socket path matches
/etc/opendkim.confand Postfix config.
DKIM=fail because DNS record is wrong
- Cause: TXT record pasted with quotes/line breaks or wrong host name.
- Check:
dig TXT s2026._domainkey.example.com +short - Fix: Re-paste the
p=value carefully; keep it as one continuous key string.
“body hash did not verify”
- Cause: A relay or filter modifies the message after signing (common with footer/banner rewriters).
- Fix: Ensure your signing happens after content modifications. If you use a relay provider, sign at the last hop you control or disable rewriting.
Mail is accepted but still goes to spam
- Cause: Reputation or missing alignment (SPF/DMARC), weak reverse DNS, or mismatched HELO.
- Fix: Confirm PTR + hostname alignment, add SPF + DMARC, and keep volume consistent.
If you’re dealing with bounces and hard rejects, this walk-through helps map error codes to real fixes: Email bounce troubleshooting tutorial.
Operational checklist (what to document for future you)
- Selector name (example:
s2026) and where the private key lives - Who can edit DNS, and where the DKIM record is managed
- Rotation plan (at least annually, and immediately after any compromise)
- Backup approach for
/etc/opendkim(private keys are sensitive—encrypt backups) - Monitoring: alert if DKIM starts failing or mail queue grows
For practical alerting, adapt the workflow in: VPS log monitoring tutorial.
Summary: a clean DKIM implementation you can verify in minutes
You installed OpenDKIM, generated a per-domain key, and published the DNS TXT record. You also configured Postfix to sign outbound mail through a local milter socket.
Next, focus on alignment. Add SPF and DMARC, keep hostname/PTR consistent, and avoid sending from random PHP scripts.
If you want a predictable environment for mail + websites—where you can control DNS, rDNS, firewall, and logs—run it on a HostMyCode VPS.
If you’d rather not maintain mail security updates and deliverability tuning yourself, consider managed VPS hosting for hands-on admin support.
If you’re setting up authenticated email on a VPS, start with infrastructure you can actually debug under pressure. HostMyCode VPS gives you root access for OpenDKIM, Postfix, and logs. If you prefer the mail stack configured and maintained for you, managed VPS hosting is a better fit when uptime and risk reduction matter more than DIY control.
FAQ: DKIM setup on a VPS
Should I use one DKIM selector or multiple?
Start with one selector per domain. Add a new selector when rotating keys or separating mail streams (marketing vs transactional) if you truly need it.
Do I need DKIM if I already have SPF?
Yes. SPF authenticates the sending IP; DKIM authenticates the message signature. DMARC policies and receiver scoring work best when both pass and align.
Can I do DKIM on a server that relays through a third-party SMTP provider?
Usually yes, but be careful. If the provider modifies headers/body after you sign, DKIM can fail. Many providers can sign on your behalf; pick one signing point and verify headers.
How often should I rotate DKIM keys in 2026?
At least annually, and immediately after any suspected server compromise or credential leakage. Rotation is also smart after major mail stack changes.
What’s the fastest way to confirm DKIM is working?
Send a test message to Gmail, open “Show original,” and confirm dkim=pass in Authentication-Results. Then validate DNS with dig from your VPS.