BIRD Internet Routing Daemon on Ubuntu 24.04 on AWS User Guide
Overview
This guide covers launching and verifying BIRD Internet Routing Daemon 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.
BIRD is an open source dynamic IP routing daemon developed by CZ.NIC. One daemon, bird, speaks BGP, OSPF, RIP, Babel, BFD and RPKI-to-router for IPv4 and IPv6, keeps its own routing tables, filters routes with a small programming language, and exports the routes it selects into the Linux kernel. Internet exchanges run it as a route server, and networks use it as an edge router, a route reflector and an anycast announcer. You drive it at runtime with the birdc client.
The image ships BIRD 2.20.0 as the bird2 package from CZ.NIC Labs' signed Debian repository (pkg.labs.nic.cz/bird2), which publishes BIRD releases for Ubuntu 24.04 sooner than Ubuntu does. Apt trusts that repository with one signing key, and apt preferences keep you on the 2.x line: apt upgrade takes new 2.x releases from CZ.NIC. They never pull in Ubuntu's older bird2, a CZ.NIC development snapshot, BIRD 1 or BIRD 3.
A routing daemon needs peers to show anything, so the image ships a working configuration that touches nothing outside the instance:
deviceandkernelprotocols for IPv4 and IPv6, so BIRD tracks the interfaces and exports its routes into the Linux kernel- A
staticprotocol,demo_static, holding the three RFC 5737 documentation prefixes as blackhole routes. These ranges are never routed on the internet, and blackhole routes send no traffic anywhere.
You can see BIRD running end to end, from its own table to the kernel, without a single peer. No BGP, OSPF or other network protocol is configured, so BIRD opens no network port until you add one.
What is on the image:
- BIRD 2.20.0: the
birddaemon and thebirdcandbirdclclients in/usr/sbin, from the unmodified CZ.NICbird2package bird.service, which runs BIRD as the unprivilegedbirduser, andbird-firstboot.service, which sets the router ID on first boot- The configuration in
/etc/bird/bird.conf(owned byroot:bird, mode0640) and an instance record in/root/bird-instance.txt(root only) - The CZ.NIC repository source, its signing key and the apt preferences in
/etc/apt/, and the list of packages the build added, with their sources, in/usr/share/doc/cloudimg-bird/package-provenance.txt - IP forwarding switched on in
/etc/sysctl.d/99-cloudimg-bird.conf, because a router forwards packets
Default user: ubuntu · Architecture: x86_64 · Root volume: 8 GiB gp3
Security posture
- No routing port on your network until you add one. BIRD opens no TCP or UDP socket. Its only listener is the local control socket, so the instance's only network service is SSH (22). Once you add a BGP peer, BIRD listens on TCP 179, and you open that port only to your peers' addresses.
- No password, no network management interface. BIRD has no login of its own and no web or API port. You manage it with
sudo birdcover a Unix socket,/run/bird/bird.ctl, owned byroot:birdwith mode0660. Root and members of thebirdgroup can open it. The kernel refuses every other local user (Permission denied). This is a file permission, not a firewall rule, so changing the firewall does not open it. - No shared secret in the image. There is nothing to rotate: the image holds no BIRD password, key or token. For TCP-MD5 on a BGP session, you choose the password and agree it with that peer.
- Per-instance router ID. On first boot, BIRD's router ID is set to the instance's own private IPv4 address, read from IMDSv2. No two instances share an ID, and none carries the build machine's.
- Unprivileged, sandboxed daemon. BIRD starts as root only to create its control socket. It then switches to the
birduser and keeps four capabilities:CAP_NET_ADMIN(to program the kernel routing table),CAP_NET_BIND_SERVICE(to bind port 179),CAP_NET_RAW(for OSPF and RIP) andCAP_NET_BROADCAST. It runs under a systemd sandbox: a read-only/usrand/etc, no access to home directories, a private/tmpand no privilege gain. - No metadata access for the daemon. BIRD makes outbound connections to whatever neighbors you configure.
bird.serviceis therefore blocked from reaching the instance metadata service at 169.254.169.254 and fd00:ec2::254. Your peers, your VPC 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. - Crash recovery and fail-closed configuration. If BIRD crashes, systemd restarts it. If you restart with a configuration that
bird -prejects, the unit stays stopped and failed instead of restarting in a loop, and no control socket appears. - Per-instance identity. Every instance generates its own SSH host keys and machine ID.
Prerequisites
- An AWS account and permission to launch EC2 instances
- An EC2 key pair in the Region you launch in
- A VPC subnet for the router, and the addresses and AS numbers of the peers it will talk to
Recommended instance type: m5.large. BIRD is very light: the daemon in this configuration uses about 2 MB of memory. A small route server or edge router runs comfortably on t3.small or larger. Full internet tables from several peers need a few GB of memory per copy of the table.
Connecting to your instance
| Version | Operating system | SSH user |
|---|---|---|
| BIRD Internet Routing Daemon on Ubuntu 24.04 | Ubuntu 24.04 LTS | ubuntu |
Step 1: Launch the instance
Option A: AWS Marketplace console
- Open the cloudimg Hardened FRRouting, OpenBGPD & BGP Routing and Peering AMIs listing on AWS Marketplace, choose View purchase options, then Subscribe. One subscription covers every version in the listing.
- Once the subscription is active, choose Launch your software.
- Choose One-click launch from AWS Marketplace (or Launch from EC2 Console for the full launch wizard). In Version, choose BIRD Internet Routing Daemon on Ubuntu 24.04, then choose your Region.
- Pick an instance type (
m5.largerecommended; the software charge is $0.04 per vCPU per hour) and your key pair. - Security group: the listing's default group allows SSH (22) only. Restrict it to your own IP address. Step 9 covers allowing BGP from your peers.
- 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. Replace the VPC, subnet, key pair and CIDR with your own:
SG=$(aws ec2 create-security-group --group-name bird-router \
--description "BIRD 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=bird-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 reads the instance's private IPv4 address from IMDSv2 and writes it into /etc/bird/bird.conf as the router ID. It then checks the configuration with bird -p and records the details in /root/bird-instance.txt. It starts nothing itself: systemd starts BIRD once it has finished. The record holds no password or key; it is root-only because it describes the router:
sudo stat -c '%a %U:%G %n' /root/bird-instance.txt /etc/bird /etc/bird/bird.conf
sudo cat /root/bird-instance.txt
600 root:root /root/bird-instance.txt
750 root:bird /etc/bird
640 root:bird /etc/bird/bird.conf
# BIRD Internet Routing Daemon 2.20.0 on Ubuntu 24.04 - packaged by cloudimg
# This instance holds NO BIRD password or key: BIRD is managed only with 'sudo birdc' over the local control socket.
BIRD_ROUTER_ID=172.31.83.57
BIRD_VERSION=2.20.0
BIRD_CONFIG=/etc/bird/bird.conf
BIRD_CONTROL_SOCKET=/run/bird/bird.ctl
BIRD_GUIDE_URL=https://www.cloudimg.co.uk/guides/bird-internet-routing-daemon-on-ubuntu-24-04-aws/
#
# Try it: sudo birdc show status sudo birdc show protocols sudo birdc show route
# ip route show proto bird
On later boots the first-boot unit sees that the instance is already prepared and changes nothing. A router ID you set yourself is kept across reboots.
Step 3: Confirm the services and the package source
Both units are active and BIRD reports version 2.20.0. The provenance file lists the packages the build added on top of Ubuntu and where each came from: bird2 from CZ.NIC, and its one extra library from Ubuntu's main archive. apt-cache policy shows the installed version and the preference that holds it on CZ.NIC's 2.x line:
systemctl is-active bird-firstboot bird
bird --version 2>&1
cat /usr/share/doc/cloudimg-bird/package-provenance.txt
apt-cache policy bird2 | sed -n '1,5p'
active
active
BIRD version 2.20.0
# Packages the cloudimg build added on top of the cloudimg Ubuntu 24.04 golden image, with the apt source of
# the installed version (d-506: Ubuntu main only, plus bird2 from CZ.NIC Labs' signed repository).
bird2 2.20.0-cznic.1~noble https://pkg.labs.nic.cz/bird2 noble/main
libssh-gcrypt-4 0.10.6-2ubuntu0.5 http://us-east-1.ec2.archive.ubuntu.com/ubuntu noble-updates/main
bird2:
Installed: 2.20.0-cznic.1~noble
Candidate: 2.20.0-cznic.1~noble
Version table:
*** 2.20.0-cznic.1~noble 990
Step 4: BIRD is running and programming the kernel
This is the positive control: the daemon is up, and the four protocols are up. The three documentation prefixes are in BIRD's table and in the Linux kernel routing table, where proto bird marks the routes BIRD installed:
sudo birdc show status
sudo birdc show protocols
sudo birdc show route
ip route show proto bird
BIRD 2.20.0 ready.
BIRD 2.20.0
Router ID is 172.31.83.57
Hostname is ip-172-31-83-57
Current server time is 2026-11-11 10:05:04.635
Last reboot on 2026-11-11 10:04:09.528
Last reconfiguration on 2026-11-11 10:04:09.528
Daemon is up and running
BIRD 2.20.0 ready.
Name Proto Table State Since Info
dev1 Device --- up 10:04:09.528
kernel4 Kernel master4 up 10:04:09.528
kernel6 Kernel master6 up 10:04:09.528
demo_static Static master4 up 10:04:09.528
BIRD 2.20.0 ready.
Table master4:
198.51.100.0/24 blackhole [demo_static 10:04:09.528] * (200)
192.0.2.0/24 blackhole [demo_static 10:04:09.528] * (200)
203.0.113.0/24 blackhole [demo_static 10:04:09.528] * (200)
blackhole 192.0.2.0/24 metric 32
blackhole 198.51.100.0/24 metric 32
blackhole 203.0.113.0/24 metric 32
The router ID is this instance's private address. The kernel4 protocol is what copies BIRD's best routes into the kernel. Every route you later learn from a peer reaches the kernel the same way, subject to your filters.
Step 5: Secure by default
These are every TCP and UDP listener on the instance, then every Unix socket BIRD listens on. SSH listens on all interfaces (the security group restricts it). The rest are local system services: systemd-resolved's DNS stub, chrony on the loopback and the DHCP client. BIRD itself has no TCP or UDP socket at all:
ss -Hltun | awk '{print $1, $5}' | sort
sudo ss -Hlxp | grep '"bird"' | awk '{print $5}'
tcp 0.0.0.0:22
tcp 127.0.0.53%lo:53
tcp 127.0.0.54:53
tcp [::]:22
udp 127.0.0.1:323
udp 127.0.0.53%lo:53
udp 127.0.0.54:53
udp 172.31.83.57%ens5:68
udp [::1]:323
/run/bird/bird.ctl
The control socket is the only way in, and it has no password. Anyone who can open it can reconfigure or shut down the router. It is owned by root:bird with mode 0660, in a directory only the bird group can enter. The ubuntu user is not in that group, so birdc run as ubuntu without sudo is refused by the kernel, and with sudo it works:
ls -ld /run/bird /run/bird/bird.ctl
id -nG ubuntu
sudo -u ubuntu birdc show status 2>&1 | grep -o 'Permission denied' || true
sudo birdc show status | grep 'Daemon is up'
drwxr-x--- 2 bird bird 60 Nov 11 10:04 /run/bird
srw-rw---- 1 root bird 0 Nov 11 10:04 /run/bird/bird.ctl
ubuntu adm cdrom sudo dip lxd
Permission denied
Daemon is up and running
Let an operator use birdc without sudo
Add the operator's account to the bird group. The change applies at their next login:
sudo usermod -aG bird alice
Only add people you would trust with root on the router: a member of the bird group can change every protocol, route and filter. Never expose the control socket on the network. If a remote system must drive BIRD, reach the socket over SSH instead.
Step 6: Metadata access is blocked for the daemon
bird.service carries a systemd IPAddressDeny rule for the instance metadata addresses, so nothing running inside BIRD can reach them. Each entry is a single address, so your peers, your VPC and the Amazon Time Sync Service at 169.254.169.123 keep working, as chrony shows. BIRD runs as the unprivileged bird user:
systemctl show -p IPAddressDeny bird.service
chronyc -n sources | grep 169.254
ps -o user,args -C bird
IPAddressDeny=::ffff:169.254.169.254/128 fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32 ::ffff:168.63.129.16/128
^* 169.254.169.123 3 4 177 1 -3792ns[ +102us] +/- 499us
USER COMMAND
bird /usr/sbin/bird -f -c /etc/bird/bird.conf -u bird -g bird
You can see the block from inside the service. The first command runs curl in bird.service's own control group and asks the metadata service for an IMDSv2 token. The request never gets an answer (000). The second runs the same request as root outside the service and gets 200:
CG=$(systemctl show -p ControlGroup --value bird.service)
sudo sh -c 'echo $$ > "/sys/fs/cgroup$1/cgroup.procs" && exec curl -s -m 4 -o /dev/null -w "%{http_code}\n" -X PUT -H "X-aws-ec2-metadata-token-ttl-seconds: 60" http://169.254.169.254/latest/api/token' _ "$CG" || true
curl -s -m 4 -o /dev/null -w "%{http_code}\n" -X PUT -H "X-aws-ec2-metadata-token-ttl-seconds: 60" http://169.254.169.254/latest/api/token
000
200
Your VPC is deliberately not blocked. BIRD's peers are normally other hosts in your VPC or across a VPN, so bird.service may reach any address except the metadata service. Limit who BIRD talks to with its configuration (only the neighbors you define get a session) and with the security group.
Step 7: Forwarding and the EC2 source/destination check
The image is a router, so IP forwarding is on for IPv4 and IPv6:
cat /etc/sysctl.d/99-cloudimg-bird.conf
sysctl net.ipv4.ip_forward net.ipv6.conf.all.forwarding
# cloudimg BIRD appliance: this instance is an IP router. On EC2 also turn OFF the instance's source/destination
# check, or the VPC drops the traffic it forwards (see the guide).
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
If this instance will forward traffic for the routes it learns, rather than only exchange routes, two EC2 settings matter:
- Turn off the source/destination check. By default, EC2 drops any packet whose source or destination is not the instance's own address, and that is every packet a router forwards. In the EC2 console, choose the instance, then Actions, Networking, Change source/destination check, and select Stop. From the AWS CLI:
bash
aws ec2 modify-instance-attribute --instance-id i-0123456789abcdef0 --no-source-dest-check
- Point your VPC route tables at the instance (its network interface) for the prefixes it should carry.
The image ships no firewall rule of its own. The security group is the place to decide what may reach the router.
If the instance only exchanges routes, for example as a route server or route reflector, leave the source/destination check on. To stop forwarding altogether, set both values to 0 in /etc/sysctl.d/99-cloudimg-bird.conf and run sudo sysctl --system.
Step 8: Peer with your own routers
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.83.57
The configuration is /etc/bird/bird.conf. It ends with a commented-out BGP example. Edit the file with sudo and your editor (for example sudo nano /etc/bird/bird.conf), and add a protocol per peer. This is a minimal eBGP session with one router: it authenticates with TCP-MD5, accepts only the prefixes you expect from that peer and announces only your own:
# Prefixes this router accepts from the core, and the ones it announces.
define CORE_PREFIXES = [ 10.20.0.0/16{16,24} ];
define MY_PREFIXES = [ 10.30.0.0/16 ];
protocol bgp core1 {
description "core-router-1";
local 172.31.83.57 as 64512;
neighbor 172.31.80.10 as 64513;
password "replace-with-the-key-you-agree-with-this-peer";
ttl security on; # directly connected peer only (GTSM, RFC 5082)
ipv4 {
import where net ~ CORE_PREFIXES;
export where net ~ MY_PREFIXES;
};
}
Replace the AS numbers, addresses, prefixes and password with your own:
passwordturns on TCP-MD5 for the session. The peer must use the same password, and a session with a wrong or missing one never comes up. Generate a long random one yourself (for examplehead -c 18 /dev/urandom | base64) and agree it with that peer only.ttl security onmakes BIRD send with TTL 255 and drop BGP packets that arrive with a lower TTL. A spoofer more than one hop away therefore cannot reach the session. Enable it on both ends, and only for directly connected peers. For a peer several hops away, leave it out and usemultihopinstead.- The
importandexportfilters decide what crosses the session. An eBGP session should never beimport allorexport all. Theexportfilter above announces only your own prefixes, and only those BIRD actually has in its table.
The demo_static blackholes stay in place unless you remove that protocol. Remove it once you no longer need the demo. Check the file, then apply it to the running daemon without a restart:
sudo birdc configure check
sudo birdc configure
sudo birdc show protocols all core1
birdc configure check parses the file without touching the running daemon, and prints Configuration OK or the line and reason it rejects. birdc configure applies the file. If the new file does not parse, BIRD keeps running with the old configuration and reports the error. show protocols all core1 shows the session state (Established once the peer is up) and how many routes were imported, filtered and exported.
Step 9: Open BGP to your peers in the security group
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 |
Add one rule per peer rather than a subnet-wide range. BIRD accepts a session only from neighbors it has a protocol for, and the security group stops everyone else from reaching port 179 at all. OSPF (IP protocol 89), RIP (UDP 520), Babel (UDP 6696) and BFD (UDP 3784 and 3785) need their own rules if you use them. OSPF also needs multicast, which a VPC does not carry, so on AWS it is normally run over a GRE or VPN tunnel.
Step 10: Manage the service
BIRD logs through syslog, so its messages are in the journal under bird.service:
systemctl status bird --no-pager -n 0
sudo journalctl -u bird -b --no-pager -o cat | tail -n 4
● bird.service - BIRD Internet Routing Daemon (cloudimg)
Loaded: loaded (/etc/systemd/system/bird.service; enabled; preset: enabled)
Drop-In: /etc/systemd/system/bird.service.d
└─50-cloudimg-imds-deny.conf
Active: active (running) since Wed 2026-11-11 10:04:10 UTC; 54s ago
Docs: https://www.cloudimg.co.uk/guides/bird-internet-routing-daemon-on-ubuntu-24-04-aws/
https://bird.nic.cz/
man:bird(8)
Process: 834 ExecStartPre=/usr/lib/bird/prepare-environment (code=exited, status=0/SUCCESS)
Process: 876 ExecStartPre=/usr/sbin/bird -p -c ${BIRD_CONF} (code=exited, status=0/SUCCESS)
Process: 901 ExecStartPost=/usr/local/sbin/bird-ctl-socket (code=exited, status=0/SUCCESS)
Main PID: 899 (bird)
Tasks: 1 (limit: 3748)
Memory: 1.7M (peak: 2.8M)
CPU: 164ms
CGroup: /system.slice/bird.service
└─899 /usr/sbin/bird -f -c /etc/bird/bird.conf -u bird -g bird
Starting bird.service - BIRD Internet Routing Daemon (cloudimg)...
Started
bird-ctl-socket: /run/bird/bird.ctl root:bird 0660 in /run/bird bird:bird 0750; BIRD is up
Started bird.service - BIRD Internet Routing Daemon (cloudimg).
The drop-in line confirms the metadata block is applied. Prefer sudo birdc configure to a restart: it applies a new configuration without dropping sessions whose settings did not change. Common commands:
sudo birdc show protocols # every protocol and its state
sudo birdc show protocols all <name> # one protocol in detail, with route counts
sudo birdc show route protocol <name> # the routes one peer sent you, after import filters
sudo birdc show route export <name> # what you announce to one peer
sudo birdc show route for 10.20.1.1 # which route matches an address
sudo birdc reload in <name> # re-run import filters without dropping the session
sudo birdc restart <name> # reset one session
Step 11: Update BIRD
unattended-upgrades installs Ubuntu's security updates automatically. BIRD itself comes from CZ.NIC's repository, so it updates when you choose. These commands show whether a newer 2.x release is available:
sudo apt-get update
apt-cache policy bird2 | sed -n '1,3p'
Then install it:
sudo apt-get install --only-upgrade bird2
If apt asks about /etc/bird/bird.conf ("A new version of configuration file ... is available, but the version installed currently has been locally modified"), choose keep the local version currently installed. That file is your configuration, and the first-boot router ID is in it. The upgrade restarts BIRD, so sessions re-establish. The file's root:bird 0640 ownership is registered with dpkg-statoverride, so upgrades keep it.
The apt preferences in /etc/apt/preferences.d/cloudimg-bird2 keep this instance on BIRD 2. BIRD 3 is a new major release, with multithreading and some configuration changes, and CZ.NIC publishes it as a separate bird3 package in its own repository. The preferences block it, so an upgrade never switches you over by accident. To evaluate BIRD 3, test a copy of your configuration on a separate instance first.
Step 12: 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 password, agreed with that peer. Enable TTL security for directly connected peers.
- Filter explicitly. Accept only the prefixes you expect from each peer and announce only your own; never
import allorexport allon an eBGP session. - Guard the control socket. It has no password, so whoever can open it controls the router. Keep the
birdgroup to trusted operators and keep the socket permissions as shipped. - Keep the OS and BIRD patched. Unattended security upgrades are on for the Ubuntu packages. Check for new BIRD 2.x releases with
apt-cache policy bird2aftersudo apt-get update.
Support and licensing
BIRD is developed by CZ.NIC and distributed under the GNU General Public License, version 2 or later. The licence and copyright notice are in /usr/share/doc/bird2/copyright, and CZ.NIC publishes the source at bird.nic.cz. cloudimg provides the packaging, the pinned signed repository, the first-boot router ID, the secure defaults and 24/7 support.
cloudimg is not affiliated with or endorsed by CZ.NIC. BIRD 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.