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

Isometric illustration of a signpost with two firewall units at its base, a road leading away and a sealed certificate leaning on the post.

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

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"]

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

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:

  1. Add the new addresses as aliases on the WAN CARP VIP.
  2. Clone every port forward to the new address, including port ranges, with the tool.
  3. Lower the DNS TTL to 300, flip each record, and write a "was X" comment on it.
  4. 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.
  5. Probe every published port on every public address from two external vantage points.
  6. 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.

Related