O9
Networking AWS

OpenBGPD 9.2 on AWS User Guide

| Product: OpenBGPD 9.2 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and verifying OpenBGPD 9.2 on Ubuntu 24.04 from cloudimg on Amazon EC2. It is one version of the cloudimg Hardened FRRouting, OpenBGPD & BGP Routing and Peering AMIs listing on AWS Marketplace; you pick it as the software version when you subscribe and launch. The other routing and peering products in the listing are described on the listing's guide hub.

OpenBGPD is the OpenBSD project's implementation of BGP-4, the routing protocol that ties networks together across the internet and inside large data centres. It is a small, auditable code base with a strong focus on security and correctness. It runs as one daemon, bgpd, configured with a readable text file, and you operate it with the companion control utility bgpctl, which shows sessions, routes and policy at a glance. It can act as an edge speaker, a route server, a route reflector or an RPKI-validating router.

The image builds OpenBGPD 9.2 from the pinned upstream openbgpd-portable source release, verified by SHA-256 before it is unpacked, with the looking-glass HTTP daemon left out, so the instance serves no web page at all. The compiler and development headers used for the build are removed again, so no toolchain ships.

A bare BGP daemon has nothing to show on its own, so the image ships a self-contained reference deployment on the one instance:

  • A primary bgpd (AS 65001), the daemon you drive with bgpctl, listening for BGP on the internal address 10.255.255.1 only
  • A demo peer bgpd (AS 65002) on the internal address 10.255.255.2
  • A real eBGP session between them, authenticated with a TCP-MD5 key generated on your instance's first boot, with routes propagating both ways and an import policy filtering one prefix

Both internal addresses live on the loopback interface, so nothing in the demo is reachable from your VPC. No external neighbor is configured: the instance talks BGP to nobody else until you add your own peers.

What is on the image:

  • OpenBGPD 9.2 (bgpd and bgpctl in /usr/local/sbin), built from openbgpd-9.2.tar.gz (SHA-256 4e21ab5f478efc898640966a9115d9b6be42fabe114ea600dc9c22cf4e0f03aa)
  • openbgpd.service (the primary, control socket /var/run/bgpd.sock.0) and openbgpd-peer.service (the demo peer, control socket /var/run/bgpd-peer.sock)
  • openbgpd-firstboot.service, which generates the session key and proves the deployment on first boot, and openbgpd-loaddr.service, which adds the two internal addresses to lo on every boot
  • The configuration in /etc/openbgpd/bgpd.conf and a credentials record in /root/openbgpd-credentials.txt (root only)
  • The upstream ISC licence and build evidence in /usr/share/openbgpd/

Default user: ubuntu · Architecture: x86_64 · Root volume: 8 GiB gp3

Security posture

  • No BGP port on your network until you open one. Both daemons listen on their internal loopback addresses only. The listing security group opens SSH (22) only, and you open TCP 179 to your own peers when you add them.
  • Connections from anyone who is not a configured neighbor are refused. bgpd closes a BGP connection from any address it has no neighbor block for and logs connection from non-peer ... refused.
  • Per-instance session key. The TCP-MD5 key that authenticates the demo session is generated on each instance's first boot. No key ships in the image, so no two instances share one.
  • Root-only control. bgpctl talks to the daemons over UNIX sockets owned by root with mode 0660; any other user is refused. There is no network control port and no web interface.
  • Privilege separation. bgpd starts as root only to bind port 179, set the TCP-MD5 socket option and open its control socket. Its session, route and RTR engines run as the unprivileged _bgpd user inside an empty chroot (/var/empty). The services also run under a systemd sandbox (read-only /usr, /boot and /etc, no access to home directories, a private /tmp, no privilege gain).
  • No metadata access for the daemons. bgpd makes outbound connections to whatever neighbors you configure, so every OpenBGPD unit is blocked from reaching the instance metadata service (169.254.169.254 and fd00:ec2::254). Your peers and Amazon Time Sync (169.254.169.123) are unaffected.
  • IMDSv2 required. The AMI is registered with ImdsSupport v2.0, so instances launched from it refuse token-less (IMDSv1) metadata requests.
  • Fails closed. If first boot cannot prove the session establishes, the routes propagate and the filter works, it stops both daemons and removes the generated configuration, and tries again on the next boot.
  • Per-instance identity. SSH host keys and the machine ID are generated fresh on every instance.

Prerequisites

  1. An AWS account and permission to launch EC2 instances
  2. An EC2 key pair in the Region you launch in
  3. A VPC subnet for the router, and the addresses and AS numbers of the peers it will talk to

Recommended instance type: m5.large. OpenBGPD is lightweight; a small route server or edge speaker runs comfortably on t3.small or larger. Large full-table deployments need more memory.

Connecting to your instance

Version Operating system SSH user
OpenBGPD 9.2 on Ubuntu 24.04 Ubuntu 24.04 LTS ubuntu

Step 1: Launch the instance

Option A: AWS Marketplace console

  1. Open the cloudimg Hardened FRRouting, OpenBGPD & BGP Routing and Peering AMIs listing on AWS Marketplace and choose View purchase options, then Subscribe. One subscription covers every version in the listing.
  2. Once the subscription is active, choose Launch your software.
  3. Choose One-click launch from AWS Marketplace (or Launch from EC2 Console for the full launch wizard). In Version, choose OpenBGPD 9.2 on Ubuntu 24.04, then choose your Region.
  4. Pick an instance type (m5.large recommended; the software charge is $0.04 per vCPU per hour) and your key pair.
  5. Security group: the listing's default group allows SSH (22) only. Restrict it to your own IP address. You will allow BGP from your peers in Step 9.
  6. Choose your VPC and subnet, leave the metadata defaults (the image already requires IMDSv2), then choose Launch.

Option B: AWS CLI

Run this on your own workstation. It creates a security group that allows SSH from your management address only, then launches the instance. Replace the AMI ID with the one shown for your Region on the Marketplace launch page, and the VPC, subnet, key pair and CIDR with your own:

SG=$(aws ec2 create-security-group --group-name openbgpd-router \
  --description "OpenBGPD router" --vpc-id vpc-0123456789abcdef0 \
  --query GroupId --output text)
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 22 --cidr <your-mgmt-cidr>
aws ec2 run-instances \
  --image-id ami-0123456789abcdef0 \
  --instance-type m5.large \
  --key-name my-key-pair \
  --security-group-ids "$SG" \
  --subnet-id subnet-0123456789abcdef0 \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=openbgpd-router}]'

You do not need --metadata-options: the AMI already makes IMDSv2 required.

Step 2: Connect and read the instance details

Connect as ubuntu with the key pair you chose:

ssh -i ~/.ssh/my-key-pair.pem ubuntu@<public-ip>

First boot finishes within a minute of launch. It generates the instance's TCP-MD5 session key, writes both daemon configurations, starts the daemons, proves the session reaches Established, and records the details in /root/openbgpd-credentials.txt, readable by root only. The rendered configurations carry the key too, so they are readable only by root and the _bgpd group:

sudo stat -c '%a %U:%G %n' /root/openbgpd-credentials.txt /etc/openbgpd/bgpd.conf /etc/openbgpd/bgpd-peer.conf
sudo grep -v '^OPENBGPD_PEERING_MD5_KEY=' /root/openbgpd-credentials.txt
600 root:root /root/openbgpd-credentials.txt
640 root:_bgpd /etc/openbgpd/bgpd.conf
640 root:_bgpd /etc/openbgpd/bgpd-peer.conf
# OpenBGPD on Ubuntu 24.04 LTS — packaged by cloudimg
#
# The key below was generated on this instance's FIRST BOOT and exists
# nowhere else. The AMI this instance was launched from contains no key at all:
# OpenBGPD ships no credential of its own, and the rendered configurations that
# carry this key were removed before the image was captured. This instance's
# authenticated demo eBGP session is the only place the key is used.
#
# Generated: 2026-10-19T21:25:36Z


# The reference deployment on this instance
#   Primary daemon (AS 65001)   control socket /var/run/bgpd.sock.0        (drive it with: bgpctl ...)
#   Demo peer (AS 65002)        control socket /var/run/bgpd-peer.sock   (bgpctl -s /var/run/bgpd-peer.sock ...)
#   Internal demo link          10.255.255.1 <-> 10.255.255.2 (loopback-local)
#   Session authentication      BGP TCP-MD5, key above (unique to this instance)
#
# Try it:
#   bgpctl show summary
#   bgpctl show neighbor 10.255.255.2
#   bgpctl show rib
#   bgpctl -s /var/run/bgpd-peer.sock show rib
#
# The primary daemon's config is /etc/openbgpd/bgpd.conf. To peer with YOUR OWN routers,
# add a neighbor { ... } block per router and this instance's PRIVATE IP to a
# 'listen on' line, allow TCP 179 from those peers only in the instance's EC2
# security group, check with: sudo bgpd -n -f /etc/openbgpd/bgpd.conf
# then apply with: sudo systemctl restart openbgpd

The demo key itself is on the OPENBGPD_PEERING_MD5_KEY= line, which the command above leaves out. You never need it for your own peers: each real neighbor gets its own key, agreed with that peer.

Step 3: Confirm the services are running

The four units are active, bgpd reports version 9.2, and the build record names the exact upstream release and its hash:

systemctl is-active openbgpd-loaddr openbgpd-firstboot openbgpd openbgpd-peer
sudo bgpd -V
cat /usr/share/openbgpd/build-evidence.txt
active
active
active
active
OpenBGPD 9.2
OpenBGPD 9.2 — packaged by cloudimg for AWS Marketplace
upstream project   : https://www.openbgpd.org/ (openbgpd-portable)
release            : openbgpd-9.2.tar.gz
artifact sha256    : 4e21ab5f478efc898640966a9115d9b6be42fabe114ea600dc9c22cf4e0f03aa
licence            : ISC (see /usr/share/openbgpd/LICENSE.upstream)
built with         : ./configure --disable-bgplgd --with-privsep-user=_bgpd
packaged at        : 2026-10-19T21:12:14Z

Step 4: The authenticated eBGP session is Established

bgpctl show summary lists the primary's neighbors and their state; the demo peer is up with two prefixes received. bgpctl show neighbor shows the session detail, including that it is authenticated with md5sig:

sudo bgpctl show summary
sudo bgpctl show neighbor 10.255.255.2 | sed -n '1,5p'
Neighbor                   AS    MsgRcvd    MsgSent  OutQ Up/Down  State/PrfRcvd
cloudimg-demo-peer      65002          6          6     0 00:01:18      2
BGP neighbor is 10.255.255.2, remote AS 65002
 Description: cloudimg-demo-peer
  BGP version 4, remote router-id 10.255.255.2, using md5sig
  BGP state = Established, up for 00:01:18
  Last read 00:00:18, holdtime 90s, keepalive interval 30s

The session only reaches Established because both ends hold this instance's TCP-MD5 key.

bgpctl show summary with the demo peer up and two prefixes received, and the neighbor detail showing the md5sig-authenticated session Established

Step 5: Routes propagate

bgpctl show rib shows the primary's routing table: it has learned 192.0.2.0/24 from the demo peer and originates 203.0.113.0/24 itself. Query the demo peer's control socket with -s /var/run/bgpd-peer.sock to see the other direction, where 203.0.113.0/24 arrives from AS 65001:

sudo bgpctl show rib
sudo bgpctl -s /var/run/bgpd-peer.sock show rib
flags: * = Valid, > = Selected, I = via IBGP, A = Announced,
       S = Stale, E = Error, F = Filtered, L = Leaked
origin validation state: N = not-found, V = valid, ! = invalid
aspa validation state: ? = unknown, V = valid, ! = invalid
origin: i = IGP, e = EGP, ? = Incomplete

flags  vs destination          gateway          lpref   med aspath origin
      N-? 192.0.2.0/24         10.255.255.2      100     0 65002 i
AI*>  N-? 203.0.113.0/24       0.0.0.0           100     0 i
flags: * = Valid, > = Selected, I = via IBGP, A = Announced,
       S = Stale, E = Error, F = Filtered, L = Leaked
origin validation state: N = not-found, V = valid, ! = invalid
aspa validation state: ? = unknown, V = valid, ! = invalid
origin: i = IGP, e = EGP, ? = Incomplete

flags  vs destination          gateway          lpref   med aspath origin
AI*>  N-? 192.0.2.0/24         0.0.0.0           100     0 i
AI*>  N-? 198.51.100.0/24      0.0.0.0           100     0 i
      N-? 203.0.113.0/24       10.255.255.1      100     0 65001 i

The learned routes carry no * (valid) flag. bgpd checks every next hop against the routing table it reads from the kernel, and the demo addresses are host addresses on the loopback interface, not routes, so bgpctl show nexthop lists them as unresolved and the demo routes are never installed into the kernel. A peer on your VPC subnet resolves through the connected subnet route, and its routes are marked valid and selected. The three demo prefixes are documentation ranges (RFC 5737) that are never routed on the internet.

The primary's RIB with the learned 192.0.2.0/24 and the self-originated 203.0.113.0/24, and the demo peer's RIB with 203.0.113.0/24 learned from AS 65001

Step 6: The import policy filters

The primary's import filter rejects 198.51.100.0/24. The demo peer genuinely advertises it (it is in the peer's own RIB) but it is absent from the primary's RIB, because the deny from any prefix 198.51.100.0/24 rule dropped it. OpenBGPD applies RFC 8212 default-deny to eBGP sessions, so the accept and announce rules are explicit too:

sudo bgpctl -s /var/run/bgpd-peer.sock show rib 198.51.100.0/24 | tail -1
sudo bgpctl show rib 198.51.100.0/24 | tail -1
sudo grep -E '^(allow|deny)' /etc/openbgpd/bgpd.conf
AI*>  N-? 198.51.100.0/24      0.0.0.0           100     0 i
flags  vs destination          gateway          lpref   med aspath origin
allow from any
allow to any
deny from any prefix 198.51.100.0/24

The second command prints only the table header: the primary never installed the blackholed prefix. sudo bgpctl show rib neighbor 10.255.255.2 in lists what the peer sent before filtering, and it does include 198.51.100.0/24, so the prefix arrived and the policy dropped it. Filter rules are evaluated in order and the last match wins, so the deny rule overrides the allow from any above it.

The demo peer advertising 198.51.100.0/24, the primary's RIB with no such route, and the allow and deny filter rules

Step 7: Secure by default

These are every TCP listener on the instance. SSH listens on all interfaces (the security group restricts it), BGP is bound to the two internal demo addresses only, and the rest are systemd-resolved's local DNS stub:

sudo ss -Hltn | awk '{print $4}' | sort
0.0.0.0:22
10.255.255.1:179
10.255.255.2:179
127.0.0.53%lo:53
127.0.0.54:53
[::]:22

A BGP connection from an address that is not a configured neighbor is closed straight away and logged. This connects from 127.0.0.1, which has no neighbor block, and shows the log line bgpd writes:

nc -z -w 3 -s 127.0.0.1 10.255.255.1 179; sleep 1
sudo journalctl -u openbgpd --since "1 minute ago" -o cat | grep 'non-peer' | tail -1
Connection to 10.255.255.1 179 port [tcp/bgp] succeeded!
connection from non-peer 127.0.0.1 refused

The TCP connection opens, but bgpd sends no BGP OPEN and closes it. The same applies to any address on your network once you add a listen on line: only the neighbors you configure can bring up a session.

The control sockets belong to root. bgpctl works with sudo and is refused for any other user:

ls -l /var/run/bgpd.sock.0 /var/run/bgpd-peer.sock
bgpctl show summary 2>&1 | head -1 || true
srw-rw---- 1 root root 0 Oct 19 21:25 /var/run/bgpd-peer.sock
srw-rw---- 1 root root 0 Oct 19 21:25 /var/run/bgpd.sock.0
bgpctl: connect: /var/run/bgpd.sock.0: Permission denied

The TCP listening set with BGP on the internal demo addresses only, a connection from a non-neighbor address refused and logged, and bgpctl refused for a non-root user

Step 8: Metadata access is blocked for the daemons

Every OpenBGPD unit carries a systemd IPAddressDeny rule for the instance metadata addresses, so nothing running inside bgpd can reach them. Each entry is a single address, so your peers and the Amazon Time Sync Service at 169.254.169.123 keep working, as chrony shows. Privilege separation is visible in the process list: only the two parent processes run as root.

systemctl show -p IPAddressDeny openbgpd.service openbgpd-peer.service
chronyc -n sources | grep 169.254
ps -o user,args -C bgpd
IPAddressDeny=fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32

IPAddressDeny=fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32
^* 169.254.169.123               3   4   377    12  -2559ns[+8277ns] +/-  468us
USER     COMMAND
root     /usr/local/sbin/bgpd -d -v -f /etc/openbgpd/bgpd.conf
root     /usr/local/sbin/bgpd -d -v -f /etc/openbgpd/bgpd-peer.conf
_bgpd    /usr/local/sbin/bgpd -R -d -v
_bgpd    /usr/local/sbin/bgpd -R -d -v
_bgpd    /usr/local/sbin/bgpd -S -d -v
_bgpd    /usr/local/sbin/bgpd -S -d -v
_bgpd    /usr/local/sbin/bgpd -T -d -v
_bgpd    /usr/local/sbin/bgpd -T -d -v

-R, -S and -T are the route decision, session and RTR engines.

Step 9: Peer with your own routers

The primary's configuration is /etc/openbgpd/bgpd.conf. First find this instance's private IP: BGP peers on your VPC connect to it, never to the public address. The metadata service needs an IMDSv2 token:

TOKEN=$(curl -s -X PUT http://169.254.169.254/latest/api/token -H 'X-aws-ec2-metadata-token-ttl-seconds: 60')
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/local-ipv4; echo
172.31.95.206

Then edit /etc/openbgpd/bgpd.conf with sudo and your editor. For a production router, replace the demo content (the AS 65001 and router-id lines, the network 203.0.113.0/24 line, the demo neighbor block and the three demo filter rules) with your own. A minimal edge configuration that peers with one router, announces one prefix and accepts only what you expect looks like this:

AS 64512
router-id 172.31.95.206
listen on 172.31.95.206

network 198.18.0.0/24

neighbor 172.31.80.10 {
    descr "core-router-1"
    remote-as 64513
    tcp md5sig password "replace-with-the-key-you-agree-with-this-peer"
}

# eBGP is default-deny (RFC 8212): allow only what you mean to exchange
allow from 172.31.80.10 prefix 10.20.0.0/16 or-longer
allow to 172.31.80.10 prefix 198.18.0.0/24

Replace the AS numbers, addresses, prefixes and key with your own. Check the file before you apply it, then restart the daemon:

sudo bgpd -n -f /etc/openbgpd/bgpd.conf
sudo systemctl restart openbgpd

bgpd -n prints configuration OK when the file parses. Once you no longer need the demo, stop the demo peer with sudo systemctl disable --now openbgpd-peer.service.

Then allow TCP 179 from your peers only in the instance's security group. In the EC2 console choose the instance, open Security, choose the security group, then Edit inbound rules:

Protocol Port Source Purpose
TCP 179 each peer's address (/32) BGP sessions

Two EC2 settings matter if this instance will forward traffic for the routes it learns rather than only exchange routes:

  • Disable the source/destination check on the instance's network interface (EC2 console: Actions, Networking, Change source/destination check). EC2 drops forwarded packets otherwise.
  • Point your VPC route tables at the instance for the prefixes it should carry, and enable IP forwarding on the instance (net.ipv4.ip_forward=1 in /etc/sysctl.d/).

bgpd installs the routes it selects into the kernel routing table. If the instance is a route server that only exchanges routes and never forwards traffic, add fib-update no to the global section of bgpd.conf so it leaves the kernel table alone. The bgpd.conf(5) and bgpctl(8) manual pages on openbgpd.org cover every option.

Step 10: Manage the service

systemctl status openbgpd --no-pager -n 0
sudo journalctl -u openbgpd -n 10 --no-pager -o cat
● openbgpd.service - OpenBGPD primary daemon — AS 65001 (cloudimg)
     Loaded: loaded (/etc/systemd/system/openbgpd.service; enabled; preset: enabled)
    Drop-In: /etc/systemd/system/openbgpd.service.d
             └─40-cloudimg-sandbox.conf, 50-cloudimg-imds-deny.conf
     Active: active (running) since Mon 2026-10-19 21:25:30 UTC; 1min 24s ago
       Docs: https://www.cloudimg.co.uk/guides/openbgpd-9-2-on-ubuntu-24-04-aws/
             https://www.openbgpd.org/
   Main PID: 905 (bgpd)
      Tasks: 4 (limit: 3736)
     Memory: 2.0M (peak: 2.2M)
        CPU: 31ms
     CGroup: /system.slice/openbgpd.service
             ├─905 /usr/local/sbin/bgpd -d -v -f /etc/openbgpd/bgpd.conf
             ├─916 /usr/local/sbin/bgpd -R -d -v
             ├─919 /usr/local/sbin/bgpd -S -d -v
             └─921 /usr/local/sbin/bgpd -T -d -v
softreconfig out done for Loc-RIB
RDE soft reconfiguration done
neighbor 10.255.255.2 (cloudimg-demo-peer): state change Active -> Connect, reason: ConnectRetryTimer expired
neighbor 10.255.255.2 (cloudimg-demo-peer): state change Connect -> OpenSent, reason: Connection opened
neighbor 10.255.255.2 (cloudimg-demo-peer): state change OpenSent -> OpenConfirm, reason: OPEN message received
neighbor 10.255.255.2 (cloudimg-demo-peer): state change OpenConfirm -> Established, reason: KEEPALIVE message received
neighbor 10.255.255.2 (cloudimg-demo-peer): sending IPv4 unicast EOR marker
neighbor 10.255.255.2 (cloudimg-demo-peer): received IPv4 unicast EOR marker
connection from non-peer 127.0.0.1 refused
connection from non-peer 127.0.0.1 refused

The journal shows every session state change, update and policy event; the drop-in line confirms the sandbox and the metadata block are applied. Common bgpctl commands:

sudo bgpctl show summary                 # every neighbor and its state
sudo bgpctl show rib neighbor <peer-ip> in   # what a peer sent you (adj-rib-in)
sudo bgpctl show rib neighbor <peer-ip> out  # what you announce to it
sudo bgpctl reload                       # re-read bgpd.conf without dropping sessions
sudo bgpctl neighbor <peer-ip> clear     # reset one session

Step 11: Security recommendations

  • Keep the security group tight. Allow TCP 179 only from each peer's own address, and SSH only from your management address.
  • Authenticate every session. Give each neighbor its own TCP-MD5 key (tcp md5sig password), agreed with that peer.
  • Filter explicitly. Allow only the prefixes you expect from each peer and announce only your own. Never leave allow from any and allow to any in place on a router with real peers.
  • Consider RPKI. OpenBGPD validates route origins against RPKI data from an RTR cache (rtr blocks in bgpd.conf); see the manual for rpki-client and RTR.
  • Keep the OS patched. Unattended security upgrades are on for the Ubuntu packages. OpenBGPD itself is built from source, so updating it means a newer cloudimg version from the listing.

Support and licensing

OpenBGPD is developed by the OpenBSD project and distributed under the ISC licence; a few portability files carry the BSD 3-Clause and BSD 2-Clause licences. The upstream licence text is in /usr/share/openbgpd/LICENSE.upstream. cloudimg provides the packaging, the reference deployment, the per-instance key, the secure defaults and 24/7 support.

cloudimg is not affiliated with or endorsed by the OpenBSD project. OpenBGPD is a mark of its owner and is used here only to identify the software.

For help with deployment or configuration, email support@cloudimg.co.uk or visit cloudimg support.