BGP Reference
BGP Route Health Injection
Nexus GSLB includes an embedded BGP speaker (GoBGP v3) that injects and withdraws routes based on health check results. This lets the daemon participate directly in your network's routing fabric alongside its DNS-layer load balancing.
Two modes operate independently and can be combined:
| Mode | What it does |
|---|---|
| Node prefix | Announces configured prefixes while the daemon is running; withdraws them on graceful shutdown. Used for anycast VIPs. |
| Pool prefix | Announces prefixes when a pool's healthy-endpoint ratio meets a threshold; withdraws them when the pool degrades below it. |
Prerequisites
- A BGP-capable upstream: FRR, BIRD, VyOS, pfSense, OPNsense, or a physical router (Cisco IOS, Juniper JunOS, Arista EOS).
- Your own ASN, or a private-use ASN (64512–65534 for 2-byte, 4200000000–4294967294 for 4-byte).
- IP prefixes you are authorised to announce (your own PA/PI space, or RFC 5737/RFC 6598 private ranges in test setups).
- Layer-3 connectivity between the Nexus node and its BGP peer.
BGP RHI is designed for on-premises and colocation deployments where you control the routing infrastructure. Most public cloud VM providers do not offer BGP peering to tenants.
License: BGP route health injection requires the pro tier or above. It is disabled at startup on free and paid installations. Contact licensing@gslb.nexus to upgrade.
Configuration reference
All fields live under the bgp: key in
gslbd.yaml.
bgp:
enabled: true
localASN: 65001
routerID: "10.0.0.1"
listenAddr: "0.0.0.0"
listenPort: 179
holdTime: "90s"
keepAliveTime: "30s"
peers:
- remoteASN: 65000
remoteAddr: "10.0.0.254"
password: ""
nodePrefixes:
- "203.0.113.0/24"
poolPrefixes:
- poolID: "web-prod"
prefixes:
- "198.51.100.0/24"
withdrawThreshold: 0.5Top-level fields
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
bool | false |
Must be true to activate BGP. When false
all other fields are ignored. |
localASN |
uint32 | — | Required. Your autonomous system number. Private-use: 64512–65534 (2-byte) or 4200000000–4294967294 (4-byte). |
routerID |
string | — | Required. A stable IPv4 address used as the BGP router ID. Typically the primary loopback or management IP of this node. Must be unique across all GSLB nodes peering with the same upstream. |
listenAddr |
string | "0.0.0.0" |
Address on which GoBGP listens for incoming BGP connections. Set to a specific interface IP to restrict peering. |
listenPort |
int | 179 |
TCP port for BGP. Change only for non-standard setups (lab environments where port 179 is blocked). |
holdTime |
duration | "90s" |
BGP hold timer negotiated with peers. If no keepalive is received within this window the session drops. RFC 4271 minimum is 3 seconds. |
keepAliveTime |
duration | "30s" |
Interval at which keepalives are sent. Should be one third of
holdTime. |
peers — BGP neighbours
A list of BGP sessions to establish. Each entry describes one neighbour.
| Field | Type | Required | Description |
|---|---|---|---|
remoteASN |
uint32 | ✓ | Neighbour's AS number. Use the same ASN as localASN for
iBGP; a different ASN for eBGP. |
remoteAddr |
string | ✓ | IPv4 address of the neighbour. Must be reachable from this node. |
password |
string | — | MD5 TCP session password. Must match the peer's configuration. Leave empty if not used. |
nodePrefixes
— always-on VIP announcement
A list of CIDR prefixes announced unconditionally for the lifetime of the daemon. Routes are injected immediately after BGP sessions establish and withdrawn when the daemon shuts down gracefully (SIGINT/SIGTERM triggers a BGP NOTIFICATION before exit).
Use this for anycast VIPs that represent the GSLB service itself — you want these routes up as long as this node is alive.
bgp:
nodePrefixes:
- "203.0.113.0/24"
- "2001:db8:1::/48" # (IPv6 prefix announcement is not yet supported)!!! note "IPv4 only" The current implementation announces IPv4 unicast routes only. IPv6 prefix support is planned for a future release.
poolPrefixes
— health-driven prefix injection
A list of pool-to-prefix bindings. Each entry maps one pool (by its ID from the database) to one or more prefixes.
| Field | Type | Default | Description |
|---|---|---|---|
poolID |
string | — | Required. The pool's ID as stored in the SQLite
database. Find it with GET /api/v1/pools or in the
TUI. |
prefixes |
[]string | — | Required. One or more CIDR prefixes to announce when the pool is healthy. |
withdrawThreshold |
float64 | 0.0 |
Fraction of endpoints that must be healthy to keep the prefix
announced. 0.0 means "withdraw only when every endpoint is
down." 0.5 means "withdraw when fewer than 50% of endpoints
are healthy." Must be in the range
0.0–1.0. |
Announcement timing: pool prefixes are never announced at startup. They are announced only after the first health probe result arrives for the pool and the healthy-endpoint ratio meets the threshold. This prevents announcing routes before health state is known.
Idempotency: repeated health events for the same pool that do not change the announce/withdraw decision produce no BGP UPDATE — only genuine state transitions generate route churn.
How pool prefix injection works
The health manager calls a sink function on every probe result. Nexus wires BGP into this chain before the database sink:
probe result
→ health.Manager (deduplication)
→ BGP sink: OnHealthChange(poolID, ip, healthy)
→ recalculate pool healthy/total ratio
→ ratio >= threshold? → AnnouncePrefix (if not already announced)
→ ratio < threshold? → WithdrawPrefix (if currently announced)
→ DB sink (persist, metrics, WebSocket broadcast)
→ alerter (webhook delivery)
The threshold check uses a simple ratio:
healthy_count / total_count >= withdrawThreshold
where total_count is the number of distinct endpoint IPs
that have ever reported a health result for this pool in the current
daemon lifetime. This means:
- New pool member added while the daemon is running: total increases as soon as the first probe fires, which can temporarily push the ratio below threshold. If you add a member to a pool that is already announcing a prefix, the prefix stays announced until the new member's first probe completes.
- Member removed from the pool (via API or DB): the endpoint's last-known state remains in the in-memory map. Restart the daemon (or wait for the probe cycle) to clear it. In practice this is not an issue because removed members stop being probed and their state is frozen.
Validation
At startup, bgp configuration is validated before the
daemon enters the BGP session establishment loop. Validation errors are
fatal:
| Check | Error message |
|---|---|
localASN == 0 |
bgp.localASN must be non-zero when BGP is enabled |
routerID not a valid IP |
bgp.routerID "..." is not a valid IP address |
listenPort out of range |
bgp.listenPort out of range: N (must be 1–65535) |
holdTime not parseable |
bgp.holdTime "..." is not a valid duration |
keepAliveTime not parseable |
bgp.keepAliveTime "..." is not a valid duration |
Peer remoteASN == 0 |
bgp.peers[N].remoteASN must be non-zero |
Peer remoteAddr not a valid IP |
bgp.peers[N].remoteAddr "..." is not a valid IP |
| Node prefix not a valid CIDR | bgp.nodePrefixes: "..." is not a valid CIDR: ... |
Pool prefix poolID empty |
bgp.poolPrefixes entry is missing poolID |
| Pool prefix not a valid CIDR | bgp.poolPrefixes[id]: "..." is not a valid CIDR: ... |
withdrawThreshold outside 0–1 |
bgp.poolPrefixes[id].withdrawThreshold must be 0.0–1.0 |
Logging
BGP events are logged through the standard structured logger. Key log lines:
INFO bgp: node prefix announced prefix=203.0.113.0/24
INFO bgp: pool prefix announced pool=web-prod prefix=198.51.100.0/24 healthy=3 total=4
WARN bgp: pool prefix withdrawn pool=web-prod prefix=198.51.100.0/24 healthy=1 total=4
INFO BGP server started localASN=65001 routerID=10.0.0.1 peers=1 nodePrefixes=1 poolPrefixes=2
INFO BGP: graceful shutdown complete
Set logging.level: debug to see GoBGP's internal FSM
transitions (session state changes, UPDATE messages, keepalive
timing).
Configuration in the validation table
The BGP section is included in the startup validation table:
| Section | Validated fields |
|---|---|
bgp |
(when enabled) localASN non-zero;
routerID valid IP; listenPort 1–65535;
holdTime/keepAliveTime parseable durations;
each peer remoteASN non-zero and remoteAddr
valid IP; all nodePrefixes and
poolPrefixes[].prefixes valid CIDRs;
withdrawThreshold in 0.0–1.0 |
Terraform
The nexus_bgp_status data source reads live BGP state
for use in Terraform plans and outputs.
data "nexus_bgp_status" "this" {}
output "bgp_enabled" {
value = data.nexus_bgp_status.this.enabled
}
output "bgp_peers" {
value = data.nexus_bgp_status.this.peers
}
| Attribute | Type | Description |
|---|---|---|
enabled |
bool | Whether BGP is active on this node. |
local_asn |
number | Local autonomous system number. |
router_id |
string | BGP router ID. |
peers |
list(object) | Configured neighbours: address (string) and
asn (number) per entry. |
announced |
list(string) | Prefixes currently announced. |