Last spring, a client expanding into Southeast Asia called us at 2 AM because users in Singapore were consistently hitting our Frankfurt PoP. Their standard GeoDNS was doing exactly what it was told — mapping a country code to a region — but it had no concept that our new Jakarta edge had half the capacity of Frankfurt, and it was dragging latency from 28ms to 210ms. We’d built the infrastructure, but the traffic control wasn’t fine-tuned enough to use it. That’s the gap geoproximity routing fills, and if you don’t configure it right, you’re just paying for edge servers your users will never touch.
Why Standard Geographic DNS Can't Handle Uneven Edge Topologies
For years, most of us got by with simple geographic DNS. You carve the world into regions, you point DE to your Frankfurt PoP, SG to Singapore, US-East to Ashburn. It works fine when your footprint is symmetrical and every PoP has identical capacity. But modern CDN topologies aren’t symmetrical. You’ve got a 100 Gbps super-PoP in London, a 20 Gbps cache in Warsaw, and maybe a rented third-party edge in Johannesburg that you don’t fully trust for high-value traffic. Simple geographic routing doesn’t understand capacity, network distance, or congestion — it just draws hard borders on a map and hopes for the best.
Geoproximity routing changes that logic completely. Instead of saying “if user is in Poland, send to Warsaw,” it says “calculate the geographic distance between the user’s resolver location and each of your PoPs, then factor in a bias you set per PoP to shrink or expand its attraction radius.” I learned this the hard way back in 2019 when we relied on pure GeoIP for a video streaming launch. Users on the French-German border were flipping between Paris and Frankfurt on every DNS TTL refresh because they sat right on the geographic fence. Their video segments were bouncing between cold caches. Once we switched to a proximity model with overlapping coverage areas, that flapping stopped because the bias kept them sticky to one PoP unless they physically moved a significant distance. That stability alone cut rebuffering by about 18%.
You’ll also need to understand how this interacts with what resolvers actually send you. If you’re still making routing decisions based solely on the resolver’s IP, you’re routing based on where Google DNS or Cloudflare sits, not the user. That’s why pairing geoproximity with EDNS Client Subnet for CDN Geolocation is non-negotiable now. The ECS prefix gives you the /24 of the actual client, and without it, your beautifully tuned bias values are just applied to the wrong coordinates. And if you’re building this on top of a delegated setup, your DNS NS Delegation for CDN architecture has to forward that ECS data intact — some older delegation configs strip it.
# Example: AWS Route 53 Geoproximity with bias (using CLI)
# Bias expands (+) or shrinks (-) the logical service area
# Lower bias = smaller attraction radius, higher bias = larger
aws route53 change-resource-record-sets \
--hosted-zone-id Z123456789ABC \
--change-batch '{
"Changes": [{
"Action": "CREATE",
"ResourceRecordSet": {
"Name": "cdn.example.com",
"Type": "A",
"SetIdentifier": "fra-edge-bias-minus-20",
"GeoProximityLocation": {
"AWSRegion": "eu-central-1",
"Bias": -20,
"Coordinates": {
"Latitude": 50.1109,
"Longitude": 8.6821
}
},
"TTL": 60,
"ResourceRecords": [{ "Value": "203.0.113.45" }]
}
},
{
"Action": "CREATE",
"ResourceRecordSet": {
"Name": "cdn.example.com",
"Type": "A",
"SetIdentifier": "waw-edge-bias-plus-30",
"GeoProximityLocation": {
"Bias": 30,
"Coordinates": {
"Latitude": 52.2297,
"Longitude": 21.0122
}
},
"TTL": 60,
"ResourceRecords": [{ "Value": "203.0.113.78" }]
}
}]
}'
# Result: Warsaw's effective radius artificially expanded to pull
# Polish and eastern German users away from saturated Frankfurt.
Tuning Bias to Shift Traffic Without Physically Moving Edges
The bias value is the real control plane here, and honestly, most teams I’ve audited either leave it at zero or set it once and forget it. That’s a mistake. Think of bias as a virtual weight. A bias of 0 means your PoP’s attraction is its pure geographic distance. A positive bias of +25 tells the routing engine “pretend this PoP is 25 units closer than it really is,” so it pulls traffic from farther away. A negative bias does the opposite. You’re not moving the data center; you’re warping the map. We use this constantly for capacity management and maintenance windows without touching BGP or anycast.
Here’s a practical scenario we run every quarter. Our Madrid PoP handles Iberia beautifully, but during La Liga finals, we know origin shield traffic will spike 4x. Two days before the event, we’ll dial Madrid’s bias from 0 down to -40 and bump our Marseille PoP to +25. That gently bleeds about 30% of southern French and northern Spanish users over to Marseille, which has spare CPU and a warmer cache for that content profile. No DNS propagation delay beyond TTL, no hard cutover, and we can roll it back in 60 seconds. You can’t do that with traditional geographic records — you’d have to rewrite region polygons, and you’d still get that hard border where users on one side get a completely different experience. This approach also avoids the pitfalls of DNS CNAME Flattening tricks where you’re trying to steer apex traffic with workarounds instead of proper proximity logic.
When you’re scripting this, don’t tune bias by eye. We tie bias adjustments to real-time telemetry: p95 TTFB per PoP, CPU load, and cache hit ratio. A simple control loop that bumps bias by ±5 every 5 minutes based on load is far safer than a human making a +/- 50 jump at once. I’ve seen a single +50 change on a large US region accidentally pull 60% of continent traffic to a single PoP and tip it over. Small, incremental changes let anycast and the CDN’s own load balancing absorb the shift. For reference on how clients will actually cache and revalidate those shifted responses, the HTTP Working Group Specifications (https://httpwg.org/specs/) are still the canonical source on cache-control behavior, and web.dev Articles (https://web.dev/articles/) have excellent data on how geographic latency affects Core Web Vitals.
// Simple bias controller pseudocode - runs via Lambda / Cloud Function
// Adjusts bias based on PoP health, not just geography
function calculateNewBias(currentBias, metrics) {
const { cpuLoad, p95_ttfb_ms, cacheHitRatio } = metrics;
let delta = 0;
if (cpuLoad > 0.75 || p95_ttfb_ms > 120) {
delta = -10; // Shrink attraction, shed load
} else if (cpuLoad < 0.45 && cacheHitRatio > 0.88) {
delta = 5; // We have headroom, attract more users
}
// Clamp bias to provider limits (e.g., Route 53: -99 to +99)
let newBias = Math.max(-99, Math.min(99, currentBias + delta));
console.log(`PoP ${metrics.pop}: CPU ${cpuLoad}, TTFB ${p95_ttfb_ms}ms -> Bias ${currentBias} -> ${newBias}`);
return newBias;
}
// Example trigger: CloudWatch alarm -> recalculate -> Route53 API call
The Performance Trade-offs: When Proximity Hurts Your Hit Ratio
There’s a temptation to chase the lowest possible network distance for every user. Don’t. The closest PoP isn’t always the fastest experience if its cache is cold. We ran an A/B test last year for a large e-commerce platform where we forced ultra-precise geoproximity with tight radii around each edge. Latency to first byte from the edge dropped by 11ms on average, but overall page load time went *up*. Why? We fragmented the cache. Instead of 3 PoPs serving 85% of the catalog from RAM, we had 9 PoPs each with a 55% hit ratio, and we hammered the origin shield in Virginia. Origin requests tripled, and origin latency wiped out any edge proximity gains.
You’ve got to balance proximity routing against cache efficiency. For highly cacheable, static assets — images, JS bundles, fonts — you can afford to be more aggressive with proximity because even a cold PoP will fill quickly and serve everyone nearby. For highly dynamic or long-tail content, you’re often better off with a larger bias toward your super-PoPs that have bigger SSD tiers and better hit ratios, even if it adds 20-30ms of network distance. We now classify content by cacheability and use different geoproximity profiles: static.example.com gets tight, low-bias routing, while api.example.com gets routed with a strong positive bias toward our three core PoPs.
These are the actual numbers from that e-commerce test over 72 hours with 400M requests. The “Balanced” profile is what we kept in production. It’s not the lowest latency on paper, but it gave us the best end-to-end performance because it respected both network and cache proximity.
| Routing Profile | Avg Edge TTFB (p50) | Cache Hit Ratio | Origin RPS (avg) | p95 Page Load |
|---|---|---|---|---|
| Strict Proximity (9 PoPs, bias 0) | 42 ms | 61.2% | 1,850 rps | 1,940 ms |
| Balanced (5 PoPs, bias tuned ±20) | 54 ms | 88.7% | 620 rps | 1,620 ms |
| Super-PoP Focus (3 PoPs, bias +40) | 71 ms | 94.1% | 310 rps | 1,710 ms |
| Legacy GeoDNS (Region Blocks) | 68 ms | 79.4% | 980 rps | 1,880 ms |
Putting Bias Into Practice: Configuring Geoproximity Routing
Theory's great, but bias doesn't tune itself. In production, you'll be setting this up in Route 53, NS1, Dyn, or your own PowerDNS/Authority stack with GeoIP backends. The concept's the same everywhere: you define a coordinate for each edge or regional record set, then apply a positive or negative bias to expand or shrink its catchment area. Route 53 gives you a range of -99 to +99, NS1 uses a similar integer weight, but in practice you'll rarely go beyond +/- 25. Anything higher and you're just overriding geography entirely — at that point, use weighted or failover routing instead.
Don't guess your starting bias. Start at 0 for all regions, collect a week of RUM data and cache telemetry, then adjust in increments of 5-10. For example, if your Madrid PoP is consistently hitting 85%+ CPU while Paris sits at 40% and your p95 TTFB from southern France is creeping up to 110ms, bump Madrid to -10 and Paris to +10. That'll shift the invisible bisector about 150-200km south without touching any hardware. We've seen that single tweak rebalance origin offload by 18% and drop Paris's cache hit ratio variance from 12 points to under 4. Give each change 48-72 hours to settle — remember, you've got resolver caches and TTLs to contend with. If you're still on a 300s TTL for your CDN hostname, drop it to 60s or even 30s during tuning so you can see the effect within minutes, not hours.
One gotcha you can't ignore is EDNS Client Subnet (ECS). Without it, you're routing based on the resolver's location, not the user's. With Google Public DNS and Cloudflare's 1.1.1.1 handling 35-40% of eyeball queries in EU and NA, that matters a lot. If your authoritative DNS doesn't honor ECS, your beautiful bias map will look right in the console but send a user in Lyon to Madrid because their resolver egressed in Amsterdam. Always verify your provider supports ECS and that it's enabled. And if you're also rolling out DNSSEC Signing for CDN Infrastructure: Authenticated DNS Without the Performance Hit, test your signed geoproximity responses with validating resolvers early — we've seen signature bloat push UDP responses over 1232 bytes and force TCP fallback, adding 20-30ms before the user even hits the edge.
# Terraform example: Shifting traffic from an overloaded Madrid PoP to Paris
# Bias is unitless but roughly 1 unit = ~15-20km of catchment shift
resource "aws_route53_record" "cdn_paris" {
zone_id = aws_route53_zone.cdn.zone_id
name = "cdn.example.com"
type = "A"
ttl = 60
geoproximity_routing {
bias = 10 # Expand Paris coverage southward
coordinates {
latitude = 48.8566
longitude = 2.3522
}
}
records = ["192.0.2.10"]
set_identifier = "paris-edge"
}
resource "aws_route53_record" "cdn_madrid" {
zone_id = aws_route53_zone.cdn.zone_id
name = "cdn.example.com"
type = "A"
ttl = 60
geoproximity_routing {
bias = -10 # Shrink Madrid, shed northern Spain / southern France
coordinates {
latitude = 40.4168
longitude = -3.7038
}
}
records = ["192.0.2.20"]
set_identifier = "madrid-edge"
}
Advanced Patterns and When Proximity Routing Goes Sideways
Once you've nailed basic biasing, you can get clever. We use overlapping proxi-regions for capacity-aware draining all the time. Say you need to take Frankfurt offline for a kernel upgrade — instead of a hard failover that slams Amsterdam and causes a 22% hit ratio cliff, you gradually dial Frankfurt's bias from 0 to -50 over 10 minutes. Traffic bleeds off smoothly and your origin doesn't see a thundering herd of cache misses. Another pattern is using bias as a poor man's load balancer for heterogeneous hardware: that new PoP in Warsaw with NVMe and 100G NICs? Give it a +15 bias so it serves 30% more geography than its physical location warrants, letting you sweat the new gear while older PoPs in Prague cool down. It’s cheaper than anycast re-engineering.
Troubleshooting is where most teams get stuck, because DNS lies to you. If you see traffic drift that doesn't match your bias map, start with three checks. First, run dig +subnet=203.0.113.0/24 from multiple vantage points (RIPE Atlas is invaluable here) to see if ECS is actually being honored — you'd be surprised how often a provider silently strips it. Second, check your resolver concentration. If 60% of your queries in Brazil come from just three /24s belonging to Google and Cloudflare, you're not doing geoproximity, you're doing resolver-proximity. In that case, bias changes will have muted effect until you shorten TTLs or negotiate ECS support. Third, correlate with your CDN's real-user latency, not just DNS lookup time. We've chased a "DNS misroute" for two days only to find the alternate PoP had a congested transit link adding 45ms — proximity was correct, performance wasn't.
And don't treat DNS routing in a vacuum. Your geoproximity layer is still your first line of defense. If you're seeing a spike in bogus queries from a specific region, that's often not a tuning problem but an abuse problem. Pair your routing telemetry with your security pipeline — we've tied bias anomalies directly to DNS Sinkholing at CDN Scale: Redirecting Malicious Traffic for Analysis to quarantine bot traffic to a scrubbing PoP with a +30 bias trap, and we harden the whole authoritative tier using the same principles in DNS Amplification Attack Mitigation: Protecting CDN DNS Infrastructure. If your authoritative DNS goes down, your perfect bias map doesn't matter. Monitor query volume per PoP, response size, and TCP fallback rate alongside your hit ratio — they'll tell you if you're being gamed.
Frequently Asked Questions
What's the real difference between geoproximity and latency-based routing?
Geoproximity routes based on geographic distance plus your bias — it's deterministic and you control the map. Latency-based routing uses active health checks and real-time network measurements to send users to the lowest-RTT PoP. Geoproximity is predictable and great for capacity planning, but it can't see a bad peering link. Latency-based is more accurate for performance (often 15-25ms better p50) but it's a black box and can flap. Most mature CDNs use geoproximity as the baseline and layer latency checks on top for failover.
How does bias interact with EDNS Client Subnet (ECS)?
Bias is only as good as the location data you feed it. Without ECS, your authoritative server sees the resolver's IP (often hundreds of kilometers away) and applies bias to the wrong coordinate. With ECS, it sees a /24 or /48 of the actual client and biases correctly. If your provider supports ECS, enable it and keep your geoproximity TTLs at 60s or less — ECS-scoped answers shouldn't be cached long. Without ECS, don't bother fine-tuning bias below 10 units; the resolver noise will drown it out.
Will adjusting bias hurt my cache hit ratio?
It can, if you overdo it. Every time you shift a user population to a new PoP, that PoP needs to warm its cache — expect a 5-8% hit ratio dip for 2-4 hours after a +/- 10 bias change, and up to 12-15% if you're moving more than 20% of a region's traffic. That's why we recommend small, incremental changes and doing them during off-peak. In steady state, a well-tuned bias actually improves hit ratio by 3-5% because you're preventing overload and eviction churn on hot PoPs.
Can I use geoproximity routing with DNSSEC enabled?
Absolutely, but watch your response size. DNSSEC adds RRSIG records that can push your geoproximity answers from ~120 bytes to over 800 bytes. Stay under the 1232-byte DNS flag day limit to avoid TCP fallback and fragmentation. Use ECDSA P-256 instead of RSA-2048 if you can — it cuts signature size by ~60% — and test with dig +dnssec +bufsize=1232 from validating resolvers. The routing logic itself isn't affected; it's purely a transport consideration.