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 with Authorization: Bearer <token> (scopes dns:read / dns:write), and add X-Requested-With: XMLHttpRequest on 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 codeServed to visitors near
globalEverywhere (this is the default)
us-eastUS East (Virginia, Miami)
us-centralUS Central (Houston)
eu-westEU West (Amsterdam)
eu-southEU 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_typeProbes
httpAn HTTP request, expects a status code
httpsSame over TLS
tcpOpens a TCP connection to a port (port required)
pingICMP echo

Parameters

FieldDefaultNotes
targetthe record's own valueHost or IP to probe; override only if you check a different endpoint
portrequired for tcpAlso used by http/https
pathnonehttp/https only, e.g. /health
expected_status200http/https only
interval_secs60How often to probe; minimum 10, maximum 3600
timeout_secs5Per-probe timeout; must be less than interval_secs
healthy_threshold2Consecutive successes before the IP returns to DNS
unhealthy_threshold3Consecutive failures before the IP is pulled
enabledtrueBilled 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 failed unhealthy_threshold times 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

PlanGeoDNSHealth checksZonesRecords per zoneMin TTL
FreeNo035060 s
ProYesup to 101020030 s
BusinessYesup to 5050100010 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

SymptomLikely cause
403 setting a region or health checkThe zone is on the Free plan; GeoDNS and health checks need Pro or Business
400 Health checks are only supported on A and AAAA recordsYou attached one to a CNAME/MX/TXT/etc; only A/AAAA can fail over
400 A TCP health check requires a portAdd a port for tcp checks
400 timeout must be less than intervalLower timeout_secs below interval_secs
Region change not taking effectA resolver is still caching the old answer; wait out the TTL or lower it
Visitor gets the global answer, expected a regional oneNo record exists for that region, or the visitor's resolver is geographically far from them; always keep a global fallback
last_status stuck on unknownThe 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.