Advanced SRV & MX Routing
Controlling service discovery and mail delivery through DNS requires precise SRV and MX configuration, where priority and weight fields turn a flat list of records into a deterministic load-balancing and failover plan. While DNS Fundamentals & Advanced Record Configuration covers baseline record creation, production environments demand strict control over routing hierarchies, RFC compliance, and the per-provider quirks that silently break delivery. This guide covers the SRV weight/priority algorithm, MX fallback chains, provider-specific constraints across Cloudflare, AWS Route 53, and Azure/GCP/Fastly, the email-authentication records that ride alongside MX, and systematic diagnostic workflows.
Key implementation objectives:
- Understand SRV syntax: priority, weight, port, and target resolution order, plus the integer math clients use to split traffic.
- Implement MX fallback chains, Null MX, and equal-priority distribution with RFC 2181 / RFC 7505 compliance.
- Navigate Cloudflare, Route 53, Azure, GCP Cloud DNS, and Fastly provider constraints and APIs.
- Pair MX with SPF, DKIM, and DMARC so deliverability and routing are configured as one unit, and debug both with
dig,nslookup, and DNSViz.
SRV Record Architecture and Load-Balancing Logic
Clients parse SRV records to distribute traffic across multiple endpoints without an application-layer load balancer. The format is _service._proto.name TTL IN SRV priority weight port target. The priority field selects between routing tiers — lower values are tried first, exactly like MX. The weight field controls proportional load distribution within a single priority tier and has no meaning across tiers.
Weight distribution calculation (RFC 2782):
- Group all returned records by priority and select the lowest-numbered tier that still has reachable targets.
- Sum the weights of every record in that tier.
- For each record, its traffic fraction is
weight / sum. A client draws a random number in[0, sum]and walks the records in random order, subtracting weights, to pick a target. This is integer arithmetic, so very large weight sums can introduce rounding skew. - A weight of
0is valid: it means the record is selected only when no non-zero-weight peer in the same tier is available. Use it to mark a cold standby inside an otherwise active tier.
_sip._tcp.example.com. 300 IN SRV 10 60 5060 sip-primary-us.example.com.
_sip._tcp.example.com. 300 IN SRV 10 40 5060 sip-primary-eu.example.com.
_sip._tcp.example.com. 300 IN SRV 20 100 5060 sip-backup-us.example.com.
Priority 10 records receive all initial traffic: ~60% to the US node, ~40% to the EU node. Only if both priority-10 targets are unreachable does a compliant client advance to the priority-20 backup. The target must be a hostname that resolves to A/AAAA records — never 0 .-style nulls and never a CNAME chain you cannot reason about. For protocol-specific deployment, including the _sips._tcp and _xmpp-server._tcp service labels, see Configuring SRV records for SIP and XMPP services. For how SRV fits beside A, AAAA, TXT, and CNAME in a zone, see Understanding DNS Record Types.
The service label structure is rigid and easy to get wrong. The owner name is _service._proto, where both underscored labels are literal and case-insensitive: _sip, _xmpp-client, _xmpp-server, _minecraft, _caldav, _ldap, and so on are drawn from the IANA service-name registry, and _proto is almost always _tcp or _udp. A record published at the wrong label simply never gets queried — the client asks for _sip._tcp and your record at sip._tcp (missing underscore) is invisible. Because the query is for the SRV type at a structured name rather than a wildcard scan, there is no fallback: the label must match exactly what the client library constructs.
Weight values are deliberately coarse. Compliant clients perform a weighted random selection, not a strict ratio, so over a small number of connections the observed split will diverge from the published percentages; the ratio only converges over many independent selections. This matters when you reason about capacity. If you publish weight 1 and weight 99 expecting a 1:99 split, a single client that opens one connection sends 100% of its traffic to one target. SRV weighting is a statistical hint across a population of clients, not a per-connection guarantee, which is why it complements rather than replaces an application-layer load balancer or a health-checked anycast front end. When a node must be drained, set its weight to 0 and let connections bleed off as caches expire rather than yanking the record, which would cause a hard reset for clients mid-session.
MX Record Prioritization and Email Delivery Routing
Mail Transfer Agents evaluate MX records sequentially by preference value. The format is name TTL IN MX preference target, where lower preference is tried first. MX records have no weight field — records sharing a preference value are selected by round-robin, randomized, or alphabetical order entirely at the MTA’s discretion. To weight delivery you must split senders across distinct preference values or front your mail with a smart host that load-balances internally.
RFC 7505 defines the Null MX record (0 .) which explicitly signals that a domain accepts no inbound mail. Publishing it on parked domains, marketing subdomains, and send-only services prevents retry loops from other MTAs, returns an immediate permanent failure, and conserves bandwidth. Note the dot target and preference 0 are both mandatory for the null semantics.
A subtle but consequential rule governs domains with no MX record at all. Under RFC 5321, if a domain has no MX but does have an A or AAAA record, sending MTAs fall back to delivering mail directly to that address — the “implicit MX.” This is why publishing a Null MX is the only correct way to declare a domain unable to receive mail: simply omitting MX is not enough, because your web server’s A record will be treated as a mail target and silently accept (or bounce) connections on port 25. For an apex domain that serves a website and receives mail through a provider, you must publish explicit MX records so the implicit-MX fallback never engages and routes mail to your web host.
Equal-preference distribution deserves a deliberate design decision rather than an accident. If you list 10 mx1 and 10 mx2, an MTA randomizes between them, giving you crude active-active redundancy with roughly even load. If you list 10 mx1 and 20 mx2, mx2 only receives mail when mx1 refuses connections — active-passive. Most managed mail providers publish several equal-preference MX hosts behind their own load balancers, so you typically inherit their topology by using their include-style MX block verbatim rather than hand-tuning preferences.
example.com. 3600 IN MX 10 mx1.mailprovider.com.
example.com. 3600 IN MX 20 mx2.mailprovider.com.
no-mail.example.com. 3600 IN MX 0 .
dig +short MX example.com
Expected output:
10 mx1.mailprovider.com.
20 mx2.mailprovider.com.
An MX record that routes mail is only half of a deliverable domain. The receiving and sending sides both depend on three TXT-based authentication records that you should treat as part of the same change set. Align MX routing with SPF records to prevent spoofing so only your declared mail hosts can send as your domain, DKIM signing for your domain so recipients can cryptographically verify message integrity, and DMARC with monitoring and enforcement to bind SPF and DKIM together with a reporting and rejection policy. A correct MX with no SPF/DKIM/DMARC will deliver inbound mail but see your outbound mail quarantined. For TTL tuning on MX and these TXT records, see Mastering TTL Strategies.
example.com. 3600 IN TXT "v=spf1 include:_spf.mailprovider.com -all"
selector1._domainkey.example.com. 3600 IN TXT "v=DKIM1; k=rsa; p=MIGfMA0G...AB"
_dmarc.example.com. 3600 IN TXT "v=DMARC1; p=quarantine; rua=mailto:[email protected]; pct=100"
Platform-Specific Implementation
DNS providers enforce distinct validation rules, record-field encodings, and API behaviors. Ignoring these constraints causes silent routing failures where the zone “looks right” in the dashboard but resolves wrong on the wire.
Cloudflare
On Cloudflare, SRV and MX records are always DNS-only — the orange-cloud proxy toggle is disabled for these types automatically, because the proxy operates on HTTP(S) hostnames, not service records. The dashboard splits the SRV target into discrete priority, weight, port, and target inputs; via the API you supply a data object.
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"type": "SRV",
"name": "_sip._tcp.example.com",
"ttl": 300,
"data": { "priority": 10, "weight": 60, "port": 5060, "target": "sip-primary-us.example.com" }
}'
AWS Route 53
Route 53 stores the full SRV/MX rdata as a single space-separated string inside ResourceRecords. Alias routing targets only specific AWS resources, so for external mail hosts use standard ResourceRecords rather than an alias. Trailing dots are accepted and normalized.
resource "aws_route53_record" "mx_primary" {
zone_id = aws_route53_zone.primary.zone_id
name = "example.com"
type = "MX"
ttl = 3600
records = ["10 mx1.mailprovider.com.", "20 mx2.mailprovider.com."]
}
resource "aws_route53_record" "sip_srv" {
zone_id = aws_route53_zone.primary.zone_id
name = "_sip._tcp.example.com"
type = "SRV"
ttl = 300
records = ["10 60 5060 sip-primary-us.example.com.", "10 40 5060 sip-primary-eu.example.com."]
}
Azure DNS, GCP Cloud DNS, and Fastly
Azure DNS requires a trailing dot on FQDN targets; omitting it triggers a validation failure or, worse, appends the zone name to produce a wrong target. GCP Cloud DNS groups all records of a type at a name into a single rrdatas array and likewise wants fully qualified, dotted targets. Fastly does not host authoritative SRV/MX directly — point your MX at the mail provider and keep Fastly for the HTTP edge — but its managed DNS product follows the same space-separated rdata convention.
# Azure CLI: MX record with mandatory trailing dot
az network dns record-set mx add-record \
--resource-group dns-rg --zone-name example.com \
--record-set-name "@" --preference 10 --exchange mx1.mailprovider.com.
# GCP Cloud DNS: SRV rrset
gcloud dns record-sets create _sip._tcp.example.com. \
--zone=example-zone --type=SRV --ttl=300 \
--rrdatas="10 60 5060 sip-primary-us.example.com."
A cross-provider gotcha worth calling out: the same logical record can be rejected on one platform and accepted on another because of how each validates rdata. Cloudflare’s typed fields will reject a non-numeric weight outright, while Route 53’s string form happily stores "10 60 5060 target" even if you transpose port and weight — the error only surfaces when a client parses it. Treat the provider API as a syntax checker, never a semantic one, and keep a single canonical definition of each record in your IaC so the same intent is rendered identically everywhere. When migrating between providers, export with dig AXFR (where transfers are permitted) or the provider’s zone-export endpoint and diff the normalized rdata rather than eyeballing dashboards.
Platform comparison
| Provider | Mechanism | Wire behavior | Failover / Notes |
|---|---|---|---|
| Cloudflare | Discrete priority/weight/port/target JSON fields |
Standard RFC 2782/RFC 974 rdata emitted | Always DNS-only; proxy disabled for SRV/MX |
| AWS Route 53 | Space-separated rdata string in ResourceRecords |
Verbatim rdata; alias only for AWS resources | Use get-change to confirm propagation |
| Azure DNS | Typed record-set with explicit preference/target | Trailing dot mandatory on FQDN | Missing dot appends zone name silently |
| GCP Cloud DNS | rrdatas array per name+type |
Dotted FQDN required | All same-type records edited as one rrset |
| Fastly | Managed DNS, HTTP edge separate | Space-separated rdata | Not authoritative for mail; delegate MX to provider |
Numbered Configuration and Operational Procedure
- Inventory targets. Confirm each SRV/MX target resolves to direct A/AAAA records (
dig +short A mx1.mailprovider.com). Never point MX or SRV at a CNAME. - Stage TTLs. If a cutover is planned, lower SRV/MX TTL to 300 about 48 hours ahead so caches drain before the change, as covered in Mastering TTL Strategies.
- Write records in version control. Author the zone or Terraform/HCL changes so the prior state is recoverable.
- Apply via API/IaC, not the dashboard, so the change is auditable and reproducible across environments.
- Verify authoritative answer on the nameservers directly (see Troubleshooting Step 1) before trusting any recursive resolver.
- Validate the application path — open the SIP/XMPP port or send a test message and inspect MTA logs.
- Confirm email-auth alignment by running the SPF, DKIM, and DMARC checks alongside the MX change so a new mail host is authorized before it sends.
- Restore baseline TTL once the change is stable to reduce authoritative query load.
TTL, Caching, and Propagation Implications
SRV and MX answers are cached by recursive resolvers for the full TTL, so failover speed is bounded by the value you publish, not by how fast you edit the zone. A 300-second TTL gives roughly five-minute worst-case failover at the cost of higher authoritative query volume; a 3600-second TTL is cache-efficient but delays emergency rerouting. The practical pattern is a low baseline (300–900s) for actively failover-managed services and a higher value (3600s) for stable mail routing that rarely changes.
Two propagation subtleties bite teams repeatedly. First, MTAs and SIP clients frequently apply their own minimum caching or negative-cache (SOA minimum) windows, so even a 60-second TTL may not fail over instantly. A misconfigured high SOA minimum means a negative answer — for example, a temporarily missing MX during a botched edit — gets cached far longer than your positive-record TTL would suggest, extending an outage well past the change window. Second, the target hostname’s A/AAAA TTL is a second cache layer — a short MX TTL on a target whose A record has a 24-hour TTL still pins you to the old IP. Tune both, and prefer changing the MX preference list over changing a target’s IP when you need fast cutover, because the former is under your TTL control while the latter may sit behind a provider’s long A-record TTL.
Mail adds a third consideration: deferred retries. When a sending MTA cannot reach your highest-preference host, it does not abandon the message; it queues and retries with backoff for hours or days. This forgiving behavior masks intermittent MX problems, so a record that is wrong only 20% of the time can pass casual testing while quietly delaying a fraction of mail. Always test against multiple public resolvers and confirm every MX target answers on port 25, rather than trusting a single successful send. For diagnosing why a change has not taken effect everywhere, the resolver-comparison techniques in the propagation guides under DNS Fundamentals & Advanced Record Configuration apply directly.
Troubleshooting, Diagnostics, and Rollback
Always verify authoritative responses before blaming client-side caches.
Step 1: Authoritative resolution trace
dig +trace SRV _sip._tcp.example.com
dig @ns1.example.com MX example.com +norecurse
Expected output (abbreviated):
_sip._tcp.example.com. 300 IN SRV 10 60 5060 sip-primary-us.example.com.
Step 2: Port and TLS validation
# Test TCP reachability of the SRV target/port
nc -zv sip-primary-us.example.com 5060
# Test STARTTLS handshake on the SIP service
openssl s_client -connect sip-primary-us.example.com:5060 -starttls sip
# Confirm the MX target speaks SMTP
nc -zv mx1.mailprovider.com 25
Step 3: MTA and auth log analysis
Check /var/log/mail.log (or your provider’s delivery dashboard) for NXDOMAIN, SERVFAIL, connection timed out, or Recipient address rejected errors. If inbound works but outbound is quarantined, the fault is almost always SPF/DKIM/DMARC, not MX — re-check SPF records and DMARC aggregate reports. Stale cache mismatches usually clear after the TTL expires.
Step 4: Confirm the answer is what you authored, byte for byte
# Compare authoritative MX rdata against your expected set
dig +short MX example.com | sort
# Validate that the chosen SRV target actually has an address record
dig +short A sip-primary-us.example.com
# Catch an accidental CNAME on a mail/SRV target
dig CNAME mx1.mailprovider.com +short
A non-empty answer to that last query is a red flag: the target is a CNAME and must be replaced with a hostname that has direct A/AAAA records. For email problems specifically, validate the auth stack in the same pass — a TXT lookup for _dmarc.example.com and the DKIM selector confirms whether a delivery rejection is a routing fault or an authentication fault.
Rollback. Maintain version-controlled zone files or Terraform state. If routing fails, revert to the previous commit and re-apply. On Route 53, watch aws route53 get-change --id <ChangeId> until INSYNC to confirm the rollback has propagated to all Route 53 edge locations before declaring recovery. Because resolvers may still hold the broken answer for the remainder of its TTL, a rollback is only “complete” once that TTL window has elapsed — which is the operational argument for keeping failover-critical SRV/MX records on a short baseline TTL in the first place.
Edge Cases and Gotchas
- MX or SRV target is a CNAME — RFC 2181 violation; many MTAs reject delivery or enter resolution loops. Always resolve targets to direct A/AAAA records.
- Legacy resolvers ignore SRV weights — produces uneven distribution or bypass of secondary endpoints. Test with modern clients and add application-layer health checks rather than trusting DNS alone.
- Emergency TTL reduction at cutover time — causes resolver cache thrashing and a query spike. Pre-stage reductions 48 hours prior, then revert.
- Forgetting the Null MX dot —
0 .versus0 mx.example.com.are opposite intents; a typo here can blackhole or open a domain to mail. - DKIM TXT split across strings — long RSA public keys exceed 255 octets and must be published as multiple quoted chunks; an unsplit key fails validation. See DKIM signing for your domain.
- SPF lookup limit — more than 10 DNS-resolving mechanisms returns
permerrorand breaks alignment regardless of a correct MX.
Frequently Asked Questions
Can I use CNAME records for MX or SRV targets? No. RFC 2181 prohibits CNAMEs at MX and SRV targets. Use A/AAAA records directly, or provider-specific alias mechanisms that resolve to IP addresses at the authoritative level.
How do SRV priority and weight interact during failover? Clients attempt all targets at the lowest active priority first, distributing load by weight within that tier. Only if every target in that tier is unreachable do they advance to the next priority value. Weight has no effect across priority tiers.
Why do some MTAs ignore my MX weight or priority settings? MX records have no weight field, so load distribution between equal-preference MX records is at the MTA’s discretion — typically round-robin or alphabetical. Only the preference value governs ordering; to weight delivery you must use distinct preference values.
What is the recommended TTL for SRV and MX records in production? 300–3600 seconds. Low TTLs (300s) enable faster failover but increase authoritative query load; high TTLs (3600s) improve cache efficiency but delay emergency routing changes. Remember the target’s A/AAAA TTL is a second cache layer to tune.