All posts

PTR records and why VPS mail lands in spam

Nine of ten public MX addresses had a matching PTR. Outlook's five MX addresses were NXDOMAIN. Gmail's block for that miss is 550 5.7.25.

Written by NodeRings Inc.

NodeRings Inc. writes these notes. The company runs the marketplace and the agent that stays on the provider’s side of the hypervisor.

I took the lowest-preference MX of ten mail domains, asked for its first A record, then ran dig -x on that address and dig A on every name that came back. Nine of the ten addresses showed up in the A record of every PTR name returned for them. The tenth was outlook.com. Five addresses for outlook-com.olc.protection.outlook.com all came back NXDOMAIN.

Dig was 9.18.39, through the resolver on this host. These are inbound MX addresses, one site of each name, queried from here. They are the machines that receive mail for those domains. Gmail's check is the address that connects outbound. The two lookups are the same either way.

Which VPS providers still open port 25 is the path. Once the packets can leave, this record is the next thing a receiver asks. The three TXT records are SPF, DKIM and DMARC.

DomainMXAddressPTR nameForward lookupTTL
google.comsmtp.google.com172.253.124.26ys-in-f26.1e100.netthat address3599
outlook.comoutlook-com.olc.protection.outlook.com52.101.194.162NXDOMAIN
yahoo.commta5.am0.yahoodns.net67.195.204.74mtaproxy5.free.mail.vip.bf1.yahoo.comthat address599
icloud.commx01.mail.icloud.com17.57.154.33mx02.mail.icloud.com and mx01.mail.icloud.comboth sets include it299
proton.memail.protonmail.ch176.119.200.128mail.protonmail.chthree addresses, including it1199
fastmail.comin1-smtp.messagingengine.com103.168.172.217phl-mx-02.messagingengine.comthat address3599
zoho.comsmtpin.zoho.com136.143.191.23smtpin.zoho.com, smtpin2, smtpin3each A is that address7199
mailbox.orgmx1.mailbox.org80.241.60.212mx1.mailbox.orgthat address299
posteo.demx01.posteo.de185.67.36.62mx01.posteo.dethat address and 185.67.36.613599
hey.comhome-mx.app.hey.com64.202.125.67home-mx.app.hey.comthat address299

Google, Yahoo, and Fastmail pass with a PTR name that is a different string from the MX hostname. The check is the address, then the name, then the address again. icloud published two PTR names, zoho three, and every one of those names still had the address in its A set. Proton's name has three A records, 185.205.70.128, 185.70.42.128, and 176.119.200.128. The one I started from is in the set, so the pair holds. Extra addresses beside it are a pass.

What 550 5.7.25 is

Gmail's sender guidelines, in the IP addresses section, say the sending address must match the address of the hostname in the PTR record. The public address needs a PTR that resolves to a hostname, and that hostname needs an A record, or an AAAA record for IPv6, that resolves to the same address. They call the first step a reverse lookup and the second a forward lookup.

The SMTP error table uses three codes for that miss. 550 5.7.25 is a block. 421 4.7.0 and 451 4.7.23 are temporary rate limits. The 550 cell says the sending address has no PTR, or the forwarding DNS entry leaves the sending address out. Their word in that cell is forwarding. The guidelines call it a forward lookup. The figure is that table, cut down to the PTR sentence.

Google's SMTP error table. 550 5.7.25 is a block. 421 4.7.0 and 451 4.7.23 are temporary rate limits for a missing PTR or a forward lookup that leaves the sending address out.
Shortened from Google's SMTP error table. 550 5.7.25 is the block. The 421 and the 451 are rate limits for the same miss. No mail was sent from this host.

5.7.26, 5.7.27, 5.7.30, and 5.7.40 on that same table are SPF, DKIM, and DMARC. Those are the other post.

Outlook's policies require valid reverse DNS on email servers, and they may refuse connections from dynamic IP space. Yahoo's sender page asks for a forward and a reverse record on every sending IP, and for a PTR that reflects the domain and reads as a static mail server.

Who publishes the PTR

The reverse name lives under in-addr.arpa for IPv4 and ip6.arpa for IPv6. I traced 172.253.124.26, the Google MX address from the table. The root handed back in-addr.arpa at TTL 172800, from b.root-servers.net, in 278 ms. in-addr.arpa handed 172.in-addr.arpa to ARIN at TTL 86400, from a.in-addr-servers.arpa, in 106 ms. ARIN handed 253.172.in-addr.arpa to Google's nameservers at TTL 86400, from x.arin.net, in 115 ms. ns2.google.com answered the PTR, ys-in-f26.1e100.net, at TTL 3600, in 44 ms.

The zone for the mail domain is a different tree. The registrar where you bought the domain delegates the forward zone. The reverse zone is delegated by the RIR to whoever holds the address block. On a VPS that holder is the provider, or the provider's upstream. A network that announces its own prefix gets the reverse zone delegated to the nameservers it registers, and then the PTR is a record it publishes itself.

dig +trace of 172.253.124.26. in-addr.arpa at TTL 172800, 172.in-addr.arpa at 86400 from ARIN, 253.172.in-addr.arpa at 86400 to Google, PTR ys-in-f26.1e100.net at 3600 from ns2.google.com.
Two nameservers from each delegation in one dig +trace. DS and signatures left out. The PTR line is the answer from ns2.google.com, TTL 3600. The recursive answer for the same address was 3599.

RFC 1912 section 2.1 says the PTR and the A should match, that a multi-homed host needs a PTR for every address, and that the PTR should point at an A record. A CNAME is the wrong target.

The five Outlook MX addresses were 52.101.194.162, 52.101.73.21, 52.101.60.70, 52.101.42.163, and 52.101.137.3. The first showed up on the opening query. The other four showed up on a second query for the same MX, which rotates. Each PTR query was NXDOMAIN. The authority section was an Azure DNS SOA, ns1-37.azure-dns.com for 52.101.194.162, and the MINIMUM field was 300 on each. The TTL on the SOA record in those answers was 300, 240, 42, 30, and 300. Those are five different reverse zones. The 42 and the 30 are remainders already stored in those zones. The 300 is the full negative answer. A later query for 52.101.194.162 came back at 240, the same zone with a remainder.

That is the receiving side. Microsoft's page on external recipients says Microsoft 365 sending addresses have forward-confirmed reverse DNS, and that the HELO string is a different name from each address's PTR. Their example is 157.56.110.65 to mail-bn1on0065.outbound.protection.outlook.com and back. I asked. The PTR was that name, TTL 3599, and the A of the name was 157.56.110.65.

The two lookups

dig -x 80.241.60.212
dig A mx1.mailbox.org

The first prints the PTR name. The second has to include the address you started with. For mailbox.org both sides were mx1.mailbox.org and 80.241.60.212, TTL 299 on the PTR answer. For the Google address the PTR name was ys-in-f26.1e100.net, and the A of that name was 172.253.124.26.

A single VPS is easier with one name. Put that name on the A record, ask the provider to put it on the PTR, and set Postfix myhostname to it. smtp_helo_name defaults to $myhostname, which is the hostname Postfix sends in EHLO. That default is in the postconf manual. Run this on the mail VM:

postconf myhostname smtp_helo_name

Gmail's published check is the forward lookup of the PTR name. Microsoft's page describes receivers that want the HELO string itself to be a name whose PTR matches. One hostname on one address satisfies both. A big shared pool, which is what that Microsoft page is about, keeps FCrDNS per address and uses a different string in EHLO.

I also ran the miss on purpose, on addresses from the documentation range in RFC 5737. NSD 4.8.0 listened on 127.0.0.1 port 5454. mail.ptrlab.invalid had A 192.0.2.10. The reverse zone 2.0.192.in-addr.arpa had 10 PTR mail.ptrlab.invalid and 20 PTR the same name. The first pair meets. The second PTR names a host whose A record is 192.0.2.10, so 192.0.2.20 is outside the set. That is the second clause of 550 5.7.25. The server was stopped after the queries.

Forward zone mail.ptrlab.invalid A 192.0.2.10, and reverse zone PTRs for 192.0.2.10 and 192.0.2.20 both naming that host. The .10 pair meets. The .20 address is outside the A set.
The forward zone is the one you edit. The reverse zone is the one the address holder edits. In this lab both were NSD, so the miss is visible on one machine.
Dig against NSD shows 192.0.2.10 PTR mail.ptrlab.invalid and that name A 192.0.2.10, both TTL 300 with the AA flag. 192.0.2.20 has the same PTR. Below, the IPv6 PTR name for mailbox.org is 32 nibbles under ip6.arpa and answers mx1.mailbox.org with 225 seconds left.
Top: the matching pair and the miss, both authoritative, TTL 300. Below: a later lookup of mailbox.org's IPv6 address. The question name is the 32-nibble form. 225 seconds were left. The first answer's TTL field was 299.

The IPv6 name is 32 nibbles

mailbox.org's MX also has 2001:67c:2050:104:0:1:25:1. The PTR was mx1.mailbox.org, the same name as the IPv4 side, and the AAAA of that name was the same address, TTL 299. Expanding the address to 32 hex digits and reversing them one by one produced the question name dig printed:

1.0.0.0.5.2.0.0.1.0.0.0.0.0.0.0.4.0.1.0.0.5.0.2.c.7.6.0.1.0.0.2.ip6.arpa
import ipaddress
ip = ipaddress.IPv6Address("2001:67c:2050:104:0:1:25:1")
exploded = ip.exploded.replace(":", "")
print(".".join(reversed(exploded)) + ".ip6.arpa")

Google's MX was a different name on each family. 172.253.124.26 was ys-in-f26.1e100.net. 2a00:1450:4001:c21::1a was lcfrai-in-f26.1e100.net. Both forward-confirmed, both at TTL 3599. A v4 PTR covers the v4 connection. The v6 connection needs its own.

Gmail's guidelines say an IPv6 authorization error can mean the PTR for that send is an IPv4 name. The example code in that section is 550 5.7.1, for IPv6 sending guidelines on PTR records and authentication.

Where the provider puts the field

ProviderWhere you set itWhat they require
Hetzner CloudServer, Networking, beside the addressThe name's A or AAAA has to contain the address. Their docs show a default shaped like static.1.113.0.203.clients.your-server.de.
DigitalOceanThe Droplet nameA valid FQDN such as example.com becomes the PTR. ubuntu-s-4vcpu-8gb-fra1-01 and my-droplet stay hostnames. The Droplet name is the PTR control in that panel. The IPv6 PTR covers the first address on the Droplet.
LinodeNetwork tab, Edit RDNSThey look the A record up before saving. An empty field resets to the address with dashes, under ip.linodeusercontent.com.
OVHcloudNetwork, Public IP AddressesThey look up the A or AAAA before saving. Their page says a fresh forward record can take up to 24 hours before that check sees it.

Those paths are from each provider's docs: Hetzner, DigitalOcean, Linode, OVHcloud. Hetzner's cloud docs also say the IPv6 prefix is stored and you enter the interface identifier. OVH's 24 hours is their check of the forward name. The name they accept is lowercase, ends with a dot, and stays within 63 characters.

On a NodeRings network the DNS servers field is up to four resolvers, written into the guest with cloud-init. That field is who the VM asks. It is in the IPAM guide. The A record is a row in the customer's zone, on ans1.nrings.io and ans2.nrings.io, which the DNS guide covers. The PTR for the address belongs to whoever holds the reverse zone for the prefix.

Yahoo's request for a name that reflects the domain is the reason to replace the provider default before you send. Hetzner's published shape and Linode's published shape are the dynamic-looking names that page is talking about. static.1.113.0.203.clients.your-server.de is Hetzner's example, the documentation address 203.0.113.1 reversed. Linode's empty-field default is the address with dashes, under ip.linodeusercontent.com.

How long the old PTR stays

A resolver that already stored the PTR keeps it until the TTL it saved runs out. The same wait, measured on A records, is There is no DNS propagation. Of the nine matching PTR answers, three were 299 seconds, one was 599, one was 1199, three were 3599, and zoho.com was 7199. For the Google address the authoritative TTL on the trace was 3600, and the recursive answer was 3599.

The chart is those nine TTL fields. The five Outlook addresses returned NXDOMAIN.

Bar chart of PTR TTL in the recursive answer. hey.com, icloud.com, and mailbox.org at 299 seconds, yahoo.com at 599, proton.me at 1199, fastmail.com, google.com, and posteo.de at 3599, zoho.com at 7199.
Nine MX addresses that forward-confirmed, TTL field from this host's resolver. google.com was 3600 at ns2.google.com. The bar is the recursive 3599.

The delegation above the PTR is a longer timer, and a different record. On that Google trace, in-addr.arpa was 172800 and the two delegations under it were 86400. Changing who hosts the reverse zone waits on those. Changing the PTR value waits on the PTR's own TTL.

A missing PTR is cached too. RFC 2308 sets the negative cache to the lesser of the SOA TTL and the MINIMUM. On the Outlook zones both were 300, and one of them was already down to 240 on a second look. Adding the record finds a resolver that is still serving the NXDOMAIN it stored.

Commands

dig -x 80.241.60.212
dig A mx1.mailbox.org
dig +trace -x 172.253.124.26
dig -x 2001:67c:2050:104:0:1:25:1
postconf myhostname smtp_helo_name

dig -x on an IPv6 address prints the 32-nibble name for you. +trace walks the reverse delegation and ends at the server that holds the PTR. postconf prints the EHLO name the mail VM will use.

FAQ

Can one address have two PTR records?

icloud published two for 17.57.154.33, and zoho published three for 136.143.191.23. Each of those names had the address in its A set. DNS will return more than one PTR. Which name a receiver keeps is up to that receiver.

Does the PTR move the website?

The browser asks for the A or AAAA of the name you typed. The PTR is the name a reverse lookup returns for the address. Editing the PTR leaves the A record where it was.

How long before Gmail sees a new PTR?

Until the TTL a resolver already stored hits 0. In this set that field was 299 seconds on the short end and 7199 on the long end. A resolver that cached the absence holds the NXDOMAIN for the negative TTL, 300 seconds on the Outlook zones above. OVH's page says their own check of a new forward name can take up to 24 hours. That 24 hours is their lookup.

Should the EHLO name equal the PTR?

Gmail's guidelines ask for the forward lookup of the PTR name. Microsoft's page says their own HELO string and their PTR names differ, and that each sending address still forward-confirms. A receiver that wants the HELO string to be the PTR name will refuse that pattern. On one VPS, set myhostname to the PTR name and both checks use the same host.

For a record you can edit, plan against the TTL already cached. For this one, the edit is in the provider's reverse zone, and the nine matching PTRs were cached for 299 to 7199 seconds. The Google delegation above that PTR was 86400 at ARIN and 172800 at in-addr.arpa.