Adios
BlogNetwork

Network

Build an Anycast CDN

Build an Anycast CDN with BIRD and NGINX: get a /24, cache HTTPS, test failover, and add regional Anycast pools with GeoDNS.

Adios teamUpdated September 26, 202625 min read

To build an Anycast CDN, announce the same IP prefix from multiple locations and run an HTTPS cache at each one. We’ll use BIRD for BGP and NGINX for caching, then show how to group edges into regional Anycast pools selected by GeoDNS.

Choose global Anycast or regional Anycast with GeoDNS

Global Anycast uses one service IP across all your edges. DNS returns that IP; BGP selects the receiving edge according to network policy and available paths. A cache hit is served there. A miss goes to your origin and can populate that edge’s cache.

Regional Anycast with GeoDNS adds a selection step. Each regional pool has a different service IP, shared by the edges in that pool. GeoDNS returns a regional IP; BGP selects an edge advertising it. This lets you choose a regional pool before internet routing chooses the server.

Two ways to route requests to an Anycast CDN
Routing choiceGlobal AnycastRegional Anycast + GeoDNS
DNS answerThe same CDN IP for everyone.A different CDN IP for each selected regional pool.
BGP announcementsAll edges announce the same prefix.Edges in each pool announce that pool’s prefix.
One edge failsWithdraw its route; other announcing edges remain available.Withdraw its route; another edge in the same pool can receive new connections.
An entire region failsAnother advertising region may receive the traffic after convergence.DNS must select a healthy fallback pool. Cached DNS answers can still point to the failed pool.
Choose it whenYou want one stable IP and one global pool.You want explicit regional pool selection, separate capacity, or different regional origins.

DNS selects an IP; BGP selects an edge; NGINX serves or fills the cache

GLOBAL ANYCAST
cdn.example.com -> one IP -> BGP -> Paris or New York cache
                                              |
                                         cache miss
                                              v
                                            origin

REGIONAL ANYCAST + GEODNS
cdn.example.com -> GeoDNS -> Europe IP -> BGP -> Paris / Frankfurt
                         -> US IP     -> BGP -> New York / Chicago
                         -> default   -> chosen fallback pool

What you need for the first build

Start with the global setup: two fresh Debian 12 servers with systemd, BIRD 2, NGINX, and customer BGP; one authorized IPv4 /24 and an agreed origin ASN; an HTTPS origin on a separate IP; and a domain whose DNS you control. The examples assume direct BGP peers. Your upstream must supply the actual peering values.

Budget for address space and ASN arrangements, both edge servers, outbound traffic, origin traffic, DNS health checks, and monitoring. Get current quotes and confirm BGP eligibility before ordering. The addresses below are documentation examples; replace them with your own. Deployment and failure timings must be measured on your network.

Get a /24 and permission to announce it

For your own IPv4 announcements on the public internet, /24 is the practical minimum block size: 256 addresses. A higher prefix length means a smaller block: /25 contains 128 addresses and /26 contains 64. Those smaller blocks are commonly filtered. Confirm that both hosting locations support customer BGP and will accept your prefix before buying or leasing it.

You can lease a block with your own ASN, request space from a provider, apply directly to a registry, or buy an existing block from its holder.

Ways to obtain an IPv4 /24
RouteWhat to doCheck before committing
Lease a block with your own ASNLease a /24 and have the holder authorize your ASN to originate it. Arrange the ASN separately if you do not already have one.Confirm ROA and IRR updates, permission to announce from both PoPs, and the lease’s renewal and exit terms.
Request a provider-assigned blockAsk a provider such as Vultr for address space it can route for your BGP setup in the locations you need.Confirm whether a full /24 is available, which ASN originates it, and whether you can announce it outside that provider’s network.
Apply directly to a registryRequest an allocation from your regional internet registry. RIPE NCC has a /24 waiting list for eligible LIRs that have never received an IPv4 allocation.This is an allocation under registry policy. Paying membership fees does not guarantee a block or a delivery date.
Buy an existing blockPurchase from a holder selling its block, directly or through a broker or marketplace, then complete the registry’s transfer process.Check the seller’s authority, transfer eligibility, fees, routing and abuse history, and upstream acceptance before payment.

Make the block routable

Agree the origin ASN with your upstreams. If you need your own ASN, apply through your regional registry or a sponsoring provider under that registry’s eligibility rules. The ASN identifies the network originating your prefix; it is separate from the address lease or transfer.

Have the resource holder authorize that ASN in RPKI: create a ROA for the /24 with maximum length /24. Add the matching IRR route object where your upstream requires it, and supply a letter of authorization if requested. A ROA authorizes routing; it does not announce the route for you.

Send this request to both hosting providers. Do not start the server configuration until they confirm the prefix is accepted and provide the peer settings.

Information to exchange with each upstream

Our prefix:       [your /24]
Our origin ASN:   [your ASN]
Locations:        Paris and New York, announced simultaneously

Please confirm:
- Prefix accepted; required ROA, IRR record, and authorization
- Peer IP, peer ASN, local source IP, and any BGP password
- Direct or multihop peering
- Replies sourced from our /24 are allowed
- Global export policy and available regional communities

Prepare the first edge server

We’ll put the first edge in Paris and the second in New York. Each location is a point of presence, or PoP. Both receive the same CDN IP; each keeps its own unicast address for SSH and origin requests.

Replace all addresses, ASNs, and example.com names below. These are documentation values, not addresses you can announce. The example assumes directly connected BGP peers; use your provider’s multihop settings if needed.

Address plan

cdn.example.com → 203.0.113.80
                       |
                 BGP chooses a PoP
                  /             \
             Paris cache     New York cache
                  \             /
                   cache misses
                        |
              origin.example.com
                 192.0.2.10:443

Prefix:              203.0.113.0/24
CDN IP on BOTH PoPs:  203.0.113.80/32
Origin ASN:          64496
Upstream ASN:        64497
Paris node / peer:   198.51.100.10 / 198.51.100.1
New York / peer:     198.51.100.20 / 198.51.100.17

Install packages and bind the CDN address

Run this on Paris. The /32 binds the CDN address locally; the covering blackhole route drops packets for unused addresses in the /24. The systemd unit below restores both after a reboot, before BIRD and NGINX start.

Allow HTTPS to the CDN IP and BGP TCP port 179 from your provider’s peer. Keep management access on the unicast address. This reverse-proxy setup does not need Linux packet forwarding.

Paris shell

sudo apt-get update
sudo apt-get install -y bird2 nginx curl ca-certificates iproute2 python3
sudo install -d -o www-data -g www-data /var/cache/nginx/cdn

Keep the address across reboots

Save this unit with your real service IP and prefix. It adds a dedicated dummy interface and retains the server’s existing unicast configuration.

/etc/systemd/system/cdn-address.service

[Unit]
Description=CDN service address
Before=bird.service nginx.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=-/usr/sbin/ip link add anycast0 type dummy
ExecStart=/usr/sbin/ip address replace 203.0.113.80/32 dev anycast0
ExecStart=/usr/sbin/ip link set anycast0 up
ExecStart=/usr/sbin/ip route replace blackhole 203.0.113.0/24

[Install]
WantedBy=multi-user.target

Order services after the address

Add the dependency to both services, start the address unit, and check the origin route. It should use the unicast network, not anycast0.

Paris shell

for service in bird nginx; do
  sudo mkdir -p /etc/systemd/system/$service.service.d
  sudo tee /etc/systemd/system/$service.service.d/cdn-address.conf >/dev/null <<'EOF'
[Unit]
Requires=cdn-address.service
After=cdn-address.service
EOF
done

sudo systemctl daemon-reload
sudo systemctl enable --now cdn-address
ip address show dev anycast0
ip route get 192.0.2.10

Configure BIRD to announce the /24

Save this as /etc/bird/bird.conf on Paris. The export filter permits only your /24. The static protocol starts disabled so the server advertises nothing until HTTPS works. BIRD keeps this route in its own routing table; the Linux blackhole route was configured separately above.

/etc/bird/bird.conf

router id 198.51.100.10;

protocol device {
  scan time 10;
}

protocol static cdn_prefix {
  disabled yes;
  ipv4;
  route 203.0.113.0/24 blackhole;
}

filter export_cdn {
  if net = 203.0.113.0/24 then accept;
  reject;
}

protocol bgp transit {
  local as 64496;
  source address 198.51.100.10;
  neighbor 198.51.100.1 as 64497;
  graceful restart off;

  ipv4 {
    import none;
    export filter export_cdn;
  };
}

Check the BGP session

Add provider-required authentication or multihop settings before starting. Expect Established with no exported CDN prefix yet. If the session stays down, check the peer address, ASN, firewall, and authentication with your upstream.

Validate and load the configuration

sudo bird -p -c /etc/bird/bird.conf
sudo systemctl enable --now bird
sudo birdc configure
sudo birdc show protocols all transit
sudo birdc show route export transit

Issue HTTPS certificates

Use DNS-01 so you can issue the CDN certificate before announcing the IP. Here is a Certbot example for a zone hosted on Cloudflare DNS. Use the matching Certbot DNS plugin if your zone is elsewhere. Cloudflare’s proxy stays off for the CDN record.

Create a DNS API token restricted to the required zone with Zone:DNS:Edit permission. On each edge, store it as dns_cloudflare_api_token = YOUR_TOKEN in /root/.secrets/cloudflare.ini, with directory mode 700 and file mode 600. Issue certificates on the two edges one at a time and keep the credentials out of source control.

Run on each edge; replace the hostname and email

sudo apt-get install -y certbot python3-certbot-dns-cloudflare
sudo install -d -m 700 /root/.secrets
sudo touch /root/.secrets/cloudflare.ini
sudo chmod 600 /root/.secrets/cloudflare.ini
sudoedit /root/.secrets/cloudflare.ini

sudo certbot certonly --dns-cloudflare \
  --dns-cloudflare-credentials /root/.secrets/cloudflare.ini \
  --dns-cloudflare-propagation-seconds 60 \
  --cert-name cdn.example.com -d cdn.example.com \
  --email ops@example.com --agree-tos --non-interactive

Reload NGINX after renewal

After the NGINX configuration below passes nginx -t, install this deploy hook and check the renewal timer on each edge. Independent certificates can serve the same hostname; they do not need to share a private key. As the fleet grows, centralize issuance and secure distribution to reduce DNS credential exposure and coordinate renewals.

Enable renewal after configuring NGINX

sudo install -d /etc/letsencrypt/renewal-hooks/deploy
sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx >/dev/null <<'EOF'
#!/bin/sh
set -eu
/usr/sbin/nginx -t
/usr/bin/systemctl reload nginx
EOF
sudo chmod 755 /etc/letsencrypt/renewal-hooks/deploy/reload-nginx
sudo systemctl enable --now certbot.timer
sudo certbot renew --dry-run --run-deploy-hooks
systemctl list-timers certbot.timer

Configure HTTPS and the edge cache

Use the origin’s separate IP in cdn_origin and its certificate hostname in proxy_ssl_name. At the origin, serve /assets/logo.v1.svg with Cache-Control: public, max-age=60, s-maxage=300 and an ETag. Also serve /cdn-probe.txt with body origin-ok and Cache-Control: no-store.

Save this complete configuration in /etc/nginx/conf.d/cdn.conf, included inside NGINX’s http block. It caches only /assets/. Other paths go directly to the origin.

/etc/nginx/conf.d/cdn.conf

proxy_cache_path /var/cache/nginx/cdn
  levels=1:2 keys_zone=cdn:50m max_size=10g
  inactive=60m use_temp_path=off;

map "$http_authorization$http_cookie" $skip_private {
  default 1;
  ""      0;
}

upstream cdn_origin {
  server 192.0.2.10:443;
  keepalive 32;
}

server {
  listen 203.0.113.80:443 ssl;
  server_name cdn.example.com;

  ssl_certificate     /etc/letsencrypt/live/cdn.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/cdn.example.com/privkey.pem;
  ssl_protocols TLSv1.2 TLSv1.3;

  if ($host != cdn.example.com) { return 421; }

  proxy_http_version 1.1;
  proxy_set_header Connection "";
  proxy_set_header Host origin.example.com;
  proxy_set_header X-Forwarded-Proto https;
  proxy_set_header X-Forwarded-For $remote_addr;
  proxy_ssl_server_name on;
  proxy_ssl_name origin.example.com;
  proxy_ssl_verify on;
  proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
  proxy_ssl_verify_depth 3;
  proxy_connect_timeout 3s;
  proxy_read_timeout 15s;

  proxy_hide_header X-Edge-Id;
  proxy_hide_header X-Cache;
  add_header X-Edge-Id "paris-1" always;
  add_header X-Cache $upstream_cache_status always;

  location = /__edge/health {
    default_type text/plain;
    return 200 "edge-ok\n";
  }

  location ^~ /assets/ {
    proxy_pass https://cdn_origin;
    proxy_cache cdn;
    proxy_cache_key "$scheme|$host|$request_uri";
    proxy_cache_methods GET HEAD;
    proxy_cache_bypass $skip_private $http_cache_control $http_pragma;
    proxy_no_cache $skip_private $http_cache_control $http_pragma
                   $upstream_http_set_cookie;
    proxy_cache_lock on;
    proxy_cache_revalidate on;
  }

  location / {
    proxy_pass https://cdn_origin;
  }
}

Give the origin predictable test responses

For an NGINX origin, add these locations inside its existing HTTPS server for origin.example.com. They provide a cacheable asset and an uncached probe without changing your application. Use the origin’s own valid certificate and confirm its access log records both paths.

Inside the origin’s HTTPS server block

location = /assets/logo.v1.svg {
  default_type image/svg+xml;
  add_header Cache-Control "public, max-age=60, s-maxage=300";
  add_header ETag '"cdn-demo-v1"';
  return 200 '<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80"><rect width="80" height="80" fill="blue"/></svg>';
}

location = /cdn-probe.txt {
  default_type text/plain;
  add_header Cache-Control "no-store";
  return 200 "origin-ok\n";
}

What this cache will store

The test asset stays fresh in a shared cache for 300 seconds because of s-maxage. inactive=60m controls eviction of unused objects, not freshness. The key separates hostnames and query strings. Requests with cookies or authorization bypass the cache; NGINX also respects private, no-store, Set-Cookie, and Vary response handling.

Each PoP has its own disk cache. Use versioned asset URLs for releases. This configuration provides no global purge API and does not force stale content to be served when the origin fails.

Enable the first PoP

Run the two HTTPS checks on Paris. --resolve connects to its local CDN address while preserving the hostname for TLS. Both requests must succeed with valid certificates and the expected bodies before you enable the route.

Check the edge and the origin path

sudo nginx -t
sudo systemctl enable --now nginx
sudo systemctl reload nginx

curl --fail --show-error --max-time 3 \
  --resolve cdn.example.com:443:203.0.113.80 \
  https://cdn.example.com/__edge/health
# Expected body: edge-ok

curl --fail --show-error --max-time 5 \
  --resolve cdn.example.com:443:203.0.113.80 \
  https://cdn.example.com/cdn-probe.txt
# Expected body: origin-ok

Announce the route and set DNS

After the checks pass, enable cdn_prefix and ask the upstream to confirm the /24 is accepted and propagated. Set your hostname’s A record to the CDN IP. Use DNS-only if your DNS provider also offers a CDN proxy. Test HTTPS from an external network before continuing.

Enable export, then create the DNS record

sudo birdc enable cdn_prefix
sudo birdc show route export transit

# DNS record, using your real hostname and IP:
# cdn.example.com.  300  IN  A  203.0.113.80

Add the second PoP

Repeat the setup on New York. Keep the /24, CDN IP, origin ASN, hostname, origin, and cache rules the same. Change the values below, install a valid certificate, and pass the local HTTPS checks before enabling cdn_prefix. Use that location’s actual peer ASN and settings if they differ.

Values that change between the two PoPs
SettingParisNew York
BIRD router ID198.51.100.10198.51.100.20
BGP source address198.51.100.10198.51.100.20
BGP neighbor198.51.100.1198.51.100.17
NGINX X-Edge-Idparis-1new-york-1

Confirm both locations serve requests

Leave DNS unchanged. Probe the public hostname from several networks and inspect X-Edge-Id. To check a specific PoP, run curl --resolve on that server itself; using the Anycast IP from your laptop cannot force Paris or New York.

Withdraw unhealthy edges automatically

A live BGP session does not mean HTTPS is working. Install the example health controller on each edge after both pass the local checks. It probes that edge’s CDN IP with the correct TLS hostname, withdraws cdn_prefix after three consecutive failures, and requires 30 seconds of continuous health plus a 60-second withdrawal hold-down before advertising again.

The systemd unit attempts withdrawal when the controller exits, restarts it after a crash, and uses a watchdog for a stalled loop. Each pass reads back BIRD’s protocol state. These files check local TLS and NGINX responsiveness; monitor cache storage, origin reachability, and public routing separately. A shared-origin outage should not automatically withdraw every edge that can still serve cached content.

Download and inspect the files before installing

curl --fail --show-error --remote-name \
  https://adios.dev/examples/anycast-cdn/edge-health.py
curl --fail --show-error --remote-name \
  https://adios.dev/examples/anycast-cdn/edge-health.service

# Review both files, then install on the dedicated example edge.
sudo install -m 755 edge-health.py /usr/local/sbin/edge-health.py
sudo install -m 644 edge-health.service /etc/systemd/system/edge-health.service
sudo tee /etc/default/edge-health >/dev/null <<'EOF'
CDN_HOST=cdn.example.com
CDN_IP=203.0.113.80
EOF
# Replace these documentation values before starting.
sudoedit /etc/default/edge-health
sudo systemctl daemon-reload
sudo systemctl enable --now edge-health
sudo journalctl -u edge-health -n 30 --no-pager

Test cache hits and failover

On each PoP, request the test asset twice. A fresh key should produce MISS then HIT. Confirm the second request does not reach the origin by checking its access log. An initial HIT means the key is already cached.

Run locally on each edge

for attempt in 1 2; do
  curl --silent --show-error --fail --max-time 10 \
    --resolve cdn.example.com:443:203.0.113.80 \
    -D - -o /dev/null https://cdn.example.com/assets/logo.v1.svg
done

# This request should report X-Cache: BYPASS.
curl --silent --show-error --fail --max-time 10 \
  --resolve cdn.example.com:443:203.0.113.80 \
  -H 'Cookie: session=cdn-test' -D - -o /dev/null \
  https://cdn.example.com/assets/logo.v1.svg

Withdraw one PoP

From an external network currently reaching Paris, keep making new HTTPS connections. Stop the health controller before manually disabling the route so it cannot re-enable it. Verify that successful requests eventually identify New York. Record failures and elapsed time; existing connections may need to reconnect.

Run on Paris while watching an external probe

sudo systemctl stop edge-health
sudo birdc disable cdn_prefix
sudo birdc show route export transit

# After local HTTPS checks pass again:
sudo systemctl start edge-health
# The controller waits for sustained health before advertising.

Test a service failure and a reboot

With the controller running and the route advertised, stop NGINX on Paris. Confirm that the controller withdraws cdn_prefix and external requests move to New York. Start NGINX again and verify that the recovery delay prevents immediate re-advertisement. Repeat on the other edge.

Reboot one edge while the other serves traffic. Verify the dummy address, BIRD session, certificate, cache, and controller recover in order. Measure both warm-cache and cold-cache behavior at the survivor. Record the observed results with the software versions and probe networks; do not assume a fixed BGP convergence time.

Run on one edge while probing from another network

sudo systemctl stop nginx
# Wait for the controller's failure threshold; check the journal and route.
sudo journalctl -u edge-health -n 30 --no-pager
sudo birdc show route export transit
sudo systemctl start nginx
# Watch recovery, then repeat the test with a planned reboot.

Add regional Anycast pools with GeoDNS

Build at least two edges per regional pool using the same BIRD, TLS, and cache setup. Give Europe one service IP and North America another. Within a pool, every edge announces the same prefix. Each independently routed IPv4 pool needs its own accepted prefix, typically a /24; two IPs from one shared /24 do not give BGP independent regional routes.

Change the dummy address, covering route, BIRD prefix/export filter, NGINX listen address, and controller CDN_IP to match the pool. Keep management and origin addresses outside those service prefixes. All pools serve the same CDN hostname, with valid certificates and matching cache rules. Set the origin per pool if your application requires regional backends.

Example GeoDNS policy with a global fallback pool
Query locationDNS answer for cdn.example.comEdges announcing that IP
EuropeEU_CDN_IPParis and Frankfurt, using EU_PREFIX/24.
North AmericaUS_CDN_IPNew York and Chicago, using US_PREFIX/24.
Default / fallbackGLOBAL_CDN_IPThe global pool, using its separate GLOBAL_PREFIX/24.

Create the GeoDNS records

In a GeoDNS service such as Route 53, create geolocation A records with the same name, cdn.example.com, and distinct record identifiers. Set Europe to EU_CDN_IP, North America to US_CDN_IP, and Default to GLOBAL_CDN_IP. Those values are placeholders for your actual addresses. Start with a 60-second TTL and measure resolver behavior.

If you move authoritative DNS from Cloudflare to Route 53, update certificate issuance to the Route 53 DNS plugin as well, or deliberately delegate the ACME challenge zone. The earlier Cloudflare plugin requires control of the authoritative challenge records.

Associate each record with the health of its pool. In Route 53, an unhealthy geographic match can fall back to a broader geographic record, then the default record. Keep the global fallback healthy and large enough to absorb regional traffic. If you colocate global and regional pools, configure and monitor their prefixes separately; the example controller manages one prefix per instance.

Check pool health, then test both failure paths

Combine checks of individual edges through their unicast paths with external probes of the regional service IP. A probe to an Anycast IP can keep succeeding after one edge fails because it reaches another edge. Remove a pool from DNS when the pool cannot serve traffic, rather than whenever one member fails.

First withdraw one edge: the regional IP should still work through another edge in that pool. Then make the entire pool unavailable in a controlled test: new DNS answers should select the fallback. Test both fresh lookups and clients retaining the old answer. DNS changes cannot move an existing connection or immediately replace every cached answer.

GeoDNS estimates location from the recursive resolver or an EDNS Client Subnet hint. Test from several real networks, including public resolvers. Do not treat a default record as a guaranteed fail-safe: Route 53 can return unhealthy records when all eligible choices fail health checks.

Run from machines in each target region

dig +short cdn.example.com A
curl --silent --show-error --fail --max-time 10 \
  -D - -o /dev/null https://cdn.example.com/assets/logo.v1.svg

# Record: DNS answer, X-Edge-Id, X-Cache, errors, and timing.
# Repeat during one-edge withdrawal and during whole-pool failure.

Other routing setups

You can also mix globally announced nodes with nodes whose routes an upstream exports only to selected networks. Configure this with that provider’s documented BGP communities and verify the scope externally. NO_EXPORT refers to AS boundaries, not continents. This is a separate routing policy from GeoDNS selecting regional pools.

If a hosting provider cannot peer with BGP, a transit provider can announce your prefix and deliver traffic over tunnels. Check return routing and MTU, and put a cache at each receiving location. Sending every tunnel back to one distant cache keeps that cache and its links on every request path.

Keep the CDN working

Monitor each PoP through its unicast management path as well as through the public Anycast IP. Otherwise, a failed location can disappear from your checks when routing sends every probe somewhere healthy.

Problems to watch for as the CDN grows
ProblemWhat you will noticeWhat to do
GeoDNS points clients at an unavailable poolFresh lookups or cached answers keep reaching a failed region.Check pool health, the default policy, resolver caching, and fallback capacity. Exercise whole-region failure separately from one-edge withdrawal.
Lease, ROA, or routing-record changesSome networks stop reaching the prefix while BGP sessions remain up.Track renewal dates and RPKI validity. Recheck authorization before changing ASN or upstream. Plan an overlap period when renumbering leased space.
Traffic shifts after a provider changeClients land at a distant PoP or overload a smaller location.Measure edge ID and latency from several access networks. Review upstream policy and scope before adjusting communities or prepending.
One PoP fails or its cache restartsThe surviving edge or origin receives a sudden traffic spike.Test failover capacity with a cold cache. Budget bandwidth and disk headroom; add an origin shield when duplicate fills justify it.
Old or private content is cachedUsers see stale releases or another user’s response.Use versioned assets. Test Cookie, Authorization, private, no-store, and Set-Cookie behavior after changes. Make purge delivery observable if you add it.
A deployment or certificate differs between PoPsOnly some networks see TLS failures, errors, or old behavior.Roll out one PoP first. Check certificate expiry, config version, and real HTTPS responses on every node before expanding the rollout.
The health controller fails or flapsA broken edge keeps advertising, or clients repeatedly switch locations.Supervise the controller, require recovery hold-downs, and test its watchdog. Coordinate graceful-restart behavior with the upstream.
A tunnel or return path breaksSmall requests work but large transfers stall, or replies never arrive.Check MTU, path-MTU discovery, return routing, and source-address filters. Test large downloads after tunnel or provider changes.
An attack saturates the linkHealthy servers become unreachable before HTTP limits help.Arrange upstream mitigation and know its activation process. Keep origin access restricted and track egress usage and cache-bypass traffic.
Several services share the same /24Withdrawing for one failed service also moves the healthy services.Define health policy for every service carried by the prefix. Use separate routable prefixes when services need independent withdrawal.

Your first working CDN

You are ready to add traffic when both PoPs serve valid HTTPS, each proves MISS then HIT, private responses remain uncached, and withdrawing either route moves new requests to the surviving location. Start with public static assets and measure the origin load before adding more cacheable paths.

  All articles