If you've ever sent an email from your own server and watched it land in someone's spam folder — or worse, get rejected outright — the cause is almost always the same thing: nothing on the receiving end can verify the mail actually came from you. Mailbox providers like Gmail and Outlook don't take a server's word for it anymore. They check.
This tutorial sets up the three checks that matter — SPF, DKIM, and DMARC — on a Postfix mail server running on Ubuntu 24.04, so outbound mail from your dedicated server authenticates cleanly instead of getting flagged as suspicious.
Why This Matters More on a Dedicated Server
On shared hosting or a mass-market email service, deliverability is somebody else's problem — you're sending from an IP with an existing reputation that thousands of other accounts have already shaped, for better or worse. On a dedicated server, the reputation of your outbound IP is entirely yours.
That's an advantage once it's established — nobody else's bad behaviour can drag your sender score down — but it also means there's no existing trust to borrow on day one. A new IP sending unauthenticated mail looks exactly like what spam looks like, even when it isn't. SPF, DKIM, and DMARC are how you build that trust deliberately instead of hoping for the best.
What Each One Actually Does
It's worth understanding the division of labour before configuring anything, since each protects against a different problem:
SPF (Sender Policy Framework) is a DNS record that lists which mail servers are allowed to send mail for your domain. It answers: "is this server permitted to send as me?"
DKIM (DomainKeys Identified Mail) cryptographically signs each outgoing message with a private key, with the matching public key published in DNS. It answers: "was this message altered in transit, and did it really come from a server holding my private key?"
DMARC (Domain-based Message Authentication, Reporting and Conformance) is the policy layer that tells receiving servers what to do when SPF or DKIM checks fail — and where to send reports about it. It answers: "if authentication fails, what should happen, and who should know?"
All three work together. DMARC in particular depends on SPF and/or DKIM passing — it has nothing to enforce on its own.
Prerequisites
A dedicated server running Ubuntu 24.04 LTS, with a domain you control DNS for.
Postfix already installed and able to send mail (
apt install postfixif not).A clean reverse DNS (PTR) record pointing your server's IP back to your mail hostname (e.g. mail.yourdomain.com). Ask your hosting provider to set this if you can't do it yourself. DKIM and SPF won't compensate for a missing or mismatched PTR record.
Step 1: Set Up SPF
SPF is the simplest of the three: it's just a DNS TXT record, no software required.
Add a TXT record at your domain's root:
yourdomain.com. IN TXT "v=spf1 mx -all"
This authorizes your domain's MX servers to send mail and tells receivers to reject (-all) anything from elsewhere claiming to be you. If you also send through additional services (Google Workspace, a CRM), add their include: mechanism:
yourdomain.com. IN TXT "v=spf1 mx ip4:203.0.113.10 include:_spf.google.com -all"
While testing, it's safer to use ~all (softfail) instead of -all (hardfail). Softfail tells receivers to flag a failure but generally still deliver the message:
yourdomain.com. IN TXT "v=spf1 mx ~all"
Switch to -all once you've confirmed legitimate mail is passing. Never use +all — that authorizes literally anyone to send mail claiming to be your domain. Verify it after DNS propagates:
dig +short TXT yourdomain.com
Step 2: Choose and Install a DKIM Signer
Postfix has no DKIM signing built in — it needs a separate service (a "milter") that intercepts outgoing mail and signs it before it leaves.
This tutorial uses OpenDKIM, since it remains the more commonly deployed option for a single-domain signing setup. Install it:
sudo apt update
sudo apt install -y opendkim opendkim-tools
Step 3: Generate a DKIM Key Pair
Create a key directory and generate a 2048-bit key (1024-bit keys are deprecated and shouldn't be used):
sudo mkdir -p /etc/opendkim/keys/yourdomain.com
sudo opendkim-genkey -b 2048 -d yourdomain.com -D /etc/opendkim/keys/yourdomain.com -s mail -v
sudo chown -R opendkim:opendkim /etc/opendkim/keys
The -s mail flag sets the selector — effectively a label for this key, used in the DNS record name. This generates two files: mail.private (the private key) and mail.txt (the public key for DNS).
Step 4: Configure OpenDKIM
Edit /etc/opendkim.conf:
Syslog yes
SyslogSuccess yes
LogWhy yes
Mode sv
Canonicalization relaxed/simple
Domain yourdomain.com
Selector mail
KeyFile /etc/opendkim/keys/yourdomain.com/mail.private
Socket local:/var/spool/postfix/opendkim/opendkim.sock
PidFile /var/run/opendkim/opendkim.pid
ExternalIgnoreList refile:/etc/opendkim/TrustedHosts
InternalHosts refile:/etc/opendkim/TrustedHosts
Mode sv enables both signing outgoing mail and verifying incoming mail. Create /etc/opendkim/TrustedHosts:
127.0.0.1
::1
localhost
yourdomain.com
Create the socket directory Postfix needs to reach OpenDKIM, since Postfix's mail process runs in a chroot:
sudo mkdir -p /var/spool/postfix/opendkim
sudo chown opendkim:postfix /var/spool/postfix/opendkim
sudo chmod 750 /var/spool/postfix/opendkim
sudo usermod -a -G opendkim postfix
Step 5: Connect OpenDKIM to Postfix
Append to /etc/postfix/main.cf:
milter_protocol = 6
milter_default_action = accept
smtpd_milters = local:opendkim/opendkim.sock
non_smtpd_milters = $smtpd_milters
Restart both services, OpenDKIM first since Postfix needs it available immediately on startup:
sudo systemctl restart opendkim
sudo systemctl restart postfix
Step 6: Publish the DKIM DNS Record
Print the public key generated in Step 3:
sudo cat /etc/opendkim/keys/yourdomain.com/mail.txt
It outputs something like:
mail._domainkey IN TXT ( "v=DKIM1; k=rsa; "
"p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC2hL...etc" )
Add this as a TXT record at mail._domainkey.yourdomain.com in your DNS. Set the TTL low (around 300 seconds) for now, so any mistake is cheap to fix while you're testing. Verify once it's propagated:
dig +short TXT mail._domainkey.yourdomain.com
sudo opendkim-testkey -d yourdomain.com -s mail -vvv
Step 7: Publish a DMARC Record
DMARC is, again, just a DNS TXT record — but at a specific subdomain:
_dmarc.yourdomain.com. IN TXT "v=DMARC1; p=none; rua=mailto:dmarc-reports@yourdomain.com; pct=100"
Start with p=none. This is monitor-only mode: receivers don't take any enforcement action, but they do send aggregate reports to the address in rua=, which tells you what's actually happening with your mail before you risk blocking anything legitimate.
After reviewing reports for a couple of weeks and confirming all your legitimate sending sources pass, tighten the policy in stages:
"v=DMARC1; p=quarantine; rua=mailto:dmarc-reports@yourdomain.com; pct=100"
Then, once you're confident nothing legitimate is being caught:
"v=DMARC1; p=reject; rua=mailto:dmarc-reports@yourdomain.com; pct=100"
Step 8: Verify Everything Together
Send a test message to a Gmail or Outlook address you control, then check the message's authentication headers (in Gmail: open the message, click the three-dot menu, "Show original"). You're looking for a line resembling:
Authentication-Results: mx.google.com;
dkim=pass header.d=yourdomain.com;
spf=pass (google.com: domain of you@yourdomain.com designates 203.0.113.10 as permitted sender);
dmarc=pass (p=NONE)
All three showing pass confirms the full chain is working.
A Note on Key Rotation
DKIM keys aren't meant to be set once and forgotten. Plan to rotate the key roughly once a year, or immediately if you suspect the private key has been exposed. The safe sequence is:
Generate a new key with a new selector (e.g.
mail2026binstead ofmail)Publish the new selector's DNS TXT record alongside the old one — don't remove the old one yet
Update OpenDKIM's configuration to sign with the new selector
Leave the old DNS TXT record live for at least 48–72 hours after switching, since mail already in transit may still get checked against the old key
Remove the old selector's DNS record only after that window passes
Frequently Asked Questions
Why is my email going to spam even though I haven't changed anything?
Deliverability isn't usually broken by a single misconfiguration — it's the absence of SPF, DKIM, or DMARC entirely that mailbox providers now treat as a negative signal by default, even for mail that isn't spam. If none of the three are set up, this guide's steps are usually the fix; if they're already in place, check your reverse DNS (PTR) record next.
Do I need all three — SPF, DKIM, and DMARC — or is one enough?
DMARC requires at least one of SPF or DKIM to be configured to have anything to enforce, so it can't stand alone. SPF alone is fragile because it breaks when mail is forwarded through an intermediate server. Running all three together is what current best practice — and what large mailbox providers effectively require — actually means.
What's the difference between OpenDKIM and Rspamd, and does it matter which I pick?
Both sign outgoing mail with DKIM correctly. OpenDKIM is simpler to set up for a single domain doing DKIM and nothing else. Rspamd is more actively maintained and bundles spam filtering with DKIM signing in one service, which is generally the better choice if you're also setting up inbound spam protection. The DNS records you publish are the same either way.
How long does it take for SPF, DKIM, and DMARC changes to take effect?
DNS propagation is typically a matter of minutes to a few hours, though it can take up to 24–48 hours in less common cases depending on your DNS provider and the TTL set on the record. Setting a short TTL (around 300 seconds) while actively testing makes mistakes faster to correct.
Can I skip the monitoring stage and go straight to a strict DMARC policy?
It's possible, but risky. p=reject with no prior monitoring period means any sending path you forgot about — a transactional email service, a contact form, a mailing list — starts having its mail silently rejected with no warning. Starting at p=none and reviewing the aggregate reports first is what catches those gaps.