Announce your own /24 from an OPNsense pair: BGP without a router budget

In short. Get an ASN and a portable prefix from the registry. Peer from each firewall's own WAN address, never from the shared VIP. Add a blackhole route so the prefix is actually advertised. Filter strictly in both directions. Keep the ROA at /24. Cut over with a dual-run and verify from outside.
Why provider-assigned space hurt
Every service address was the transit provider's. Changing provider meant renumbering every forward, every DNS record and every certificate. Worse, the provider's addresses had hidden dependencies: an "unused" alias deleted during a cleanup turned out to be the next-hop for a routed block, and that block was dark for about 26 hours. No local check could have seen it.
Portable space means the addresses belong to the organisation. The provider becomes a path, not an identity.
What you need
- An ASN and a portable prefix. A /23 of IPv4 and a /32 of IPv6 from the regional registry, paid yearly. The IPv4 came from the transfer market; the IPv6 was an allocation.
- One transit provider that will take a BGP session from a small customer. Many will if you ask; some charge for it.
- A firewall that speaks BGP. OPNsense runs FRR. It is not a carrier router and does not need to be: it holds a default route from the provider and announces one prefix.
Session design
flowchart TB
T["Transit provider<br/>/29 link subnet, transit only"]
subgraph FW["OPNsense CARP pair, FRR"]
F1["fw01<br/>eBGP from its own WAN IP"]
F2["fw02<br/>eBGP from its own WAN IP"]
VIP(("WAN CARP VIP<br/>every public IP is an alias"))
end
N["Null0 blackhole for the /24<br/>so the network statement is advertised"]
T -- "eBGP session 1" --> F1
T -- "eBGP session 2" --> F2
N -.-> F1
N -.-> F2
F1 --- VIP
F2 --- VIP
VIP --> V["Internal VLANs, NAT and port forwards"]- The link subnet is transit only. The provider gives a /29: their gateway, fw01's WAN address, fw02's WAN address. Nothing is ever served from it.
- Each firewall holds its own eBGP session from its own WAN address. The CARP VIP is not a BGP speaker. When the master fails, the backup's session is already up and the prefix is already announced from it.
- A blackhole route to Null0 for the prefix on each firewall. FRR only advertises a
networkstatement if the prefix is in the routing table, and nothing inside the estate is a /24 route. The blackhole puts it there; more-specific internal routes still win. - Strict prefix lists. Outbound permits exactly the prefix and nothing else. Inbound accepts a default route and nothing else. The default is "announce nothing", and the lists are in the repository.
- Public addresses are aliases on the single WAN CARP VIP, so they fail over as one group. The HA post explains why that is one VIP, not one per address.
Regions and the /24 rule
The global routing table filters anything longer than a /24. A site inside a region can use a /26 of the regional /24, but that /26 only works inside one provider's network. Both sites in a region announce the whole /24, and the link between them is sized for the worst case where one site carries the other's traffic.
Two sites that share a rack, a transit provider and a power feed are one physical site with two logical identities. The decision record says so, and says what budget would change it.
RPKI, done once
- One ROA for the IPv4 /24 with maxLength 24. Never loosen it. If a more-specific ever needs announcing, it gets its own ROA.
- One ROA for the IPv6 /32 with maxLength 48 covers every site's /48.
- Reverse DNS for legacy address space is administered by a different registry than the one that issued the prefix, and it takes weeks. Start it on day one.
IPv6: registered, not announced
The IPv6 prefix is held but not announced yet, because there is no IPv6 firewall posture yet. There is no NAT in IPv6 to hide behind; every host with a global address is reachable unless a rule says otherwise. That posture is a project of its own and is not something to switch on in an afternoon.
Cutting over with a dual-run
Moving services from provider addresses to portable ones was additive:
- Add the new addresses as aliases on the WAN CARP VIP.
- Clone every port forward to the new address, including port ranges, with the tool.
- Lower the DNS TTL to 300, flip each record, and write a "was X" comment on it.
- Verify three ways: the authoritative nameserver over TCP, a public resolver over DoH, and an HTTPS request that checks the certificate and that the connection actually landed on the new address.
- Probe every published port on every public address from two external vantage points.
- Only then retire the old addresses, with a reference guard across NAT, filter and BGP.
The laptop running the checks served stale answers from its local resolver for most of an hour. Verification now goes around the local resolver, every time.
FAQ
Is OPNsense really enough as a BGP speaker? For one prefix and one or two upstreams, yes. It is a default route and an announcement. The moment you need full tables or route reflectors, you need a router.
What about two transit providers? That is the target: two independent uplinks per site, each on its own switch port, both sessions on both firewalls. The configuration is the same with a second neighbour and a local-preference.
How much does this cost? A yearly registry fee, a one-off transfer-market price for the IPv4 block, and whatever the provider charges for a BGP session. Against that, no renumbering project ever again.