BIRD 2 BGP Configuration on a Linux VDS: Filters and Policy
A BIRD 2 BGP setup on a Linux VDS needs four things: a protocol bgp block with your neighbor details, an explicit export filter that announces only your own prefixes, an import filter that rejects junk routes, and a way to verify it with birdc. This guide walks through a complete, working configuration you can adapt, plus the mistakes that most often keep a session from coming up.
All addresses below come from documentation ranges (AS64496, 192.0.2.0/24, 198.51.100.0/24, 2001:db8::/32). Replace them with the values from your own provider and RIR records.
What you need before you start
- A Linux VDS with a public IP on the BGP peering subnet and root access.
- Your own ASN and IP block (example: AS64496 and 192.0.2.0/24).
- A valid RPKI ROA for the prefix and correct RIR/IRR objects. Most providers validate these before the session is enabled.
- The session parameters from your provider: neighbor IP, neighbor ASN, your local peering IP, and any password or multihop requirement.
On Nixcon's BGP-enabled VDS, the ASN and prefix ownership check against RPKI ROA and RIR/IRR data is automatic, and your ASN is added to the AS-NIXCON AS-SET. Create the ROA first so provisioning isn't delayed. Our network is AS40368, which is used as the upstream ASN in the examples.
Step 1: Install BIRD 2
On Debian or Ubuntu:
apt update
apt install bird2
bird --version # confirm 2.x
systemctl enable bird
The main config file is /etc/bird/bird.conf. Check your version: BIRD 1.x uses separate bird6 and different syntax, and the examples here won't work there.
Step 2: Put your prefix on the host
BGP only announces what you tell it to. The prefix usually lives on a dummy interface so the VDS answers on those addresses:
ip link add dummy0 type dummy
ip link set dummy0 up
ip addr add 192.0.2.10/24 dev dummy0
Make this persistent with netplan, systemd-networkd, or ifupdown, depending on your distribution. Otherwise it disappears on reboot.
Step 3: A complete BIRD 2 configuration
# /etc/bird/bird.conf
router id 198.51.100.2; # any unique IPv4 value; use your peering IP
log syslog all;
define MY_ASN = 64496;
define UPSTREAM_ASN = 40368;
# Prefixes we are allowed to originate
define MY_PREFIXES_V4 = [ 192.0.2.0/24 ];
# Bogons and reserved space (documentation ranges omitted
# here so the example prefix isn't blocked)
define BOGONS_V4 = [
0.0.0.0/8+, 10.0.0.0/8+, 100.64.0.0/10+, 127.0.0.0/8+,
169.254.0.0/16+, 172.16.0.0/12+, 192.168.0.0/16+,
198.18.0.0/15+, 224.0.0.0/4+, 240.0.0.0/4+
];
roa4 table roa_v4;
protocol device {}
# Originate our prefix
protocol static originate_v4 {
ipv4;
route 192.0.2.0/24 blackhole;
}
# Install learned routes into the kernel
protocol kernel {
ipv4 {
import none;
export all;
};
}
filter export_v4 {
if net ~ MY_PREFIXES_V4 then accept;
reject;
}
filter import_v4 {
if net ~ BOGONS_V4 then reject;
if net ~ MY_PREFIXES_V4 then reject; # never accept our own space
if net.len > 24 then reject; # too specific
if net.len < 8 && net.len != 0 then reject; # too broad
if bgp_path.len > 64 then reject;
if roa_check(roa_v4, net, bgp_path.last) = ROA_INVALID then reject;
accept;
}
protocol bgp nixcon_v4 {
description "Upstream AS40368 IPv4";
local 198.51.100.2 as MY_ASN;
neighbor 198.51.100.1 as UPSTREAM_ASN;
# password "from-your-provider"; # only if required
ipv4 {
next hop self;
import filter import_v4;
export filter export_v4;
import limit 1000 action restart; # raise for full table
};
}
The ROA check only works once an RPKI validator feeds the roa_v4 table (see below). Until then, comment out that line or every route comes back as unknown, which still passes, but you gain no protection.
Why the filters look the way they do
BIRD 2 follows RFC 8212 for eBGP: with no filter configured, it neither accepts nor announces anything. That is a safe default, and it explains many "session is up but nothing happens" reports. Be explicit.
Export policy
The export filter is the most important safety control. A leak of someone else's routes from your VDS can cause real damage. Rules to follow:
- Accept only prefixes in your own list, nothing else.
- Don't announce learned routes back to the upstream. The
net ~ MY_PREFIXES_V4test guarantees this. - Announce the exact prefix you hold. If you want more-specifics, list them deliberately, for example
192.0.2.0/25, and keep them within what your ROAmaxLengthallows.
Import policy
What you import depends on what you ordered. Providers typically offer a choice between Nixcon-only routes, a default route, or the full routing table. Adjust accordingly:
| Mode | Import filter approach | Suggested limit |
|---|---|---|
| Provider routes only | Accept all after bogon/length checks | Small, e.g. 1000 |
| Default route only | if net = 0.0.0.0/0 then accept; reject; |
1–2 routes |
| Full table | Full filter above (allow /8–/24) |
Size it to current table, with headroom |
Memory also matters. A full table needs a VDS with enough RAM, so check before enabling it.
Step 4: Add RPKI origin validation (optional but recommended)
Run a validator such as Routinator or StayRTR on the VDS or nearby, then point BIRD at it:
protocol rpki validator1 {
roa4 { table roa_v4; };
remote 127.0.0.1 port 3323;
retry keep 90;
refresh keep 900;
expire keep 172800;
}
Routes with an invalid origin are then dropped by the roa_check line in the import filter.
Step 5: IPv6
Add a second session over IPv6 with its own channel and filters:
define MY_PREFIXES_V6 = [ 2001:db8:1000::/48 ];
filter export_v6 {
if net ~ MY_PREFIXES_V6 then accept;
reject;
}
protocol bgp nixcon_v6 {
local 2001:db8:ffff::2 as MY_ASN;
neighbor 2001:db8:ffff::1 as UPSTREAM_ASN;
ipv6 {
import filter { if net.len > 48 then reject; accept; };
export filter export_v6;
};
}
Add a matching static route and an IPv6 bogon list in production. The /48 here is a documentation-style example; use your real allocation.
Step 6: Apply and verify
birdc configure check # syntax check
birdc configure # reload without dropping sessions
birdc show protocols # state should be "Established"
birdc show protocols all nixcon_v4
birdc show route export nixcon_v4 # what you announce
birdc show route protocol nixcon_v4 # what you learn
Then confirm from the outside. Nixcon's public looking glass supports BGP route, ping, and traceroute queries from Izmir, Frankfurt, and US locations, so you can see whether your prefix is visible and where it's reachable from.
Common mistakes and fixes
- Session stuck in Active or Connect. Check that TCP 179 isn't blocked by
iptables/nftables, that the local and neighbor IPs are right, and that the password matches. Usetcpdump -ni eth0 port 179to see whether packets arrive. - Established, but prefix not visible. Run
birdc show route export nixcon_v4. If it's empty, your static route is missing or the filter doesn't match the exact prefix. - Prefix rejected upstream. Usually a missing or wrong ROA, a
maxLengthmismatch, or an incomplete IRR route object. Fix the records, then wait for them to propagate. - All routes filtered. Use
import keep filtered on;in the channel, thenbirdc show route filteredto see which rule rejects them. - Traffic arrives but replies fail. If the VDS has multiple paths, check for asymmetric routing and
rp_filtersettings (sysctl net.ipv4.conf.all.rp_filter). - Session flaps after a config change. Use
birdc configure, not a service restart. Changingimport limitor filters may requirebirdc reload in allfor a soft refresh. - Router ID errors. BIRD 2 requires an IPv4-format ID even for IPv6-only setups.
When you need DDoS protection
An unprotected BGP announcement exposes your block directly to attack traffic. If your block is a likely target, as with game servers or hosting, consider the DDoS-protected announcement option, or DDoS protection and firewall in front of your services. Your BIRD configuration stays the same, so the same filter discipline applies.
Frequently asked questions
Do I need a full routing table on a BGP VDS?
Not usually. If you only need to announce your own prefix and reach the provider's network, provider-only routes or a default route are enough and use far less memory. A full table is useful when you want to make your own path decisions, but it needs more RAM and a larger import limit.
Why does BIRD 2 announce nothing after the session is Established?
BIRD 2 follows RFC 8212, so an eBGP session with no export filter announces nothing. Add an explicit export filter that accepts your prefixes, and make sure a static or direct route for the prefix exists in BIRD's table.
Should I install BGP-learned routes into the kernel?
Only if you need them for forwarding decisions. With a default-route setup you can keep your existing default gateway and export nothing learned. If you accept a full table, export it to the kernel with a kernel protocol and check the VDS has enough memory.
How do I check that my announcement is visible on the internet?
Run birdc show route export for your session, then query a public looking glass for your prefix. Nixcon's looking glass at lg.nixconllc.com offers BGP route, ping, and traceroute queries from several locations.