ExaBGP on Ubuntu 24.04 on Azure User Guide
Overview
ExaBGP is the Python "BGP swiss army knife" from Exa Networks. Rather than trying to be a traditional routing stack, it is built to let software talk BGP: it runs as a daemon driven by a plain text configuration, announces and withdraws routes on command through a named pipe CLI (exabgp cli), and streams every BGP event it receives as JSON to helper processes you write in any language. That makes it the tool of choice for injecting routes from scripts and controllers, steering or blackholing traffic during DDoS mitigation, announcing anycast service addresses with health checks, and prototyping BGP behaviour including FlowSpec.
A bare BGP daemon has nothing to show on its own, so the cloudimg image ships a complete, self-contained, provably-working BGP reference deployment on the one VM:
- A primary ExaBGP (AS 65001) — the daemon you drive with
sudo exabgp cli— which dials out to the demo peer and listens nowhere. - A demo peer ExaBGP (AS 65002) listening on an internal loopback range address only (
10.255.255.2:179), authenticated with a per-VM TCP-MD5 key. - A real eBGP session between them that reaches established, with static routes propagating in both directions and every BGP event the peer receives logged as JSON to
/var/log/exabgp/peer-events.jsonl.
The moment the VM boots, sudo exabgp cli show neighbor summary shows an established session, and one announce route command later you can watch your route arrive at the peer in real time — so you can see BGP working before you point the daemon at your own routers.
What is included:
- ExaBGP 5.0.9 (
exabgpandexabgp-cli) in a dedicated virtual environment at/opt/exabgp/venv, pinned to the official upstream release and verified by SHA-256 - A primary ExaBGP (AS 65001) controlled over the named pipe CLI:
sudo exabgp cli <command> - A demo peer ExaBGP (AS 65002) listening on the internal demo address only, authenticated with a per-VM TCP-MD5 key
- JSON event logs of everything the daemons receive, at
/var/log/exabgp/peer-events.jsonland/var/log/exabgp/primary-events.jsonl - Per-VM TCP-MD5 key generated at first boot, at
/root/exabgp-credentials.txt(root only) - Secure by default: no port open on the public interface at all — BGP listens only on the internal demo link
- Runs as the unprivileged
exabgpsystem account with only the two capabilities it needs - 24/7 cloudimg support
Deploying on Azure
Launch the image from the Azure Marketplace on a Standard_B2s (or larger) VM. ExaBGP is a lightweight pure Python daemon; Standard_B2s (2 vCPU / 4 GB) is ample for the reference deployment and for a modest production route injector. SSH in as azureuser with the key you selected at launch:
ssh azureuser@<vm-ip>
On first boot the appliance generates this VM's unique TCP-MD5 session key, renders both ExaBGP configurations, validates them with exabgp validate, brings the reference deployment up, and proves the session establishes, routes propagate both ways and a live CLI injection lands — before it writes its first boot sentinel. No two VMs ever share a key, and the captured image contains no key at all.
Step 1: The Authenticated eBGP Session is Established
show neighbor summary lists the primary daemon's peers and their session state. The demo peer (10.255.255.2, AS 65002) is established — and it only gets there because both ends share this VM's TCP-MD5 key:
sudo exabgp cli show neighbor summary
Expected output:
Peer AS up/down state | #sent #recvd
10.255.255.2 65002 0:00:51 established 26 22

Step 2: Routes Propagate in Both Directions
The Adjacency RIB In shows what the primary has received from the demo peer (its static route 192.0.2.0/24), and the Adjacency RIB Out shows what the primary is advertising to it (its own static route 203.0.113.0/24):
sudo exabgp cli show adj-rib in
sudo exabgp cli show adj-rib out
Expected output:
10.255.255.2 ipv4 unicast 192.0.2.0/24 next-hop 10.255.255.2
10.255.255.2 ipv4 unicast 203.0.113.0/24 next-hop 10.255.255.1
The reverse direction is provable from a file. The demo peer runs an ExaBGP API process that appends every BGP event it receives to a JSON log — the arrival of the primary's route is recorded there:
sudo jq -c 'select(.type=="update") | .neighbor.message.update.announce // empty' /var/log/exabgp/peer-events.jsonl | head -3
Expected output:
{"ipv4 unicast":{"10.255.255.1":[{"nlri":"203.0.113.0/24"}]}}

Step 3: Inject a Route Live From the CLI
Route injection is what ExaBGP is for. Announce a prefix on the primary and watch it arrive at the demo peer in real time:
sudo exabgp cli announce route 198.51.100.0/24 next-hop 10.255.255.1
sleep 3
sudo jq -c 'select(.type=="update") | .neighbor.message.update.announce // empty' /var/log/exabgp/peer-events.jsonl | tail -1
Expected output:
{"ipv4 unicast":{"10.255.255.1":[{"nlri":"198.51.100.0/24"}]}}
Withdraw it just as easily — the peer logs the withdrawal too:
sudo exabgp cli withdraw route 198.51.100.0/24 next-hop 10.255.255.1
sleep 3
sudo jq -c 'select(.type=="update") | .neighbor.message.update.withdraw // empty' /var/log/exabgp/peer-events.jsonl | tail -1
Expected output:
{"ipv4 unicast":[{"nlri":"198.51.100.0/24"}]}

Step 4: Secure by Default
Nothing is exposed on the VM's routable interface. The primary daemon listens nowhere at all (it dials out), and the demo peer's BGP listener is bound to the internal demo address 10.255.255.2 only. This VM's TCP-MD5 key lives only in /root/exabgp-credentials.txt, readable by root alone:
sudo ss -lntH | awk '{print $1, $4}' | grep -E '10.255.255|:179'
sudo grep -v '^#' /root/exabgp-credentials.txt | head -2
Expected output (BGP on the internal demo link only — nothing on eth0):
LISTEN 10.255.255.2:179

Peering With Your Own Routers
The primary daemon's configuration is at /etc/exabgp/exabgp.conf. To peer with your own routers, add a neighbor block for each one — a minimal example:
neighbor <remote-peer-ip> {
router-id <this-vm-private-ip>;
local-address <this-vm-private-ip>;
local-as 65001;
peer-as <remote-as>;
md5-password "<a-shared-md5-key>";
static {
route <prefix-to-announce> next-hop self;
}
}
Open TCP 179 to those peers in your Azure Network Security Group, then apply the change with sudo systemctl restart exabgp.
You can drive everything at runtime without editing the config: sudo exabgp cli announce route <prefix> next-hop <ip>, sudo exabgp cli withdraw route <prefix> next-hop <ip>, and sudo exabgp cli help for the full command set. To feed received routes into your own automation, add an ExaBGP process block — the shipped /usr/local/sbin/exabgp-route-log.sh is a working example that streams every event as JSON to a file.
Managing the Service
sudo systemctl status exabgp --no-pager
sudo journalctl -u exabgp -n 50 --no-pager
The demo peer (exabgp-peer.service) exists purely to make the shipped image a working reference deployment. Once you have configured your own peers you can disable it if you prefer a clean single daemon setup with sudo systemctl disable --now exabgp-peer.service.
Support
Every cloudimg deployment ships fully patched with unattended security updates enabled, and includes 24/7 cloudimg support. For help with this image, contact support through the Azure Marketplace listing or at cloudimg.co.uk.