OSPF on Linux Part 3: Installing and Configuring OSPF with FRR

Part 2 covered the architecture: zebra broking routes to the kernel, ospfd as an independent protocol daemon, vtysh as the shell over both. This post installs it and brings up a first, minimal OSPF configuration, verified against a real FRR package rather than written from documentation alone.

Installing the package

On Debian and Ubuntu, FRR is a distribution package:

apt install frr frr-pythontools

frr-pythontools isn’t optional filler, it carries frr-reload.py, the script that lets you push a new config file into a running FRR instance as a diff rather than a full restart, which matters the first time you need to change one OSPF area without dropping every adjacency on the box.

The package’s postinst does something worth knowing about before it happens rather than after: it creates a dedicated frr system user and group, and an frrvty group, and every daemon binary is compiled to run as that user and refuses to start any other way. This isn’t a packaging convenience you can skip, it’s compiled in. Pulling the raw binary out of the .deb confirms it directly, zebra -v reports its own build configuration on startup:

configured with:
  ...
  '--enable-user=frr' '--enable-group=frr' '--enable-vty-group=frrvty'
  '--enable-configfile-mask=0640' '--enable-logfile-mask=0640'

Running any FRR daemon by hand outside the package’s user setup fails immediately with privs_init: could not lookup user frr, which is the correct failure mode, not a bug to work around. Let the package own that part.

One more thing worth checking before relying on apt install frr: what version you actually get. Ubuntu 22.04 LTS ships FRR 8.1 through its default archive, which lags noticeably behind FRR’s own upstream release pace, FRR is on 10.7.0 as of July 2026. For a lab this doesn’t matter much. For production, FRR publishes its own apt repository at deb.frrouting.org specifically so LTS users aren’t stuck years behind on a project that still releases monthly.

Turning a daemon on

/etc/frr/daemons is the actual on/off switch described in Part 2, and it ships with everything disabled:

bgpd=no
ospfd=no
ospf6d=no
...

Flip the one that matters:

ospfd=yes

zebra and staticd are always started regardless of this file; everything else is opt-in per line. This is also where per-daemon startup flags live, ospfd_options=" -A 127.0.0.1" binds ospfd’s vty socket to localhost only, which is the packaged default and the right one, OSPF’s own vty has no authentication of its own beyond whatever restricts access to the box.

vtysh_enable=yes, also set by default, is what lets vtysh see and control every running daemon as one session rather than requiring a separate telnet localhost <port> per daemon the way early Zebra deployments worked.

Starting it

systemctl enable --now frr

The shipped unit is a forking service with a real watchdog, not a bare ExecStart:

[Service]
Type=forking
WatchdogSec=60s
RestartSec=5
Restart=on-abnormal
ExecStart=/usr/lib/frr/frrinit.sh start
ExecStop=/usr/lib/frr/frrinit.sh stop
ExecReload=/usr/lib/frr/frrinit.sh reload

frrinit.sh reads /etc/frr/daemons, starts zebra first, then every daemon marked yes, and hands control to watchfrr, which is the process the watchdog actually supervises, restarting any individual daemon that dies without restarting the ones that didn’t.

First OSPF config

vtysh drops into the same style of context-based CLI as Cisco IOS:

vtysh

LAB-LNX# configure terminal
LAB-LNX(config)# router ospf
LAB-LNX(config-router)# ospf router-id 10.0.0.4
LAB-LNX(config-router)# network 10.0.0.12/30 area 0.0.0.0
LAB-LNX(config-router)# exit
LAB-LNX(config)# end
LAB-LNX# write memory

Two things here are easy to get wrong coming from Cisco specifically. FRR’s network statement takes a plain prefix, 10.0.0.12/30, not a wildcard mask the way IOS-XE’s network 10.0.0.0 0.0.0.3 area 0 does, so the CORE-CSR config from OSPF Deep Dive Part 7 doesn’t translate literally, the intent does, the syntax doesn’t. And write memory is what persists the running config to /etc/frr/frr.conf, the service integrated-vtysh-config line in /etc/frr/vtysh.conf is what makes that one shared file the target for every daemon’s config rather than each daemon getting its own file, which is the default and the setting this series assumes throughout.

That’s a router with an OSPF process and one interface’s worth of area membership, the same shape of minimal config CORE-CSR, BRANCH-FGT, and EDGE-MX each started from in OSPF Deep Dive Parts 7 through 9. Part 4 puts it on the wire against that actual lab.