Back to tutorials
Tutorial

DKIM Setup Tutorial (2026): Configure Email Authentication on a VPS (Postfix + OpenDKIM) to Stop Spam Flags

DKIM setup tutorial for 2026: configure Postfix + OpenDKIM on a VPS, add DNS records, and verify mail authentication end-to-end.

By Anurag Singh
Updated on Aug 29, 2026
Category: Tutorial
Share article
DKIM Setup Tutorial (2026): Configure Email Authentication on a VPS (Postfix + OpenDKIM) to Stop Spam Flags

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 -f to 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/opendkim ownership is opendkim:opendkim
  • opendkim.conf uses UMask 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 full s2026._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 exists
  • Authentication-Results: shows dkim=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.conf and 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.