Networking on kldload
kldload systems use NetworkManager for network configuration across all supported distros. This guide starts with the basics — static IPs, bridges, VLANs, bonds, firewalls — then takes you into the modern networking stack: VXLAN overlays, BGP routing, eBPF dataplane programming, and multi-cloud connectivity. The old boundaries between "local network" and "cloud network" don't exist anymore. A packet on your homelab ZFS server and a packet in AWS traverse the same logical fabric if you build it that way.
How networking actually changed. Ten years ago, networking meant physical cables, physical switches, and a VLAN for each purpose. Your servers lived in one building. Your network ended at the building's edge. The firewall was a box in a rack.
That world is gone. Today a "network" is a software construct that can span a rack, a continent, or five cloud providers. Your containers talk to each other over virtual switches that exist entirely in kernel memory. Your edge nodes announce BGP routes to cloud routers. Your firewall rules are eBPF programs compiled and loaded at boot. The physical wire is just a transport — the network is defined in code.
The boundaries that disappeared: LAN vs WAN (WireGuard makes every link encrypted and flat), physical vs virtual (VXLAN puts 16 million virtual networks on one wire), hardware vs software firewalls (eBPF/XDP filters packets at line rate in the kernel), on-prem vs cloud (BGP peers with AWS/GCP the same way it peers with your rack switch). If you learn these four technologies, you can build any network topology that exists in production today.
This page teaches all of them. Start with the basics if you're new. Skip to VXLAN/BGP/eBPF if you're not.
Check current state
# Show all connections
nmcli connection show
# Show device status
nmcli device status
# Detailed info on a connection
nmcli connection show "Wired connection 1"
# IP addresses
ip addr show
nmcli connection show and read it. Most broken-network tickets start with someone modifying a connection they didn't identify first. NM names connections by detection order — "Wired connection 1" is just a label, not a guarantee. If you plugged in two NICs, "Wired connection 1" might be your management interface or your storage interface. Know which is which before you touch it.Static IP
Using nmcli
# Set static IP on the primary interface
nmcli connection modify "Wired connection 1" \
ipv4.method manual \
ipv4.addresses 10.100.10.50/24 \
ipv4.gateway 10.100.10.1 \
ipv4.dns "1.1.1.1 8.8.8.8"
# Apply
nmcli connection up "Wired connection 1"
Verify
ip addr show
ip route show
cat /etc/resolv.conf
Switch back to DHCP
nmcli connection modify "Wired connection 1" \
ipv4.method auto \
ipv4.addresses "" \
ipv4.gateway "" \
ipv4.dns ""
nmcli connection up "Wired connection 1"
Network bridge (for KVM VMs)
br0, not eth0. If you skip this and use the default NAT network, your VMs live in an invisible subnet that nothing outside the host can reach. Every production KVM deployment uses bridges. It's not optional — it's how VMs become real citizens on your network.VMs need a bridge interface to connect to the physical LAN:
# Create a bridge
nmcli connection add type bridge ifname br0 con-name br0 \
ipv4.method manual \
ipv4.addresses 10.100.10.50/24 \
ipv4.gateway 10.100.10.1 \
ipv4.dns "1.1.1.1"
# Attach the physical interface to the bridge
nmcli connection add type bridge-slave ifname eth0 master br0
# Bring down the old connection and bring up the bridge
nmcli connection down "Wired connection 1"
nmcli connection up br0
After this, your host gets its IP from
br0and VMs can bridge througheth0to the LAN.
Verify
nmcli connection show
bridge link show
ip addr show br0
VLAN tagging
# Create a VLAN interface (VLAN 100 on eth0)
nmcli connection add type vlan ifname eth0.100 dev eth0 id 100 \
ipv4.method manual \
ipv4.addresses 10.100.100.10/24
nmcli connection up vlan-eth0.100
VLAN on a bridge (for VMs on a specific VLAN)
# Create VLAN interface
nmcli connection add type vlan ifname eth0.200 dev eth0 id 200
# Create bridge on that VLAN
nmcli connection add type bridge ifname br-vlan200 con-name br-vlan200 \
ipv4.method manual \
ipv4.addresses 10.100.200.1/24
# Attach VLAN to bridge
nmcli connection add type bridge-slave ifname eth0.200 master br-vlan200
nmcli connection up br-vlan200
Link aggregation (bonding)
active-backup means if one cable gets yanked or a switch port dies, traffic fails over in milliseconds with zero config on the switch side. LACP (802.3ad) gives you aggregated bandwidth but needs the switch to participate — your switch admin has to configure a LAG group. For a ZFS storage server doing replication over the network, active-backup is the sane default: you want the replication to never stop, and you rarely saturate a single 10GbE link with zfs send anyway.Combine multiple NICs for redundancy or throughput:
# Create a bond (active-backup mode for redundancy)
nmcli connection add type bond ifname bond0 con-name bond0 \
bond.options "mode=active-backup,miimon=100" \
ipv4.method manual \
ipv4.addresses 10.100.10.50/24 \
ipv4.gateway 10.100.10.1
# Add slave interfaces
nmcli connection add type bond-slave ifname eth0 master bond0
nmcli connection add type bond-slave ifname eth1 master bond0
nmcli connection up bond0
Bond modes: - active-backup — one active NIC, failover
to the other (no switch config needed) - 802.3ad (LACP) —
aggregated throughput (requires switch support) -
balance-rr — round-robin (basic load balancing)
Firewall
wg0 in the trusted zone (firewalld) or explicitly accept UDP on your WG port (nftables). WireGuard authenticates at the crypto layer, so firewalling it is redundant — but you still need the port open for the initial handshake.CentOS/RHEL (firewalld)
# Check status
firewall-cmd --state
# List open ports
firewall-cmd --list-all
# Open a port
firewall-cmd --permanent --add-port=8080/tcp
firewall-cmd --reload
# Open a service
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
# Allow traffic on a WireGuard interface
firewall-cmd --permanent --zone=trusted --add-interface=wg0
firewall-cmd --reload
# Remove a rule
firewall-cmd --permanent --remove-port=8080/tcp
firewall-cmd --reload
Debian (nftables)
# Check status
nft list ruleset
# Open port 8080
nft add rule inet filter input tcp dport 8080 accept
# Make persistent
cat > /etc/nftables.d/kldload-custom.nft << 'EOF'
table inet filter {
chain input {
tcp dport 8080 accept
tcp dport { 80, 443 } accept
udp dport { 51820, 51821, 51822, 51823 } accept # WireGuard
}
}
EOF
systemctl reload nftables
Cross-distro shortcut
# Both distros: allow SSH, HTTP, HTTPS, WireGuard
for port in 22/tcp 80/tcp 443/tcp 51820/udp; do
if command -v firewall-cmd &>/dev/null; then
firewall-cmd --permanent --add-port="$port"
fi
done
firewall-cmd --reload 2>/dev/null || true
DNS configuration
1.1.1.1 but not google.com? That's DNS, not networking. On kldload, NM owns /etc/resolv.conf — don't hand-edit it, it gets overwritten on the next connection cycle. Always set DNS through nmcli connection modify. If you need split DNS (different resolvers for internal vs external), set ipv4.dns-search for your internal domain and let systemd-resolved route queries.# Set DNS servers via NetworkManager
nmcli connection modify "Wired connection 1" ipv4.dns "1.1.1.1 8.8.8.8"
nmcli connection up "Wired connection 1"
# Set search domain
nmcli connection modify "Wired connection 1" ipv4.dns-search "infra.local"
nmcli connection up "Wired connection 1"
# Verify
resolvectl status
IP forwarding (for routing/NAT)
kwg, but if you're doing manual networking, you'll need to flip it yourself. The sysctl.d drop-in survives reboots; the bare sysctl -w does not.Enable if this node acts as a router, VPN gateway, or container host:
# Enable immediately
sysctl -w net.ipv4.ip_forward=1
# Make persistent
echo "net.ipv4.ip_forward = 1" > /etc/sysctl.d/99-forwarding.conf
sysctl --system
NAT masquerade (share internet from one interface to another)
# CentOS/RHEL
firewall-cmd --permanent --add-masquerade
firewall-cmd --reload
# Debian (nftables)
nft add table nat
nft add chain nat postrouting { type nat hook postrouting priority 100 \; }
nft add rule nat postrouting oifname "eth0" masquerade
Troubleshooting
ip link says the interface is DOWN, nothing above matters. If ping 1.1.1.1 works but ping google.com doesn't, stop looking at routes and look at DNS. Most people skip straight to Layer 7 and waste an hour.# Can't reach the network
ip link show # is the interface UP?
ip addr show # does it have an IP?
ip route show # is the gateway set?
ping 1.1.1.1 # can you reach the internet?
ping google.com # DNS working?
# NetworkManager logs
journalctl -u NetworkManager --since "10 minutes ago"
# Reset a broken connection
nmcli connection delete "Wired connection 1"
nmcli device wifi connect SSID password PASS # WiFi
# or let NM auto-configure:
nmcli device connect eth0
Beyond the basics: modern networking
VXLAN overlay networks
VXLAN (Virtual Extensible LAN) wraps Layer 2 Ethernet frames inside UDP packets. This means you can create a virtual Layer 2 network between machines that are on completely different physical networks — different subnets, different data centers, different continents. The machines think they're on the same switch.
Concept: what's happening on the wire
# Normal Ethernet frame:
# [Eth Header] [IP Header] [TCP/UDP] [Payload]
# VXLAN-encapsulated frame:
# [Outer Eth] [Outer IP] [Outer UDP:4789] [VXLAN Header (VNI)] [Inner Eth] [Inner IP] [Inner TCP/UDP] [Payload]
# ^-- physical network sees this ^-- virtual network sees this
# The outer headers route the packet across the physical network.
# The inner headers are the "real" packet — completely untouched.
# The VNI in the VXLAN header says which virtual network this belongs to.
Example: connect two machines on different subnets
Machine A is on 192.168.1.0/24. Machine B is on 10.0.5.0/24.
They can't talk directly. VXLAN makes them neighbors on 172.16.0.0/24:
# ── Machine A (physical IP: 192.168.1.10) ──
# Create a VXLAN interface
# VNI 100 = our virtual network ID
# remote = Machine B's physical IP
# dstport 4789 = standard VXLAN UDP port
ip link add vxlan100 type vxlan \
id 100 \
remote 10.0.5.20 \
dstport 4789 \
dev eth0
# Give it an IP on the overlay network
ip addr add 172.16.0.1/24 dev vxlan100
ip link set vxlan100 up
# ── Machine B (physical IP: 10.0.5.20) ──
ip link add vxlan100 type vxlan \
id 100 \
remote 192.168.1.10 \
dstport 4789 \
dev eth0
ip addr add 172.16.0.2/24 dev vxlan100
ip link set vxlan100 up
# ── Test it ──
# From Machine A:
ping 172.16.0.2 # reaches Machine B over the overlay
# What's actually on the wire (tcpdump on the physical interface):
# 192.168.1.10:random -> 10.0.5.20:4789 UDP (VXLAN VNI 100)
# inside: 172.16.0.1 -> 172.16.0.2 ICMP echo request
VXLAN with multiple peers (multicast or head-end replication)
# For more than two peers, use multicast group instead of "remote":
ip link add vxlan100 type vxlan \
id 100 \
group 239.1.1.1 \
dstport 4789 \
dev eth0
ip addr add 172.16.0.1/24 dev vxlan100
ip link set vxlan100 up
# Every machine that joins VNI 100 with group 239.1.1.1
# automatically discovers all other peers via multicast.
# No need to list every remote IP.
VXLAN + bridge (give VMs an overlay network)
# Create the VXLAN tunnel
ip link add vxlan100 type vxlan id 100 remote 10.0.5.20 dstport 4789 dev eth0
ip link set vxlan100 up
# Create a bridge and attach the VXLAN
ip link add br-overlay type bridge
ip link set vxlan100 master br-overlay
ip link set br-overlay up
# Now attach VM tap interfaces to br-overlay.
# VMs on Machine A and Machine B share the same Layer 2 segment
# even though the physical machines are on different networks.
# VM migration between sites? Just move the VM — the network follows.
Make VXLAN survive reboots (NetworkManager)
# NM can manage VXLAN interfaces natively:
nmcli connection add type vxlan \
ifname vxlan100 \
con-name vxlan100 \
vxlan.id 100 \
vxlan.remote 10.0.5.20 \
vxlan.destination-port 4789 \
vxlan.parent eth0 \
ipv4.method manual \
ipv4.addresses 172.16.0.1/24
nmcli connection up vxlan100
VXLAN over WireGuard (encrypted overlay)
# Step 1: WireGuard tunnel between sites (already encrypted)
# Site A: wg0 = 10.200.0.1/24
# Site B: wg0 = 10.200.0.2/24
# (See the WireGuard Basics tutorial for setup)
# Step 2: VXLAN over the WireGuard tunnel
# On Site A:
ip link add vxlan100 type vxlan \
id 100 \
remote 10.200.0.2 \
dstport 4789 \
dev wg0 # <-- runs over WireGuard, not eth0
ip addr add 172.16.0.1/24 dev vxlan100
ip link set vxlan100 up
# On Site B:
ip link add vxlan100 type vxlan \
id 100 \
remote 10.200.0.1 \
dstport 4789 \
dev wg0
ip addr add 172.16.0.2/24 dev vxlan100
ip link set vxlan100 up
# Result: 172.16.0.0/24 is a flat Layer 2 network spanning both sites,
# fully encrypted by WireGuard. VMs, containers, ZFS replication —
# anything that needs Layer 2 adjacency works as if both racks
# were in the same room.
BGP: the routing protocol that runs the internet
BGP (Border Gateway Protocol) is how routers on the internet decide where to send packets. Every ISP, every cloud provider, every CDN uses BGP. The reason it matters to you: your Linux box can speak BGP. That means your kldload server can announce routes to cloud routers, receive routes from other sites, and participate in the same routing fabric as AWS and Google. No proprietary hardware. No expensive licenses. Just FRRouting (FRR), which is open source and runs on every distro kldload supports.
Install FRRouting
# CentOS/RHEL/Rocky/Fedora
dnf install -y frr
# Debian/Ubuntu
apt install -y frr
# Enable the BGP daemon
sed -i 's/^bgpd=no/bgpd=yes/' /etc/frr/daemons
systemctl enable --now frr
Example: announce your network to a peer
Your kldload server has network 10.100.10.0/24. You want to
announce it to a router at 10.100.10.1 (your upstream router or
cloud interconnect). Your ASN is 65001 (private range), the peer's is 65000.
# Enter FRR's shell
vtysh
# Configure BGP
configure terminal
router bgp 65001
bgp router-id 10.100.10.50
neighbor 10.100.10.1 remote-as 65000
address-family ipv4 unicast
network 10.100.10.0/24
neighbor 10.100.10.1 activate
exit-address-family
end
write memory
# Verify
vtysh -c "show bgp summary"
vtysh -c "show bgp ipv4 unicast"
vtysh -c "show ip route"
Example: multi-site BGP over WireGuard
Two kldload sites connected by WireGuard. Each site announces its local networks to the other via BGP. Routes update automatically — if a subnet appears or disappears, BGP propagates the change.
# ── Site A (WG IP: 10.200.0.1, local network: 10.100.10.0/24) ──
vtysh
configure terminal
router bgp 65001
bgp router-id 10.200.0.1
neighbor 10.200.0.2 remote-as 65002 # Site B over WireGuard
address-family ipv4 unicast
network 10.100.10.0/24 # announce Site A's network
neighbor 10.200.0.2 activate
exit-address-family
end
write memory
# ── Site B (WG IP: 10.200.0.2, local network: 10.100.20.0/24) ──
vtysh
configure terminal
router bgp 65002
bgp router-id 10.200.0.2
neighbor 10.200.0.1 remote-as 65001 # Site A over WireGuard
address-family ipv4 unicast
network 10.100.20.0/24 # announce Site B's network
neighbor 10.200.0.1 activate
exit-address-family
end
write memory
# ── Result ──
# On Site A:
ip route
# ...
# 10.100.20.0/24 via 10.200.0.2 dev wg0 proto bgp <-- learned from Site B
# On Site B:
ip route
# ...
# 10.100.10.0/24 via 10.200.0.1 dev wg0 proto bgp <-- learned from Site A
# Add a third site? It learns all routes automatically.
# No manual route tables. No static routes to maintain. BGP does it.
BGP route filtering (don't leak everything)
# Only accept specific prefixes from a neighbor:
vtysh
configure terminal
ip prefix-list ALLOWED seq 10 permit 10.100.0.0/16 le 24
ip prefix-list ALLOWED seq 100 deny any
router bgp 65001
address-family ipv4 unicast
neighbor 10.200.0.2 prefix-list ALLOWED in
exit-address-family
end
write memory
# This says: accept any /24 (or shorter) within 10.100.0.0/16.
# Reject everything else. Never accept a default route you didn't ask for.
# Never announce a route you don't own. BGP without filters is a gun
# pointed at your own network.
eBPF: programmable networking in the kernel
eBPF (extended Berkeley Packet Filter) lets you run custom programs inside the Linux kernel — at the network layer, the syscall layer, the scheduler, anywhere. For networking, this means you can write packet processing logic that runs at near-hardware speed without leaving kernel space. No copying packets to userland. No context switches. Just your code, running on every packet, at line rate.
The three eBPF network hooks you need to know
XDP (eXpress Data Path)
Runs on the NIC driver, before the kernel even allocates an sk_buff. This is as fast as software gets — process or drop packets before the kernel network stack sees them. Used for DDoS mitigation, load balancing, packet filtering.
TC (Traffic Control)
Runs after the kernel builds the sk_buff but before routing. You can inspect, modify, redirect, or drop packets. More features than XDP (you can see full packet metadata) but slightly slower. Used for policy enforcement, traffic shaping, service mesh.
Socket-level (sk_msg, sockops)
Runs on socket operations — connect, send, receive. You can redirect traffic between sockets without it ever hitting the network stack. Used for service mesh acceleration (Cilium) and transparent proxying.
Example: drop all traffic from a specific IP (XDP)
# Install bpftool and dependencies
# CentOS/RHEL/Rocky/Fedora:
dnf install -y bpftool clang llvm
# Debian/Ubuntu:
apt install -y bpftool clang llvm linux-headers-$(uname -r)
# Write a minimal XDP program (drop_ip.c)
cat > /tmp/drop_ip.c << 'XDPEOF'
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <bpf/bpf_helpers.h>
// Drop all packets from 192.168.1.100
#define BLOCKED_IP 0x6401A8C0 // 192.168.1.100 in network byte order
SEC("xdp")
int xdp_drop_ip(struct xdp_md *ctx) {
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return XDP_PASS;
if (eth->h_proto != __constant_htons(ETH_P_IP))
return XDP_PASS;
struct iphdr *ip = (void *)(eth + 1);
if ((void *)(ip + 1) > data_end)
return XDP_PASS;
if (ip->saddr == BLOCKED_IP)
return XDP_DROP; // silently drop — never reaches the kernel
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";
XDPEOF
# Compile and load
clang -O2 -g -target bpf -c /tmp/drop_ip.c -o /tmp/drop_ip.o
# Attach to eth0
ip link set dev eth0 xdpgeneric obj /tmp/drop_ip.o sec xdp
# Verify it's loaded
ip link show eth0
# eth0: ... xdpgeneric ...
# Check stats
bpftool prog list
bpftool prog show id <PROG_ID>
# Remove when done
ip link set dev eth0 xdpgeneric off
Example: count packets per source IP (TC with maps)
# This uses a BPF map — shared memory between the eBPF program
# and userland tools. The program counts; you read the counts.
cat > /tmp/pkt_count.c << 'TCEOF'
#include <linux/bpf.h>
#include <linux/pkt_cls.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <bpf/bpf_helpers.h>
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 10240);
__type(key, __u32); // source IP
__type(value, __u64); // packet count
} pkt_count SEC(".maps");
SEC("tc")
int count_packets(struct __sk_buff *skb) {
void *data = (void *)(long)skb->data;
void *data_end = (void *)(long)skb->data_end;
struct ethhdr *eth = data;
if ((void *)(eth + 1) > data_end)
return TC_ACT_OK;
if (eth->h_proto != __constant_htons(ETH_P_IP))
return TC_ACT_OK;
struct iphdr *ip = (void *)(eth + 1);
if ((void *)(ip + 1) > data_end)
return TC_ACT_OK;
__u32 src = ip->saddr;
__u64 *count = bpf_map_lookup_elem(&pkt_count, &src);
if (count) {
__sync_fetch_and_add(count, 1);
} else {
__u64 init = 1;
bpf_map_update_elem(&pkt_count, &src, &init, BPF_ANY);
}
return TC_ACT_OK; // pass all traffic, just count
}
char _license[] SEC("license") = "GPL";
TCEOF
# Compile
clang -O2 -g -target bpf -c /tmp/pkt_count.c -o /tmp/pkt_count.o
# Attach to eth0 ingress
tc qdisc add dev eth0 clsact
tc filter add dev eth0 ingress bpf da obj /tmp/pkt_count.o sec tc
# Read the map — see packet counts per source IP
bpftool map dump name pkt_count
# key: 0a 64 0a 01 value: 00 00 00 00 00 00 03 e8 <-- 10.100.10.1: 1000 packets
# key: 0a 64 0a 32 value: 00 00 00 00 00 00 00 2a <-- 10.100.10.50: 42 packets
# Remove
tc filter del dev eth0 ingress
tc qdisc del dev eth0 clsact
Practical eBPF without writing C (bcc and bpftrace)
# Install bcc tools (pre-built eBPF programs for common tasks)
# CentOS/RHEL/Rocky/Fedora:
dnf install -y bcc-tools
# Debian/Ubuntu:
apt install -y bpfcc-tools
# ── Examples ──
# Watch TCP connections in real time (who's connecting to what)
/usr/share/bcc/tools/tcpconnect
# Watch TCP accept events (who's connecting to your server)
/usr/share/bcc/tools/tcpaccept
# Trace DNS queries
/usr/share/bcc/tools/tcptracer
# Measure TCP retransmits (network quality indicator)
/usr/share/bcc/tools/tcpretrans
# Watch packet drops with stack traces (why is the kernel dropping packets?)
/usr/share/bcc/tools/tcpdrop
# bpftrace: one-liners for network diagnostics
# Count packets per destination port
bpftrace -e 'kprobe:tcp_v4_connect { @[arg2] = count(); }'
# Histogram of TCP send sizes
bpftrace -e 'kprobe:tcp_sendmsg { @bytes = hist(arg2); }'
# Watch all DNS lookups with latency
bpftrace -e 'tracepoint:net:net_dev_xmit { @[comm] = count(); }'
tcpconnect shows you every outbound TCP connection your server makes — in real time, with PID and process name. tcpretrans shows retransmits, which is how you find network quality problems that ping won't show. tcpdrop shows kernel packet drops with full stack traces — that's how you find out whether your firewall, conntrack, or socket buffer is the bottleneck. These tools replace weeks of strace/tcpdump debugging with one command.Multi-cloud routing: putting it all together
Here's the real payoff. Combine WireGuard (encrypted transport) + VXLAN (virtual Layer 2) + BGP (dynamic routing) and you get a multi-cloud network fabric that works the same whether your nodes are in a rack, in AWS, in GCP, or in your basement.
Architecture: three sites, full mesh
# Site A: On-prem kldload (ASN 65001)
# Physical: 192.168.1.0/24
# WireGuard: 10.200.0.1
# Overlay: 172.16.0.0/24
#
# Site B: AWS EC2 kldload instance (ASN 65002)
# Physical: 10.0.1.0/24 (VPC)
# WireGuard: 10.200.0.2
# Overlay: 172.16.1.0/24
#
# Site C: Hetzner bare metal kldload (ASN 65003)
# Physical: 88.99.x.x
# WireGuard: 10.200.0.3
# Overlay: 172.16.2.0/24
# Layer 1: WireGuard mesh (encrypted transport)
# Each site has WG peers to the other two sites.
# This gives you encrypted IP connectivity between all three.
# Layer 2: VXLAN over WireGuard (virtual Layer 2)
# VMs on all three sites share the same broadcast domain.
# A VM on Site A can ARP for a VM on Site C.
# Layer 3: BGP over WireGuard (dynamic routing)
# Each site announces its local + overlay networks.
# Add a fourth site? It learns all routes in seconds.
Concrete setup: Site A (on-prem)
# ── WireGuard (transport layer) ──
# /etc/wireguard/wg0.conf is already configured with peers
# Site A: 10.200.0.1, Site B: 10.200.0.2, Site C: 10.200.0.3
wg-quick up wg0
# ── VXLAN over WireGuard (overlay layer) ──
# Create VXLAN tunnel to each peer over WireGuard
ip link add vxlan100 type vxlan id 100 local 10.200.0.1 dstport 4789 nolearning
# Populate FDB (forwarding database) for each remote peer
bridge fdb append 00:00:00:00:00:00 dev vxlan100 dst 10.200.0.2
bridge fdb append 00:00:00:00:00:00 dev vxlan100 dst 10.200.0.3
# Bridge the VXLAN with local VMs
ip link add br-overlay type bridge
ip link set vxlan100 master br-overlay
ip addr add 172.16.0.1/24 dev br-overlay
ip link set vxlan100 up
ip link set br-overlay up
# ── BGP (routing layer) ──
vtysh
configure terminal
router bgp 65001
bgp router-id 10.200.0.1
neighbor 10.200.0.2 remote-as 65002
neighbor 10.200.0.3 remote-as 65003
address-family ipv4 unicast
network 192.168.1.0/24 # physical network
network 172.16.0.0/24 # overlay network
neighbor 10.200.0.2 activate
neighbor 10.200.0.3 activate
# Safety: only accept /16 or longer from peers
neighbor 10.200.0.2 prefix-list SAFE in
neighbor 10.200.0.3 prefix-list SAFE in
exit-address-family
ip prefix-list SAFE seq 10 permit 10.0.0.0/8 le 24
ip prefix-list SAFE seq 20 permit 172.16.0.0/12 le 24
ip prefix-list SAFE seq 30 permit 192.168.0.0/16 le 24
ip prefix-list SAFE seq 100 deny any
end
write memory
# ── Verify the full stack ──
# WireGuard: are the tunnels up?
wg show
# VXLAN: is the overlay interface up?
ip -d link show vxlan100
bridge fdb show dev vxlan100
# BGP: are all peers established? Are routes learned?
vtysh -c "show bgp summary"
# Neighbor AS Up/Down Pfx
# 10.200.0.2 65002 00:05:23 2 <-- Site B: 2 routes learned
# 10.200.0.3 65003 00:05:20 2 <-- Site C: 2 routes learned
vtysh -c "show ip route"
# B>* 10.0.1.0/24 via 10.200.0.2 dev wg0 <-- AWS VPC, learned via BGP
# B>* 172.16.1.0/24 via 10.200.0.2 dev wg0 <-- AWS overlay, learned via BGP
# B>* 172.16.2.0/24 via 10.200.0.3 dev wg0 <-- Hetzner overlay, learned via BGP
# End-to-end: can you reach a VM on Site C from Site A?
ping 172.16.2.10 # reaches Hetzner VM over encrypted VXLAN-over-WG with BGP routing
What you just built
Encrypted multi-cloud network fabric in ~30 commands. No proprietary hardware. No cloud-specific networking. No vendor lock-in. Every component is a Linux kernel feature or an open-source daemon.
WireGuard encrypts the transport. VXLAN creates virtual Layer 2 segments across sites. BGP dynamically routes between all sites. eBPF lets you monitor and filter at wire speed. Add a new site? Configure WireGuard peer, add a BGP neighbor, done — it learns all routes automatically.
This is the same architecture that powers Cloudflare's edge network, Kubernetes service meshes (Cilium), and cloud provider backbones. The tools are identical. The scale is different. But if you can build it on three kldload nodes, you understand how it works everywhere.
The boundaries that don't exist anymore
| Old boundary | What replaced it | Linux feature |
|---|---|---|
| LAN vs WAN | Encrypted flat network | WireGuard |
| Physical vs virtual switch | Overlay networks (16M segments) | VXLAN / GENEVE |
| Hardware firewall | Programmable kernel dataplane | eBPF / XDP |
| On-prem vs cloud routing | Same protocol everywhere | BGP (FRRouting) |
| Per-host iptables rules | Network-wide policy | Cilium / eBPF |
| Static routes | Dynamic route distribution | BGP + BFD |
| ~4,000 VLANs | ~16 million virtual networks | VXLAN VNI |
Where to go next
- WireGuard Basics — set up the encrypted transport layer
- WireGuard Masterclass — multi-peer mesh, NAT traversal, split tunneling
- WireGuard Mesh & Multi-Site — full mesh topology with route propagation
- eBPF Reference — deep dive into XDP, TC, and custom programs
- eBPF Performance — profiling and optimization at the kernel level
- Cluster & Blue/Green — apply everything above to a real multi-node deployment
- Cloud & Packer — export kldload images and deploy to any cloud