GeoDNS and DNS Health Checks: Route by Region and Fail Over Automatically
CubePath DNS can do two things a plain DNS zone can't: answer different IPs to visitors in different regions (GeoDNS), and automatically pull a dead server out of rotation until it comes back (Health Checks). Together they let you run the same hostname on several servers around the world and keep it pointing only at the ones that are close and alive.
It's the standard setup for multi-region apps, latency-sensitive APIs, active/standby failover, and any service where a single A record to one box isn't good enough.
How it works
You tag each A/AAAA record with a region (or leave it global). Our anycast edge answers each visitor with the record whose region is closest to them, falling back to your global records when there's no regional match. Separately, you attach a health check to a record: we probe its target on the interval you choose from multiple locations, and when it fails enough times in a row we stop handing that IP out in DNS answers until it passes again. You configure both in the dashboard or over the API; the routing and probing happen on the edge, with nothing to install.
Before you start
- GeoDNS and Health Checks are part of the Pro and Business DNS plans. Free zones can't use either. Upgrade a zone's plan in the dashboard under its DNS settings.
- In the dashboard: my.cubepath.com → DNS → your zone.
- Over the API: base URL
https://api.cubepath.com, authenticate withAuthorization: Bearer <token>(scopesdns:read/dns:write), and addX-Requested-With: XMLHttpRequeston writes. Get a token under Account → API Tokens.
Part 1: GeoDNS
The regions
Every A/AAAA record carries a region. global (the default) is served everywhere; a regional value is served only to visitors near that region.
| Region code | Served to visitors near |
|---|---|
global | Everywhere (this is the default) |
us-east | US East (Virginia, Miami) |
us-central | US Central (Houston) |
eu-west | EU West (Amsterdam) |
eu-south | EU South (Spain) |
List them any time at GET /dns/regions.
Global plus regional overrides
The trick that makes GeoDNS safe is that the same name in a different region is a separate record. So you publish one global record as a catch-all, then add regional records that override it for nearby visitors:
www A 185.230.55.10 region = global (fallback, everyone)
www A 10.0.1.5 region = eu-west (served in/near Amsterdam)
www A 10.0.2.5 region = us-east (served in/near Virginia, Miami)
A visitor in Madrid gets the eu-west answer; a visitor in Texas gets us-east; a visitor with no regional match gets the global one. Always keep at least one global record for a name so nobody is left without an answer.
Set a region from the API
Add region when you create or update a record:
curl -X POST https://api.cubepath.com/dns/zones/<zone_uuid>/records \
-H "Authorization: Bearer $CUBEPATH_TOKEN" \
-H "X-Requested-With: XMLHttpRequest" \
-H "Content-Type: application/json" \
-d '{ "name": "www", "record_type": "A", "content": "10.0.1.5", "ttl": 60, "region": "eu-west" }'
Leaving region out, empty, or "global" all mean the same thing: served everywhere. In the dashboard it's a Region dropdown on the record form.
Part 2: Health Checks
A health check watches one record's target and takes it out of DNS when it stops responding. One health check per record.
What you can attach it to
Health checks work on A and AAAA records only. That's deliberate: failover works by dropping a dead IP from the set of answers, which only makes sense when the record's value is an address you can probe. There's no health-checking an MX, NS, TXT, or a lone CNAME.
Check types
check_type | Probes |
|---|---|
http | An HTTP request, expects a status code |
https | Same over TLS |
tcp | Opens a TCP connection to a port (port required) |
ping | ICMP echo |
Parameters
| Field | Default | Notes |
|---|---|---|
target | the record's own value | Host or IP to probe; override only if you check a different endpoint |
port | required for tcp | Also used by http/https |
path | none | http/https only, e.g. /health |
expected_status | 200 | http/https only |
interval_secs | 60 | How often to probe; minimum 10, maximum 3600 |
timeout_secs | 5 | Per-probe timeout; must be less than interval_secs |
healthy_threshold | 2 | Consecutive successes before the IP returns to DNS |
unhealthy_threshold | 3 | Consecutive failures before the IP is pulled |
enabled | true | Billed while enabled |
Create one from the API
curl -X PUT https://api.cubepath.com/dns/zones/<zone_uuid>/records/<record_uuid>/health-check \
-H "Authorization: Bearer $CUBEPATH_TOKEN" \
-H "X-Requested-With: XMLHttpRequest" \
-H "Content-Type: application/json" \
-d '{
"name": "api uptime",
"check_type": "https",
"path": "/health",
"expected_status": 200,
"interval_secs": 30,
"timeout_secs": 5,
"healthy_threshold": 2,
"unhealthy_threshold": 3
}'
The same call updates an existing check (it's an upsert). In the dashboard it's the Health check panel on the record.
What the status means
Each check reports a last_status:
healthy: the target is passing; its IP is being served.unhealthy: it failedunhealthy_thresholdtimes in a row; its IP is not being served right now.unknown: the check was just created and hasn't run enough probes yet.
When a target recovers and passes healthy_threshold times, its IP returns to DNS automatically.
Putting them together
Region and health check stack on the same record. A common pattern is one healthy-checked record per region plus a global fallback, so visitors get the nearest server, dead servers drop out on their own, and the global record catches anyone with no regional match:
api A 185.230.55.10 region = global + health check
api A 10.0.1.5 region = eu-west + health check
api A 10.0.2.5 region = us-east + health check
Keep your record TTL low (for example 30 to 60 seconds) so resolvers pick up a failover or a region change quickly.
Plans and pricing
| Plan | GeoDNS | Health checks | Zones | Records per zone | Min TTL |
|---|---|---|---|---|---|
| Free | No | 0 | 3 | 50 | 60 s |
| Pro | Yes | up to 10 | 10 | 200 | 30 s |
| Business | Yes | up to 50 | 50 | 1000 | 10 s |
Each health check is billed at $10/month, prorated and stopped the moment you delete it or disable it.
What this doesn't do
- GeoDNS routes by the visitor's resolver location, not by load. It isn't a load balancer; it sends a whole region to one answer set, it doesn't spread requests by server load. For per-request balancing inside a region, use a Load Balancer.
- A health check is per record, not per service. It watches one IP. If you run three servers, that's three records and three checks.
- DNS failover isn't instant. Resolvers cache answers for the TTL, so failover is only as fast as your TTL plus the check interval. Set both low if you need quick cutover.
Troubleshooting
| Symptom | Likely cause |
|---|---|
403 setting a region or health check | The zone is on the Free plan; GeoDNS and health checks need Pro or Business |
400 Health checks are only supported on A and AAAA records | You attached one to a CNAME/MX/TXT/etc; only A/AAAA can fail over |
400 A TCP health check requires a port | Add a port for tcp checks |
400 timeout must be less than interval | Lower timeout_secs below interval_secs |
| Region change not taking effect | A resolver is still caching the old answer; wait out the TTL or lower it |
Visitor gets the global answer, expected a regional one | No record exists for that region, or the visitor's resolver is geographically far from them; always keep a global fallback |
last_status stuck on unknown | The check hasn't completed enough probes yet; give it a few intervals |
API reference
GET /dns/regions List GeoDNS regions. (scope: dns:read)
POST /dns/zones/{uuid}/records { ..., "region" } Create a record with a region. (scope: dns:write)
PUT /dns/zones/{uuid}/records/{uuid} { "region" } Change a record's region. (scope: dns:write)
GET /dns/zones/{uuid}/health-checks List a zone's health checks. (scope: dns:read)
GET /dns/zones/{uuid}/records/{uuid}/health-check Get a record's health check. (scope: dns:read)
PUT /dns/zones/{uuid}/records/{uuid}/health-check {...} Create or update it (upsert). (scope: dns:write)
DELETE /dns/zones/{uuid}/records/{uuid}/health-check Remove it (billing stops). (scope: dns:write)
All requests authenticate with Authorization: Bearer <token> (or X-API-Key). Write requests also need X-Requested-With: XMLHttpRequest.
