Postfix Rejecting Mail? Fix SPF, DKIM and DMARC Failures
Mail bouncing back with an SPF, DKIM or DMARC complaint means a specific record or signature is broken, not that Postfix itself is misconfigured.
Postfix rejecting mail with an SPF, DKIM or DMARC error in the bounce is almost never a Postfix bug. It means a receiving server checked one of those three things and did not like what it found. The fix depends entirely on which of the three actually failed, so the first job is finding out which one it is.
Read the actual rejection first
Do not start editing DNS records yet. /var/log/mail.log (or journalctl -u postfix on newer Ubuntu) has the exact reason a message was deferred or bounced.
grep -i 'reject\|bounce\|dkim\|spf' /var/log/mail.log | tail -40
If you have a bounce in an inbox somewhere, the Authentication-Results header in the original outbound copy (check your Sent items, or postqueue -p if it is still queued) tells you directly: spf=fail, dkim=fail or dmarc=fail. Fix the one that actually failed, checking all three blind wastes time.
SPF: the sending IP does not match the record
SPF is the simplest of the three: it is a DNS TXT record listing which IPs are allowed to send mail for your domain. If your server's IP is not in it, receiving servers see spf=fail.
dig txt yourdomain.com +short
A working record looks like v=spf1 ip4:203.0.113.10 include:_spf.google.com ~all. Add your server's outbound IP explicitly with ip4:, do not rely on a or mx mechanisms unless you are certain the sending server is the same as the one those records point to. A soft fail (~all) tells receivers to flag suspicious mail rather than reject outright, a hard fail (-all) tells them to reject it, use -all only once you are confident every legitimate sending source is listed.
DKIM: the signature is missing or invalid
DKIM signs each outgoing message with a private key, and the receiver checks it against a public key published in DNS. On Ubuntu this is normally OpenDKIM sitting in front of Postfix as a milter.
systemctl status opendkim
postconf smtpd_milters non_smtpd_milters
Both smtpd_milters and non_smtpd_milters need to point at OpenDKIM's socket, typically inet:localhost:8891. If either is empty, Postfix is sending mail unsigned regardless of how correctly OpenDKIM itself is configured.
opendkim-testkey -d yourdomain.com -s default -vvv
A response of key not secure is fine for most setups (it means DNSSEC is not in use, not that the key is broken). A response of key not found or key data mismatch means the TXT record published at default._domainkey.yourdomain.com does not match the private key OpenDKIM is signing with, usually because the key was regenerated after the DNS record was published, or the selector name in /etc/opendkim/keytable does not match the one in DNS.
DMARC: SPF or DKIM passed but do not align
DMARC failing while SPF and DKIM both technically pass usually means an alignment mismatch: the domain in SPF's Return-Path or DKIM's d= tag does not match the visible From address domain. This commonly happens when mail is relayed through a third-party service (a CRM, a support desk tool) that signs with its own domain instead of yours.
dig txt _dmarc.yourdomain.com +short
Start with v=DMARC1; p=none; rua=mailto:dmarc-reports@yourdomain.com; while you sort out alignment, since p=none requests reports without rejecting anything. Read a few days of aggregate reports to see exactly which sending source is failing alignment before moving to p=quarantine or p=reject. Tightening the policy before every legitimate sender is aligned will bounce your own mail.
Chasing down one broken record across SPF, DKIM and DMARC on a single domain is routine. Doing it for every domain hosted on a server, every time a new site gets added, is the part that turns into a full afternoon. BashEmail checks all three records and OpenDKIM's actual signing status for every hosted domain, and flags exactly which one is broken instead of leaving you to run three separate lookups.
Diagnosing a DMARC failure across every domain on the box
Checked SPF, DKIM and DMARC for all 6 hosted domains. One domain is misaligned.
| Domain | SPF | DKIM | DMARC alignment |
|---|---|---|---|
| shop.example.com | pass | pass | fail, d= does not match From |
| 5 others | pass | pass | aligned |
Its OpenDKIM signing table entry uses a leftover selector from a previous mail provider migration, so it is signing with the old d= value. Updating the signingtable entry and reloading opendkim will fix it.
sed -i 's/old-selector/default/' /etc/opendkim/signingtable
systemctl reload opendkim
Try this on one of your own servers.
Start free trialVerify the fix actually landed
Do not trust dig output alone, DNS caching can show you a stale record for hours. Send a live test message and read the real result.
- Send a message to check-auth@verifier.port25.com or use mail-tester.com, both return a full SPF/DKIM/DMARC breakdown within a minute.
- If you have a Gmail account handy, send to it and open the message, then View Original to read the live
Authentication-Resultsheader directly. - Re-run
opendkim-testkeyafter any DNS change and wait for your resolver's TTL to expire first, a cached negative result will still show as failing.
Prevent this from recurring
- Set the server's PTR (reverse DNS) record with your hosting provider to match the hostname Postfix announces in its HELO or EHLO. A missing PTR is treated as suspicious by most receiving servers independent of SPF, DKIM or DMARC.
- Keep SPF, DKIM and DMARC on a checklist for every new domain you add to the server, not just the first one you configured.
- Subscribe to DMARC aggregate reports (
rua=) permanently, not just while debugging, they catch a broken signature before it turns into a bounce.