As VPN operators and enthusiasts push WireGuard-based services to handle tens or hundreds of thousands of concurrent tunnels, the kernel networking stack and classic user-space tooling can become bottlenecks. Since 2022 eBPF/XDP has matured into a practical toolset for high-performance packet processing on Linux nodes. This guide walks through a pragmatic, production-minded approach to combining WireGuard with eBPF/XDP to harden, accelerate, and scale VPN exit nodes while keeping the configuration maintainable and auditable.
What this guide covers — and what it doesn’t
This article shows how to use XDP for early packet filtering and DoS mitigation, AF_XDP for zero-copy handoff when a userspace fast-path is required, and tc/eBPF for per-peer QoS and shaping. It covers architecture choices, example commands, observability, and deployment considerations in 2026 Linux environments.
This is not a tutorial for building a bespoke WireGuard userspace implementation from scratch; instead it explains how to integrate existing WireGuard runtimes (kernel module or vetted userspace) with eBPF tooling and where the real performance gains lie.
Why combine WireGuard and eBPF/XDP?
- Early packet inspection: XDP runs at the earliest receive point in the kernel, allowing cheap drop/redirects before more kernel work is done.
- DoS protection and blacklists: block floods or suspicious flows on ingress interfaces with minimal CPU cost.
- Fast-path forwarding: AF_XDP enables zero-copy transfer between NIC and userspace for specialized per-packet processing or custom tunneling.
- Per-peer enforcement: tc BPF programs can implement token-bucket shaping per WireGuard peer without context switches to userspace.
- Observability: BPF maps provide counters and histograms for precise, low-overhead telemetry suitable for Prometheus scraping.
High-level architecture options
Pick one of three common patterns depending on scale and capability:
- Kernel-first (default) — WireGuard kernel module handles crypto and forwarding; attach XDP programs to public NIC(s) for prefiltering and DoS mitigation. Best balance for most services.
- Kernel + tc eBPF shaping — Keep kernel WireGuard for crypto, use tc/eBPF clsact for per-peer shaping, accounting, and NAT offload. Adds granular QoS control.
- AF_XDP fast path — Use XDP/AF_XDP to deliver packets directly to a userspace fast-path that handles packet classification and passes UDP datagrams to a high-performance WireGuard userland (or a light wrapper around wg-quick/boringtun). This trades dev effort for the lowest latency and highest throughput on supported NICs.
Prerequisites and environment
- Linux kernel: recommend 6.1+ LTS (by 2026 many datacenter kernels are 6.x); AF_XDP, XDP, and CO-RE work best on modern kernels.
- Userspace: wireguard-tools, iproute2 with BPF support, bpftool, libbpf-dev (v1.x+), clang/LLVM for compiling BPF programs.
- NIC and driver support: verify AF_XDP and zero-copy support for your drivers (Intel ixgbe/i40e, recent Intel e100/ice, Mellanox mlx5 have broad support in modern kernels). Use 'ethtool -i
- Container orchestration: if using Kubernetes, run eBPF/XDP at node level (DaemonSet) with host networking and required CAP_SYS_ADMIN/CAP_BPF privileges, or use Cilium for a higher-level eBPF management layer.
Step-by-step: Add XDP prefiltering for WireGuard ingress
Goal: drop clearly invalid or abusive packets on the NIC before they consume kernel CPU.
- Compile a simple XDP program (CO-RE recommended) that:
- Checks UDP destination port(s) used by WireGuard (e.g., 51820, or your configured ports).
- Drops non-UDP or clearly malformed packets early.
- Tracks per-source counters (BPF_MAP_TYPE_LRU_HASH) to detect floods and apply temporary blacklists.
- Attach the XDP program to the public interface:
or with bpftool:ip link set dev eth0 xdp obj wg_prefilter.o sec xdpbpftool prog loadall wg_prefilter.o /sys/fs/bpf/wg_prefilter - Populate BPF maps from userspace for allowlist/denylist updates:
bpftool map update pinned /sys/fs/bpf/wg_prefilter/maps/peer_whitelist key hex XX value hex YY
Result: most garbage traffic is dropped without walking the socket layer or triggering WireGuard state machines.
Step-by-step: Per-peer shaping and NAT with tc + eBPF
Goal: enforce per-peer rate limits, priority queues for latency-sensitive traffic, and connection accounting.
- Use ip link set dev wg0 up with WireGuard kernel module for crypto and kernel routing.
- Attach a tc clsact program on the egress of wg0 to classify packets:
- Lookup packet source IP (internal peer address) in a BPF map that stores per-peer rate parameters (rate, burst).
- Use BPF_HELPERS like bpf_skb_adjust_room for simple adjustments, and redirect to fq_codel or HTB qdisc for real shaping.
- Example command to attach:
tc qdisc add dev wg0 clsact tc filter add dev wg0 egress bpf da obj wg_shaper.o sec classifier - Maintain maps via a small control-plane process that updates token-bucket parameters when subscriptions change.
Step-by-step: AF_XDP zero-copy fast path (optional)
Goal: push performance by moving packet I/O directly between NIC and a high-performance userspace process (zero-copy), minimizing kernel traversals.
- Confirm driver supports AF_XDP zero-copy UMEM. Test with xsk tool or iperf to validate.
- Deploy a userspace front-end that:
- Receives raw L3/L2 frames with AF_XDP sockets (xsk).
- Performs lightweight classification (BPF maps can be shared with kernel XDP programs).
- Extracts UDP datagrams and hands them to a highly optimized WireGuard userspace handler, or to a kernel socket via sendto if preferring kernel crypto.
- When using a userspace WireGuard implementation (e.g., boringtun or a custom high-performance binary), avoid extra copies and keep cryptographic operations pinned to dedicated CPU cores.
- Remember: AF_XDP development and maintenance cost is higher; use this only where benchmarking proves kernel-wireguard I/O is the bottleneck.
Observability: BPF maps, counters and Prometheus integration
Observability is essential for tuning and incident response.
- Expose per-peer counters (bytes in/out, packets, drops) via BPF maps. Use bpftool or an eBPF exporter to periodically read maps and publish metrics.
- Use histograms in BPF (store latency buckets) to monitor decryption latency or packet processing time.
- Integrate with existing monitoring: a small agent can read maps via libbpf and push to Prometheus with labels for peer-id, country, subscription tier.
- Use eBPF-based tcpdump-lite (bpftrace or tracepoints) to capture rare failure modes without enabling expensive packet captures on the production path.
Testing and benchmarking
Before rolling to production, benchmark and validate:
- Use pktgen (kernel module) to generate line-rate traffic for synthetic load.
- Measure latency and throughput with iperf3 and wireguard-perf tools, comparing baseline (kernel WireGuard only) to XDP + tc + AF_XDP configurations.
- Test DoS scenarios: run distributed UDP hammer tests and validate that XDP prefilter keeps CPU usage flat while dropping bad traffic.
- Monitor BPF verifier messages during development—complex programs can be rejected or JIT-deoptimized, affecting performance.
Operational considerations and pitfalls
- Verifier limits: BPF verifier complexity can block program loading. Keep eBPF programs small and use helper maps for complexity.
- Driver quirks: AF_XDP zero-copy is driver-dependent. Test across your fleet; fall back to generic XDP or kernel path where support is missing.
- Resource pinning: For high throughput, pin BPF worker threads and userspace crypto threads to dedicated CPU cores and reserve NIC queues via RSS/queue pinning.
- Safety: Roll out XDP programs incrementally and keep a kill-switch (systemd unit or orchestration health-check) to remove problematic programs automatically.
- Compatibility: Some hosting providers restrict CAP_BPF or CAP_NET_ADMIN. Plan for deployment in environments where privileged capabilities are available or use node-level privileged DaemonSets.
Security and auditing
eBPF adds powerful attack surface if misused. Follow these practices:
- Build BPF programs reproducibly and sign artifacts. Use CO-RE and explicit build pipelines so loaded programs are auditable.
- Limit map access via pinned map directories and UID/GID ownership; avoid exposing maps to arbitrary userspace.
- Log and alert when BPF maps show sudden changes in drop rates or blacklists.
- Keep kernel and libbpf updated; eBPF safety and features evolve quickly—run security backports.
When not to use eBPF/XDP
Don’t add XDP/AF_XDP purely for novelty. If your WireGuard nodes handle a few thousand tunnels and CPU is not a bottleneck, added complexity may not be justified. Use eBPF where measurements show kernel traversal or packet copy overheads limit capacity.
Checklist for production rollout
- Baseline performance numbers (throughput, p95 latency) for current kernel-WireGuard setup.
- Small lab test of XDP prefilter on representative hardware and representative traffic patterns.
- Per-node capability matrix: kernel version, driver AF_XDP zero-copy support, NIC RSS queue count.
- Automated map-control daemon that updates per-peer settings and exposes metrics.
- Rollback plan and health probes to auto-remove BPF attachments if CPU or latency regressions occur.
Bottom line
eBPF/XDP gives VPN operators a practical toolkit to push WireGuard-based services to new throughput and resiliency levels. Use XDP for cheap, early packet filtering and DoS mitigation; use tc eBPF to enforce per-peer fairness; and consider AF_XDP zero-copy only when careful benchmarking shows the kernel path is a bottleneck. Keep programs small, observable, and auditable—those operational constraints separate long-lived production deployments from one-off experiments.
If you want, I can provide a starter XDP program and a control-plane script to manage BPF maps keyed by WireGuard public keys and peer IPs, plus a sample tc eBPF classifier for per-peer shaping.