FRRouting (FRR) is an open source routing suite that runs BGP, OSPF, IS-IS and other protocols on Linux, using the kernel routing table to forward traffic. BGP is the protocol networks use to exchange routes on the Internet, and running it on a Linux server lets you announce your own IP prefix, receive routes from upstream providers and fail over between them. In this tutorial you will install FRR on Ubuntu 24.04, establish an eBGP session with an upstream provider, announce a prefix with strict filters, add a second upstream with traffic preferences and verify every step with vtysh.

Prerequisites

To follow this tutorial you need:

  • A server running Ubuntu 24.04 with a non-root user with sudo privileges, for example a CubePath VPS or bare metal server.
  • A BGP session agreed with your upstream provider, including their ASN, their peer IP address and the IP address on your side. For public announcements you need your own public ASN and an IP prefix registered to you with valid route objects (IRR and RPKI ROA).
  • Basic knowledge of IP addressing and routing.

If you do not have a real upstream yet, you can reproduce everything in a lab with two Linux VMs, each running FRR, using private ASNs (64512 to 65534).

This guide uses the following example values. Replace them with your own everywhere:

ItemExample value
Your ASN64512
Your prefix to announce203.0.113.0/24
Your IP on the link to provider A198.51.100.2/30 on eth0
Provider A peer IP and ASN198.51.100.1, AS 65010
Your IP on the link to provider B (Step 7)192.0.2.2/30 on eth1
Provider B peer IP and ASN (Step 7)192.0.2.1, AS 65020

Step 1 - Installing FRRouting from the official repository

Ubuntu ships an FRR package, but the FRR project maintains its own APT repository with the current stable release. Install the tools needed to add the repository:

sudo apt update
sudo apt install curl gnupg

Download the repository signing key into /etc/apt/keyrings:

sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://deb.frrouting.org/frr/keys.gpg -o /etc/apt/keyrings/frrouting.gpg

Add the frr-stable repository for your Ubuntu release:

echo "deb [signed-by=/etc/apt/keyrings/frrouting.gpg] https://deb.frrouting.org/frr $(. /etc/os-release && echo "$VERSION_CODENAME") frr-stable" | sudo tee /etc/apt/sources.list.d/frr.list

Install FRR and frr-pythontools, which provides the frr-reload.py script used to apply configuration changes without restarting:

sudo apt update
sudo apt install frr frr-pythontools

Check the installed version:

vtysh --version | head -1
vtysh version 10.x.x

Step 2 - Enabling the BGP daemon

FRR runs one daemon per protocol, and /etc/frr/daemons decides which ones start. zebra (which talks to the kernel) and staticd always start, but bgpd is disabled by default.

Open the file:

sudo nano /etc/frr/daemons

Change the bgpd line to:

bgpd=yes

Restart FRR so the new daemon starts:

sudo systemctl restart frr

Verify that bgpd is running:

sudo systemctl status frr --no-pager
● frr.service - FRRouting
     Loaded: loaded (/usr/lib/systemd/system/frr.service; enabled; preset: enabled)
     Active: active (running)
...
             ├─ ... /usr/lib/frr/zebra -d -F traditional -A 127.0.0.1 -s 90000000
             ├─ ... /usr/lib/frr/bgpd -d -F traditional -A 127.0.0.1
             └─ ... /usr/lib/frr/staticd -d -F traditional -A 127.0.0.1

Step 3 - Allowing BGP through the firewall

BGP runs over TCP port 179. Allow it only from your peer, not from the whole Internet:

sudo ufw allow from 198.51.100.1 to any port 179 proto tcp comment 'BGP provider A'

If UFW is not enabled yet, make sure SSH is allowed before enabling it:

sudo ufw allow OpenSSH
sudo ufw enable

Step 4 - Configuring the BGP session and filters

FRR stores the whole configuration in /etc/frr/frr.conf. Editing this file and reloading is easier to review and version than typing commands into vtysh one by one.

Two FRR defaults shape the configuration:

  • Since FRR 7.4, bgp ebgp-requires-policy is enabled: an eBGP session with no inbound and outbound route map accepts and sends nothing. This protects you from leaking routes by accident, so always define both route maps.
  • The network statement only announces a prefix that exists in the routing table. A static route to blackhole guarantees that it is always there, independently of interface state.

Open the configuration file:

sudo nano /etc/frr/frr.conf

Replace its contents with the following configuration:

frr defaults traditional
hostname router1
log syslog informational
service integrated-vtysh-config
!
ip route 203.0.113.0/24 blackhole
!
ip prefix-list MY-PREFIXES seq 10 permit 203.0.113.0/24
!
ip prefix-list UPSTREAM-IN seq 10 permit 0.0.0.0/0
!
route-map PROVIDER-A-OUT permit 10
 match ip address prefix-list MY-PREFIXES
exit
!
route-map PROVIDER-A-IN permit 10
 match ip address prefix-list UPSTREAM-IN
exit
!
router bgp 64512
 bgp router-id 198.51.100.2
 bgp log-neighbor-changes
 neighbor 198.51.100.1 remote-as 65010
 neighbor 198.51.100.1 description provider-a
 !
 address-family ipv4 unicast
  network 203.0.113.0/24
  neighbor 198.51.100.1 soft-reconfiguration inbound
  neighbor 198.51.100.1 route-map PROVIDER-A-IN in
  neighbor 198.51.100.1 route-map PROVIDER-A-OUT out
 exit-address-family
exit

What each part does:

  • ip route 203.0.113.0/24 blackhole installs the aggregate in the routing table. Traffic for addresses inside the prefix that are not assigned to a more specific route is discarded instead of looping back to the provider.
  • MY-PREFIXES with PROVIDER-A-OUT guarantee that you only ever announce your own prefix, even if you later learn other routes.
  • UPSTREAM-IN with PROVIDER-A-IN accept only a default route from the provider. That is the right choice for a server: a full Internet table needs more than 1 GB of RAM per session and is only useful when you have several upstreams and want per-destination path selection.
  • soft-reconfiguration inbound keeps a copy of everything the peer sends, before filtering, so you can inspect it later.

If your provider requires a TCP MD5 password for the session, add neighbor 198.51.100.1 password your_bgp_password inside router bgp.

Check the syntax before applying it:

sudo vtysh --dryrun -f /etc/frr/frr.conf

The command prints nothing when the file is valid. Apply the changes without restarting the daemons:

sudo systemctl reload frr

Step 5 - Verifying the session and the routes

Show the BGP summary:

sudo vtysh -c "show bgp ipv4 unicast summary"
BGP router identifier 198.51.100.2, local AS number 64512 VRF default vrf-id 0
...
Neighbor        V         AS   MsgRcvd   MsgSent   TblVer  InQ OutQ  Up/Down State/PfxRcd   PfxSnt Desc
198.51.100.1    4      65010        12        10        3    0    0 00:04:31            1        1 provider-a

A number in State/PfxRcd means the session is Established and shows how many prefixes you accepted after filtering. PfxSnt is the number of prefixes you announce. If you see Active, Connect or Idle instead, go to the Troubleshooting section.

Confirm that you announce only your prefix:

sudo vtysh -c "show bgp ipv4 unicast neighbors 198.51.100.1 advertised-routes"
   Network          Next Hop            Metric LocPrf Weight Path
*> 203.0.113.0/24   0.0.0.0                  0         32768 i

Total number of prefixes 1

Compare what the provider sends with what you accepted:

sudo vtysh -c "show bgp ipv4 unicast neighbors 198.51.100.1 received-routes"
sudo vtysh -c "show bgp ipv4 unicast neighbors 198.51.100.1 routes"

The first command lists everything received (thanks to soft-reconfiguration inbound); the second only the routes that passed PROVIDER-A-IN.

Finally, check that FRR installed the default route in the kernel:

ip route show proto bgp
default via 198.51.100.1 dev eth0 metric 20

From an external network, you can confirm the announcement is visible on the Internet with a public looking glass or a route collector such as RIPE RIS, searching for 203.0.113.0/24.

Step 6 - Tagging announcements with BGP communities

Communities are labels attached to routes. Many providers offer communities that change how they handle your announcement, such as prepending, not announcing to certain peers, or blackholing an address under attack. The values are provider specific, so check their documentation.

To tag your announcements to provider A, add a set community line to the outbound route map in /etc/frr/frr.conf:

route-map PROVIDER-A-OUT permit 10
 match ip address prefix-list MY-PREFIXES
 set community 64512:100 additive
exit

additive adds the community to any that the route already carries instead of replacing them. To act on communities that a peer sends you, define a list with bgp community-list standard NAME permit ASN:VALUE and use match community NAME in an inbound route map, together with the prefix list match so your inbound filter stays strict.

Reload and check the community on your own route:

sudo systemctl reload frr
sudo vtysh -c "show bgp ipv4 unicast 203.0.113.0/24"

The output includes a line such as Community: 64512:100.

Step 7 - Adding a second upstream provider

With two upstreams, you control which one is used for outgoing traffic with local-preference (higher wins), and you influence incoming traffic by making the backup path longer with AS path prepending.

First allow BGP from the second peer:

sudo ufw allow from 192.0.2.1 to any port 179 proto tcp comment 'BGP provider B'

Then edit /etc/frr/frr.conf. Replace the existing PROVIDER-A-IN route map with the version below, which sets a local preference, and add the two route maps for provider B. The outbound map to B prepends your ASN twice, so the Internet sees a longer path through B and prefers A for incoming traffic:

route-map PROVIDER-A-IN permit 10
 match ip address prefix-list UPSTREAM-IN
 set local-preference 200
exit
!
route-map PROVIDER-B-IN permit 10
 match ip address prefix-list UPSTREAM-IN
 set local-preference 100
exit
!
route-map PROVIDER-B-OUT permit 10
 match ip address prefix-list MY-PREFIXES
 set as-path prepend 64512 64512
exit

Inside the existing router bgp 64512 block, add the second neighbor next to the first one, and its settings inside the existing address-family ipv4 unicast section. The lines to add are:

router bgp 64512
 neighbor 192.0.2.1 remote-as 65020
 neighbor 192.0.2.1 description provider-b
 !
 address-family ipv4 unicast
  neighbor 192.0.2.1 soft-reconfiguration inbound
  neighbor 192.0.2.1 route-map PROVIDER-B-IN in
  neighbor 192.0.2.1 route-map PROVIDER-B-OUT out
 exit-address-family
exit

Validate and reload:

sudo vtysh --dryrun -f /etc/frr/frr.conf
sudo systemctl reload frr

Check which default route BGP selected:

sudo vtysh -c "show bgp ipv4 unicast 0.0.0.0/0"
Paths: (2 available, best #1, table default)
  65010
    198.51.100.1 from 198.51.100.1 (...)
      Origin IGP, localpref 200, valid, external, best (Local Pref)
  65020
    192.0.2.1 from 192.0.2.1 (...)
      Origin IGP, localpref 100, valid, external

Outgoing traffic uses provider A. If that session goes down, the route through provider B is installed automatically. Test it in a maintenance window by shutting down the session to A:

sudo vtysh -c "configure terminal" -c "router bgp 64512" -c "neighbor 198.51.100.1 shutdown"
ip route show proto bgp
default via 192.0.2.1 dev eth1 metric 20

Bring it back with the same command using no neighbor 198.51.100.1 shutdown. Changes made in vtysh like this are not saved to frr.conf unless you run write memory, so a reload restores the file's state.

Troubleshooting

The session stays in Active or Connect. There is no TCP connection. Check that you can reach the peer IP, that UFW allows port 179 from it, and that the provider has configured your side correctly:

ping -c 3 198.51.100.1
sudo ss -tnp | grep ':179'

The session is Established but PfxRcd or PfxSnt shows (Policy). No route map is applied in that direction, and bgp ebgp-requires-policy blocks everything. Apply both in and out route maps to the neighbor.

Your prefix is not announced. The network statement requires an exact match in the routing table. Check that the static route exists:

sudo vtysh -c "show ip route 203.0.113.0/24"

If the provider receives the route but does not propagate it, the cause is usually outside your server: missing IRR route objects, an RPKI ROA that does not match your ASN and prefix length, or a provider filter that has not been updated.

Configuration changes do not take effect. Read the FRR logs:

sudo journalctl -u frr -n 50 --no-pager

After changing a route map, the session keeps the old results until routes are re-evaluated. Trigger a soft refresh with sudo vtysh -c "clear bgp ipv4 unicast 198.51.100.1 soft", which does not reset the session.

Conclusion

You installed FRRouting from the official repository on Ubuntu 24.04, established an eBGP session with explicit inbound and outbound policy, announced your prefix, tagged it with communities and added a second upstream with local preference and AS path prepending for failover.

Next steps:

  • Add IPv6 with a separate address-family ipv6 unicast block and ipv6 prefix-list filters.
  • Enable BFD (bfdd) for sub-second failure detection on the BGP sessions.
  • Use OSPF with FRR for internal routing between your own routers, and keep BGP for the edge.