What the receiving server checks
The examples below use
example.com, the server mail.example.com and the address 203.0.113.42. Substitute your own.
0. First: outbound port 25
None of this matters if your server cannot reach anyone else. Many providers block outbound port 25 by default. We do: outbound ports 25, 465 and 587 are closed on every new VPS, and open on request for reasonable use (a personal mail server, application notifications), never for bulk sending. The details are in the network documentation. Test from the VPS:1. Hostname, A record and reverse DNS
Three values must agree: the name your server announces in itsHELO, the A record for that name, and the reverse DNS (PTR) of the IP address.
A for mail.example.com pointing to 203.0.113.42, and the domain’s MX pointing to mail.example.com. The reverse DNS does not live in your zone: the IP belongs to your provider, and only they can set it. With us, it is set from the console; the steps are in the Reverse DNS documentation.
Check that forward and reverse agree:
2. SPF: who may send
A singleTXT record at the root of the domain. For a server that is both the website and the mail server:
a authorises the address in example.com’s A record, mx those of the MX hosts, and -all states that nothing else sends for this domain. This is the record the console’s email profile writes.
If another service also sends on your behalf (hosted mailbox, newsletter tool), add its include: mechanism to the same record, not a second one.
SPF checks the envelope address (
MAIL FROM), not the From: header your recipient reads. DMARC, section 4, is what ties the two together.3. DKIM: sign what leaves
DKIM is the one of the four the console cannot write for you: the private key is generated on your server and must never leave it. You only publish the public key. On Debian or Ubuntu with Postfix, OpenDKIM does the job:s2026.private (the private key) and s2026.txt (the DNS record to publish). The selector is any name you like; putting the year in it makes rotation easier later.
In /etc/opendkim.conf, set the domain, selector and key, and replace the existing Socket line with a local port, which Postfix can reach even from its chroot:
s2026.txt as a TXT record on s2026._domainkey.example.com:
TXT string allows, so the file splits it into several quoted strings. Together they form one record, not several. Once published, check that it comes back whole and matches the private key:
key not secure line before it only means the zone is not DNSSEC-signed. It does not stop DKIM from working.
4. DMARC: tie it together and say what to do on failure
DMARC requires the domain in theFrom: header to align with the one that passed SPF or DKIM, publishes what the receiver should do otherwise, and says where to send you reports.
p=quarantine: a failing message goes to spam.p=noneasks for nothing;p=rejecthas it refused.rua=: the address that receives daily aggregate reports (compressed XML) from the large providers. That mailbox must exist.
p=none, read the reports for a week or two, then move up.
5. Check end to end
The records can be right and the message still fail. Send a real message to a Gmail address, through your own Postfix so it gets signed:PASS on all three lines: SPF, DKIM and DMARC. The detail is in the Authentication-Results header:
Common pitfalls
Two SPF records. A domain must carry exactly one. With twoTXT records starting with v=spf1, the result is a permanent error and SPF fails for everyone. Merge them.
More than ten DNS lookups. Every include, a, mx, ptr, exists and redirect costs one query, and nested includes count too. Past ten, SPF returns a permanent error. Three or four third-party services are enough to get there.
-all or ~all. -all (fail) says nothing else sends; ~all (softfail) says it without insisting. Some receivers reject on an SPF fail before even evaluating DKIM, which penalises forwarded mail. If your domain’s messages are often forwarded, ~all with DKIM and DMARC in place is a common compromise.
DMARC at p=none forever. p=none is for watching. It meets the letter of the requirements but does nothing to stop someone spoofing your domain. Set yourself a date to move to quarantine.
Reverse DNS and HELO that disagree. A generic provider PTR, a PTR pointing to a name with no matching A record, or a myhostname left at localhost or the install-time name: any one of them is enough to make the receiver suspicious.
Forgotten IPv6. If your VPS has an IPv6 address, Postfix will happily use it to reach Gmail, and that address then needs its own PTR and must be covered by SPF (the a mechanism includes the domain’s AAAA records). Check with dig -x on the IPv6 address. Failing that, tell Postfix to prefer IPv4:
mail host with no SPF. Receivers may also check SPF against the name announced in HELO. A TXT record "v=spf1 a -all" on mail.example.com covers it.
Going further
- Point a domain to a VPS: the records the console writes for you.
- Reverse DNS (PTR): set and check your IP’s PTR.
- Email ports: the conditions for opening port 25.
- Enhanced security on a Linux VPS for the layer underneath.

