Email security for servers: SPF, DKIM, DMARC and MTA-STS
Anyone can send mail that claims to come from your domain. SPF, DKIM and DMARC let receivers check that claim and reject fakes. MTA-STS makes other servers use verified TLS when they deliver to you. Gmail and Yahoo now require these basics, so without them your own mail lands in spam or bounces.
Why spoofing and blocklisting happen
SMTP, the protocol that moves mail between servers, has no built-in check on the sender. A spammer can put From: billing@example.com on a message from any server. Receivers therefore look at DNS records that you publish:
- SPF (RFC 7208)
- A list of servers allowed to send mail for your domain. It checks the hidden envelope sender (the “MAIL FROM” or bounce address), not the visible From.
- DKIM (RFC 6376)
- Your server signs each message with a private key. Receivers fetch the public key from DNS and check the signature. It survives forwarding as long as the message is not changed.
- DMARC (RFC 9989)
- Ties SPF and DKIM to the visible From domain (“alignment”), tells receivers what to do with failures, and asks them to send you reports.
- MTA-STS (RFC 8461) and TLS-RPT (RFC 8460)
- Protect mail coming to you from downgrade and interception, and report TLS problems.
Blocklisting is a separate problem with overlapping causes. Your IP or domain gets listed when spam leaves it: a hacked mailbox password, a vulnerable web form or script, a compromised server, or an open relay. Missing reverse DNS and missing authentication make listings and spam-folder placement more likely. If you are already listed, see IP blacklisted.
What Gmail and Yahoo require
| Requirement | All senders | Bulk senders |
|---|---|---|
| SPF and DKIM | SPF or DKIM | Both |
| DMARC record | — | Yes; p=none is enough |
| From domain aligned with SPF or DKIM | — | Yes |
| Valid forward and reverse DNS for sending IPs | Yes | Yes |
| Spam complaint rate | Below 0.3% | Below 0.3% (Google advises under 0.1%) |
| One-click unsubscribe | — | Yes, for marketing mail |
Google counts more than 5,000 messages a day to Gmail accounts as bulk, also requires TLS for all senders, and asks for DKIM keys of at least 1024 bits (2048 recommended). Reverse DNS means the PTR record of your sending IP names a host whose A or AAAA record points back to that IP. Set the PTR at your hosting provider.
SPF: list your senders
; server at 203.0.113.10 plus a mailing service
example.com. IN TXT "v=spf1 mx ip4:203.0.113.10 include:_spf.mail-provider.example -all"
; a domain that never sends or receives mail
parked.example. IN TXT "v=spf1 -all"
parked.example. IN MX 0 .
- Publish exactly one SPF record per name. Two records give a permanent error.
- The terms
include,a,mx,ptr,existsandredirecteach cost a DNS lookup. More than 10 in total, counting nested includes, is a permanent error, so SPF fails for everyone.ip4andip6cost nothing. - Do not use
ptr; the RFC itself says so. - End with
-all(fail) once you have listed every sender;~all(softfail) while you are still finding them. MX 0 .is a “null MX” (RFC 7505): it says the domain accepts no mail.
DKIM with OpenDKIM and Postfix
Install the packages: sudo apt install opendkim opendkim-tools on Debian and Ubuntu, or sudo dnf install opendkim opendkim-tools from EPEL on RHEL, Rocky and AlmaLinux. Then make a 2048-bit key. Use a dated selector so you can rotate later:
sudo mkdir -p /etc/opendkim/keys/example.com
sudo opendkim-genkey -b 2048 -d example.com -s s2026 -D /etc/opendkim/keys/example.com
sudo chown -R opendkim:opendkim /etc/opendkim/keys
sudo chmod 600 /etc/opendkim/keys/example.com/s2026.private
sudo cat /etc/opendkim/keys/example.com/s2026.txt # the DNS record to publish
Publish the contents of s2026.txt as a TXT record at s2026._domainkey.example.com. A 2048-bit key is longer than one 255-character DNS string, so the file splits it into several quoted strings; keep them that way. Set these lines in /etc/opendkim.conf (one domain, one key):
Mode sv
Domain example.com
Selector s2026
KeyFile /etc/opendkim/keys/example.com/s2026.private
Canonicalization relaxed/simple
OversignHeaders From
Socket inet:8891@localhost
UserID opendkim
OpenDKIM signs mail from the hosts in InternalHosts (default 127.0.0.1) and verifies the rest. A TCP socket avoids path problems when Postfix runs chrooted. For several domains, use KeyTable and SigningTable instead of the three single-key lines. Connect Postfix in /etc/postfix/main.cf:
smtpd_milters = inet:localhost:8891
non_smtpd_milters = $smtpd_milters
milter_default_action = tempfail
Note that Postfix writes inet:host:port while OpenDKIM writes inet:port@host. With tempfail (the default), mail waits if OpenDKIM is down; accept sends it unsigned instead. Apply and test. This restarts OpenDKIM and reloads Postfix:
sudo systemctl restart opendkim
sudo postfix check && sudo systemctl reload postfix
sudo opendkim-testkey -d example.com -s s2026 -k /etc/opendkim/keys/example.com/s2026.private -v
Or sign with rspamd
If you already run rspamd for spam filtering, let it sign and don’t add OpenDKIM too. Its default key path is /var/lib/rspamd/dkim/$domain.$selector.key:
sudo rspamadm dkim_keygen -s s2026 -b 2048 -d example.com -k /var/lib/rspamd/dkim/example.com.s2026.key
# prints the DNS record; then in /etc/rspamd/local.d/dkim_signing.conf:
# selector = "s2026";
# path = "/var/lib/rspamd/dkim/$domain.$selector.key";
# Postfix main.cf: smtpd_milters = inet:localhost:11332
Make the key readable only by the user rspamd runs as (check with ps -o user= -C rspamd), then restart rspamd. By default rspamd signs mail from authenticated users and local networks.
DMARC: monitor, then enforce
DMARC passes when SPF or DKIM passes and its domain matches the From domain. By default the match is relaxed, so mail.example.com aligns with example.com. The current standard, RFC 9989 (May 2026), replaced RFC 7489. Records still start with v=DMARC1. It removed the pct tag and added t=y (test mode) and np (policy for subdomains that don’t exist).
; step 1: monitor only
_dmarc.example.com. IN TXT "v=DMARC1; p=none; rua=mailto:dmarc-reports@example.com"
; final: reject failures, including made-up subdomains
_dmarc.example.com. IN TXT "v=DMARC1; p=reject; np=reject; rua=mailto:dmarc-reports@example.com"
- p=none with
rua. Receivers send aggregate reports: XML files listing which IPs sent mail as your domain and whether SPF and DKIM passed. Use a report parser or service; they are hard to read by hand. - Fix every legitimate source the reports show: web apps, the CRM, the invoicing system, the newsletter tool. Each needs aligned DKIM, or aligned SPF at least.
- p=quarantine. You can add
t=yfirst; receivers then apply one level lower (none instead of quarantine) while you watch. - p=reject. The RFC says a domain with
p=rejectmust sign with DKIM and not rely on SPF alone, because forwarding breaks SPF. It also warns thatp=rejecton domains whose users post to mailing lists causes trouble for those lists.
If reports go to another domain, that domain must agree: publish example.com._report._dmarc.reports.example.net as a TXT record containing v=DMARC1. Failure reports (ruf) can contain parts of real messages, so think about privacy before you ask for them.
MTA-STS and TLS-RPT
Servers deliver mail to your MX using STARTTLS, but by default a sender falls back to plain text if TLS fails, and does not check your certificate. An attacker on the path can strip TLS. MTA-STS publishes a policy saying “my MX hosts are these, and they have valid certificates; don’t deliver otherwise.”
_mta-sts.example.com. IN TXT "v=STSv1; id=20261103T1200;"
_smtp._tls.example.com. IN TXT "v=TLSRPTv1; rua=mailto:tls-reports@example.com"
Serve the policy over HTTPS at https://mta-sts.example.com/.well-known/mta-sts.txt as text/plain, with a valid certificate:
version: STSv1
mode: testing
mx: mail.example.com
max_age: 604800
- Your MX hosts present certificates from a public CA that match their names (Let’s Encrypt works; see HTTPS and TLS).
- Start in
mode: testingand read the TLS-RPT reports for a few weeks, then switch toenforce. - Change the
idin DNS every time you change the policy file, or senders keep the cached copy. - Raise
max_age(in seconds, maximum 31557600) to weeks or more once stable.
This protects mail to you. Postfix does not check other domains’ MTA-STS policies on its own; the add-on daemon postfix-mta-sts-resolver supplies them through smtp_tls_policy_maps.
Check your setup
dig +short TXT example.com | grep spf1
dig +short TXT s2026._domainkey.example.com
dig +short TXT _dmarc.example.com
dig +short TXT _mta-sts.example.com
dig +short -x 203.0.113.10 # PTR of the sending IP
curl -s https://mta-sts.example.com/.well-known/mta-sts.txt
Then send a message to a Gmail or other outside mailbox and read the Authentication-Results header: you want spf=pass, dkim=pass and dmarc=pass. Keep the mail software itself patched too: see Exim and Postfix. Watch the queue (postqueue -p) for sudden growth, which is often the first sign of a hacked account sending spam.
Sources
- RFC 7208: Sender Policy Framework (SPF), RFC Editor
- RFC 6376: DomainKeys Identified Mail (DKIM) Signatures, RFC Editor
- RFC 8301: Cryptographic Algorithm and Key Usage Update to DKIM, RFC Editor
- RFC 9989: Domain-Based Message Authentication, Reporting, and Conformance (DMARC), RFC Editor
- RFC 9990: DMARC Aggregate Reporting, RFC Editor
- RFC 8461: SMTP MTA Strict Transport Security (MTA-STS), RFC Editor
- RFC 8460: SMTP TLS Reporting, RFC Editor
- RFC 7505: A “Null MX” No Service Resource Record, RFC Editor
- Email sender guidelines, Google Workspace Admin Help
- Sender Best Practices, Yahoo Sender Hub
- opendkim.conf(5), Debian manpages
- opendkim-genkey(8), Debian manpages
- opendkim-testkey(8), Debian manpages
- opendkim package (Fedora and EPEL), Fedora Packages
- Postfix before-queue Milter support, postfix.org
- DKIM signing module, Rspamd documentation
- MTA integration, Rspamd documentation
- postfix-mta-sts-resolver, GitHub