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.5

Top-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.01.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.

Was this article helpful?
© 2026