At 03:14 on a Tuesday, a /32 anycast prefix began flapping between two PoPs. Both sites were "equally good" — same AS path length, same origin, no MED, no local_pref differentiation. BGP's tiebreaker landed on router ID, and traffic bounced between Frankfurt and Ashburn every 30 seconds for forty minutes while everyone stared at dashboards wondering why latency graphs looked like a seismograph. The fix was four lines of config. The detection took ninety minutes because nobody had alerting on route churn.
This guide is for senior sysadmins and network engineers running BGP in anger — people who already know what a peer group is and don't need "BGP is the protocol that makes the internet work" explained to them. By the end you'll be able to build anycast that fails over deterministically, not by luck, and you'll understand exactly where the sharp edges are.
Scope boundary: BIRD 2.x only. BIRD 1.x uses a different config dialect and is effectively legacy at this point. If you're still on 1.6, migrate first — everything below assumes 2.x semantics.
:::note[TL;DR]
- Anycast is trivial; deterministic failover is the actual engineering problem. BGP best-path will happily pick a path you didn't intend.
- Use BFD for fast link/node failure detection and health-checked static routes for application-level failures. They protect against different things.
-
local_prefis your lever (inbound, you control it);MEDis advisory (outbound, your upstream decides whether to honor it). Prepend only after you've measured. - Every export filter ends in
reject. Whitelist prefixes. The default-route leak is not a hypothetical. :::
Prerequisites
- BIRD 2.x installed (
bird2package on Debian/Ubuntu;birdon RHEL-family via EPEL — check your distro, naming varies) - Root or
CAP_NET_ADMINfor kernel route programming - At least one BGP session to an upstream or route reflector
- Basic familiarity with
birdcand the concept of routing tables vs. protocols - A health-check endpoint you actually trust
Why BIRD, and When You Shouldn't Use It
Picking a routing daemon is a fifteen-year decision. Get it wrong and you inherit a config dialect that fights you every time you need to express something non-trivial. BIRD's selling point is that its filter language is a real expression language — you can do arithmetic, string matching, set membership, and conditional logic without shelling out to Python. That matters enormously when you're encoding a steering contract across dozens of peers.
Here's the minimal skeleton. Notice the separation of concerns: device learns link state, kernel syncs to the FIB, static holds your anycast service IP, and bgp speaks to the world.
router id 192.0.2.1;
protocol device {
}
protocol kernel {
ipv4 {
export all; # push BIRD's table into the kernel FIB
import none; # we don't want kernel routes back unless you know why
};
scan time 10;
}
protocol static anycast_v4 {
ipv4;
route 203.0.113.10/32 reject; # placeholder; toggled by health check
}
protocol bgp upstream_a from bgp_template {
neighbor 198.51.100.1 as 64500;
}
The import/export direction is the number one thing people get backwards. import means "routes coming from the peer into my table." export means "routes going from my table to the peer." If you find yourself importing your own anycast prefix, you've inverted something.
⚠️ TRUNCATED VERSION
This is an abbreviated cross-post. Full article (all config files, architecture diagrams, images): valtersit.com
🛠️ Partner Tools for Developers
ValtersIT curates partner deals for developers and sysadmins — VPS hosting, security tools, monitoring platforms and dev productivity gear. No filler, no affiliate spam.
Top comments (0)