← Back to Blog

BGP over WireGuard: A Tested Config, Compared with GRE, IPsec and VXLAN for IP Transit

Published · by Leo Gashi, RS Computers

BGP IP transit WireGuard

BGP over WireGuard works like BGP over any point-to-point link once two settings are right: wg-quick must not install routes (Table = off), and your end must accept every source address (AllowedIPs = 0.0.0.0/0, ::/0). In our Debian 13 lab, BIRD 2 reached Established about 4 seconds after the WireGuard tunnel came up and carried 0.9 Gbit/s on one vCPU, against 5.4 Gbit/s for unencrypted GRE.

We set up the session the way you would take IP transit over a tunnel, then ran it inside GRE, WireGuard, IPsec, VXLAN and IPIP in October 2026, between two machines of one vCPU each. Encryption is what fills the CPU: the plain tunnels moved 5.3 to 5.6 Gbit/s, WireGuard 0.9 and IPsec 0.6. Per packet, GRE costs 24 bytes, IPsec 56 and WireGuard 60 or more. Below are the numbers, the configs we ran, a troubleshooting table, and how to pick a tunnel when a cross-connect is not an option.

Key facts, checked on 17 October 2026:

GRE vs WireGuard vs IPsec vs VXLAN: speed, MTU and CPU

Throughput is the median of three 20-second iperf3 runs each way, from a server behind one router to a host behind the other. CPU is the busier router's share of its one vCPU, and the next column divides it by the throughput.

TunnelBytes added (IPv4 path)Tunnel MTUThroughput, each wayBusier routervCPU per Gbit/sEncryptedCarried as
No tunnel (reference)015009.3 / 8.3 Gbit/s95 to 96%0.10 to 0.12NoPlain IP
GRE2414765.4 / 5.4 Gbit/s89 to 90%0.17NoIP protocol 47
IPIP (extra)2014805.6 / 5.6 Gbit/s93 to 94%0.17NoIP protocol 4, IPv4 inside only
VXLAN5014505.5 / 5.3 Gbit/s97%0.18NoUDP 4789, layer 2 frames
WireGuard60 plus padding1420 by default, 1440 fits an IPv4 path0.88 / 0.91 Gbit/s98% (the receiving side)1.1Yes, ChaCha20-Poly1305UDP
IPsec, ESP AES-256-GCM56, or 64 behind NAT1446, or 1438 behind NAT0.62 / 0.63 Gbit/s99% (the sending side)1.6YesESP (protocol 50), UDP 4500 behind NAT

Per gigabit, the plain tunnels needed about 1.5 times the CPU of no tunnel at all. WireGuard needed about 6.5 times what GRE did, and IPsec about 9.5 times. BGP did not care. Every session reached Established 3.6 to 4.7 seconds after the second end came up. That wait is BIRD's 5-second connect delay, not the tunnel, and IPsec's key exchange added only about 0.2 seconds.

The five tunnels in one line each:

How we tested, and what the numbers cannot tell you

We ran everything in October 2026 on our Amsterdam test server, an AMD EPYC 7742, in two KVM virtual machines of 1 vCPU and 1 GiB each, the size of a VPS Nano. Software: Debian 13.7, kernel 6.12.111, BIRD 2.17.5 (package bird2), wireguard-tools, strongSwan 6.0.1, iproute2 6.15 and iperf3 3.18. One machine played the provider's edge (AS64512), the other a customer router (AS4200000001). In each, a network namespace (a separate network stack inside Linux) played a host on the internet (203.0.113.10) or the customer's server (198.51.100.10), so BGP routed the test traffic through the tunnel, as transit does.

The limits: both ends shared one physical host and a 1500-byte virtual bridge while other labs ran on it, and the iperf3 endpoints shared the routers' vCPU, so CPU figures include the test traffic. We used one TCP stream and IPv4 BGP sessions only. Compare the tunnels with each other; over the internet, your uplink and the round trip set the pace first.

We used VMs, not containers, because containers share the host's kernel and ours had no GRE or ESP module loaded. A container-based VPS can hit the same wall; a KVM VPS has its own kernel. The tunnel settings below belong to Linux and WireGuard, not to the BGP daemon, though we ran only BIRD.

GRE vs WireGuard MTU: the bytes each tunnel adds

MTU is the largest packet an interface sends. A tunnel's headers come out of the 1500 bytes the path allows, so the packet inside must be smaller. We found each limit with pings that may not be fragmented, halving the range each time:

# as root on the customer router; 10.255.2.1 = the provider's address inside the tunnel
lo=1200; hi=1480
while [ $((hi-lo)) -gt 1 ]; do
  mid=$(( (lo+hi)/2 ))
  if ping -M do -c 1 -W 1 -s $mid 10.255.2.1 > /dev/null 2>&1; then lo=$mid; else hi=$mid; fi
done
echo "largest DF ping payload: $lo (IP packet $((lo+28)) bytes)"
# output we saw, one line per tunnel
largest DF ping payload: 1448 (IP packet 1476 bytes)   # GRE
largest DF ping payload: 1452 (IP packet 1480 bytes)   # IPIP
largest DF ping payload: 1422 (IP packet 1450 bytes)   # VXLAN
largest DF ping payload: 1392 (IP packet 1420 bytes)   # WireGuard, default MTU
largest DF ping payload: 1418 (IP packet 1446 bytes)   # IPsec ESP

With tcpdump we caught the outer packet of a 1028-byte inner one: 1052 bytes for GRE, 1078 for VXLAN, 1084 for IPsec and 1100 for WireGuard. WireGuard's 72 extra bytes are 60 of headers plus 12 of padding. Of those 60, the outer IPv4 header takes 20 and UDP 8, leaving 32 for WireGuard's own header and tag; guides that quote 32 bytes of WireGuard overhead count only that part.

How to set up BGP over WireGuard: Table = off and AllowedIPs

Two settings decide it. Table = off "disables the creation of routes altogether", says the wg-quick manual; the default, auto, adds routes for AllowedIPs. BIRD should be the only thing installing routes. AllowedIPs picks the peer for an outgoing destination and lists the source addresses a peer may send from. The whole internet sits behind a transit provider, so your side must allow everything.

The customer router's wg0.conf

# as root on the customer router: create the key pair once
umask 077
wg genkey | tee /etc/wireguard/b.key | wg pubkey > /etc/wireguard/b.pub
# /etc/wireguard/wg0.conf on the customer router (edit as root)
[Interface]
PrivateKey = (the contents of /etc/wireguard/b.key)
Address = 10.255.2.2/30
ListenPort = 51820
MTU = 1440
Table = off
PostUp = sysctl -qw net.ipv4.conf.%i.rp_filter=1
PostUp = ip rule add from 198.51.100.0/24 lookup 100 priority 1000
PreDown = ip rule del from 198.51.100.0/24 lookup 100 priority 1000 || true

[Peer]
PublicKey = (the provider's public key)
Endpoint = 10.141.188.109:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25
# as root on the customer router
systemctl enable --now wg-quick@wg0
ip route show dev wg0
ip route show table 100 dev wg0
# output we saw: wg-quick added only the tunnel's own /30, BIRD filled table 100 once BGP was up
10.255.2.0/30 proto kernel scope link src 10.255.2.2
default via 10.255.2.1 proto bird metric 32
203.0.113.0/24 via 10.255.2.1 proto bird metric 32

wg-quick brought the tunnel back after both of our test reboots, once before and once after we added the PostUp lines. Use MTU 1420 over IPv6. The PostUp lines belong to the return path, explained further down. A WireGuard interface gets no IPv6 link-local address (ip -6 addr show dev wg0 printed nothing), so an IPv6 session needs assigned tunnel addresses.

The AllowedIPs trap: BGP up, traffic dead

Site-to-site guides often list only the peer's tunnel address, and that breaks transit. With 10.255.2.1/32 on the customer side:

# as root on the customer router, with AllowedIPs = 10.255.2.1/32 on the provider peer
birdc show protocols wg
ping -c 1 -W 1 -I 198.51.100.1 203.0.113.10
# output we saw
wg         BGP        ---        up     04:54:46.431  Established
From 198.51.100.1 icmp_seq=1 Destination Host Unreachable
ping: sendmsg: Required key not available

BGP stayed Established and BIRD still installed 203.0.113.0/24 via 10.255.2.1 dev wg0, yet no peer was allowed that destination, so every packet was refused. With 0.0.0.0/0 traffic flowed at once.

On the provider's side the list should hold only your tunnel address and prefixes, which makes it a free anti-spoofing filter. Pings our customer router sent from 192.0.2.55, outside its list, vanished and showed up as 3 receive errors on the provider's wg0.

Change wg0.conf without dropping BGP

Restarting wg-quick deleted wg0 for a moment. The provider's BIRD logged "Startup delayed by 60 seconds due to errors", rejected 11 reconnects, and the routes were gone for 49 to 61 seconds in three tries. wg syncconf applies only the differences without disrupting current sessions, as the wg manual puts it. We changed the keepalive to 20 seconds this way and BGP never dropped:

# as root on the customer router, after editing /etc/wireguard/wg0.conf
wg syncconf wg0 <(wg-quick strip wg0)

If you want a private VPN for people rather than routes for a network, see our WireGuard VPN server page instead.

BGP over GRE, VXLAN and IPsec: the tunnel commands

Below, 10.141.188.231 is the customer's public address and 10.141.188.109 the provider's edge. Ours were private lab addresses; yours will be public. The /30 inside each tunnel comes from your provider. In the GRE line, ttl 255 gives the outer packets a fixed TTL instead of copying the inner packet's.

# as root on the customer router: GRE (allow IP protocol 47 from the edge in your firewall)
ip tunnel add gre1 mode gre local 10.141.188.231 remote 10.141.188.109 ttl 255
ip addr add 10.255.1.2/30 dev gre1
ip link set gre1 up
# as root on the customer router: VXLAN, VNI 100, bound to the uplink enp5s0
ip link add vxlan100 type vxlan id 100 local 10.141.188.231 remote 10.141.188.109 dstport 4789 dev enp5s0
ip addr add 10.255.4.2/30 dev vxlan100
ip link set vxlan100 up

For IPsec we used strongSwan with an XFRM interface. This route-based style suits BGP: anything routed into the interface is encrypted. Both ends negotiate 0.0.0.0/0, and strongSwan installs no routes for a CHILD_SA with an outbound interface ID, per its route-based VPN guide. Add ::/0 to both selectors for IPv6. Inside an OpenStack cloud, Neutron VPNaaS builds IPsec site-to-site tunnels from a virtual router.

# /etc/swanctl/conf.d/transit.conf on the customer router (edit as root)
connections {
  transit {
    version = 2
    local_addrs = 10.141.188.231
    remote_addrs = 10.141.188.109
    proposals = aes256gcm16-prfsha384-x25519
    dpd_delay = 10s
    local { auth = psk
            id = customer }
    remote { auth = psk
             id = edge }
    children {
      transit {
        local_ts = 0.0.0.0/0
        remote_ts = 0.0.0.0/0
        if_id_in = 42
        if_id_out = 42
        esp_proposals = aes256gcm16-x25519
        start_action = start
        dpd_action = restart
      }
    }
  }
}
secrets {
  ike-transit { id-1 = edge
                id-2 = customer
                secret = "lab-only-psk-replace-with-a-long-random-one" }
}
# as root on the customer router
ip link add ipsec0 type xfrm dev enp5s0 if_id 42
ip addr add 10.255.3.2/30 dev ipsec0
ip link set ipsec0 up
ip link set ipsec0 mtu 1446
swanctl --load-all
# what strongSwan printed when the SA came up (SPIs trimmed)
[IKE] CHILD_SA transit{1} established with SPIs ... and TS 0.0.0.0/0 === 0.0.0.0/0

start_action = start makes the customer side open the tunnel as soon as the config loads. The provider's side in our lab had no start action and dpd_action = clear. The kernel used its AES-NI driver (rfc4106-gcm-aesni-avx), and IPsec was still the slowest tunnel. Single runs with AES-128-GCM gave 0.75 and 0.65 Gbit/s, and ChaCha20-Poly1305 inside ESP 0.41 and 0.43. BGP stayed Established each time strongSwan swapped the SA for a new cipher.

BIRD 2 config for a transit session over a tunnel

eBGP is BGP between two different ASNs, which is what a transit session is. The router keeps its own default route for the uplink that carries the tunnel. Everything learned over BGP goes into kernel table 100, which only traffic from your prefix uses. This is the file we ran:

# /etc/bird/bird.conf on the customer router (edit as root)
log syslog all;
router id 10.255.0.2;

ipv4 table transit4;

protocol device { }

# routes learned from the upstream go to kernel table 100, not the main table
protocol kernel ktransit {
  kernel table 100;
  ipv4 { table transit4; import none; export where source = RTS_BGP; };
}

# the prefix we announce, anchored so a flapping LAN port does not withdraw it
protocol static origin {
  ipv4 { table transit4; };
  route 198.51.100.0/24 unreachable;
}

filter from_upstream {
  if net = 0.0.0.0/0 then accept;
  if net = 203.0.113.0/24 then accept;
  reject;
}

filter to_upstream {
  if net = 198.51.100.0/24 then accept;
  reject;
}

template bgp upstream {
  local as 4200000001;
  hold time 30;
  ipv4 { table transit4; import filter from_upstream; export filter to_upstream; };
}

# one session per tunnel type; keep only the one you use
protocol bgp gre   from upstream { neighbor 10.255.1.1 as 64512; }
protocol bgp wg    from upstream { neighbor 10.255.2.1 as 64512; }
protocol bgp ipsec from upstream { neighbor 10.255.3.1 as 64512; }
protocol bgp vxlan from upstream { neighbor 10.255.4.1 as 64512; }
# as root on the customer router
bird -p -c /etc/bird/bird.conf && echo parse-ok
systemctl restart bird
birdc show protocols wg
ip route show table 100
# output we saw (the last three lines again after a reboot)
parse-ok
wg         BGP        ---        up     05:39:08.095  Established
default via 10.255.2.1 dev wg0 proto bird metric 32
203.0.113.0/24 via 10.255.2.1 dev wg0 proto bird metric 32

203.0.113.0/24 stands in for the internet, and 198.51.100.0/24 is your prefix. With a real full table, use sanity filters instead: no bogons, nothing longer than /24, a maximum prefix count. After the reboot the main table still held the router's own default via its uplink. For a session with us, the IP transit page lists what we need from you.

Without a rule, replies to traffic that came through the tunnel leave by the router's normal default route, and the local provider usually drops packets from addresses it did not assign. rp_filter is Linux's reverse path check: 0 is off, 1 strict, 2 loose, and the higher of the "all" and per-interface values wins (kernel documentation). The kernel default is 0, but our Debian 13 VMs started new interfaces at 2. We sent three pings to the customer's server from each of two internet addresses:

Setup on the customer routerResultWhat happened
Loose (2), no source rule0 of 6tcpdump showed the replies leaving by the local uplink, enp5s0
Strict (1), no source rule0 of 6Dropped on arrival; TcpExtIPReversePathFilter counted 6
Strict (1), rule "from 198.51.100.0/24 lookup 100"6 of 6Replies went back through wg0; the counter did not move
# as root on the customer router
ip rule add from 198.51.100.0/24 lookup 100 priority 1000
ip route get 192.0.2.77 from 198.51.100.10 iif vB
# output we saw (vB is the lab port where the server lives)
192.0.2.77 from 198.51.100.10 via 10.255.2.1 dev wg0 table 100

Loose mode only hides the problem. The PostUp lines in wg0.conf set strict mode and the rule whenever wg0 is created, and both were back after a reboot. The || true on PreDown matters: when we had deleted the rule by hand, PreDown failed, wg-quick aborted the stop, and the next start failed with "wg0 already exists".

Forwarding goes in /etc/sysctl.d/90-transit.conf as net.ipv4.ip_forward = 1. The GRE version with systemd-networkd is in our guide to remote DDoS protection over a GRE tunnel.

MSS clamping on a tunnel: why one direction is not enough

MSS is the largest TCP payload per packet, and each side announces its own when a connection opens. A server on a normal LAN announces 1460, too big for any tunnel. Path MTU discovery fixes that unless something drops the ICMP messages it relies on. We dropped them on both routers and sent 8 seconds of traffic each way over WireGuard at MTU 1440:

Clamping on the customer routerServer to internetInternet to server
NoneStalled, iperf3 error0 bytes
Outgoing SYNs only, set rt mtu0 bytes853 Mbit/s
Fixed 1400, both directions923 Mbit/s818 Mbit/s

Clamping only outgoing SYNs fixes what the far side sends you. It misses the MSS arriving from the internet, which sizes what your server sends. Clamp both ways:

# mss.nft on the customer router; load it as root with: nft -f mss.nft
# 1400 is the WireGuard MTU of 1440 minus 40
table inet mss {
  chain forward {
    type filter hook forward priority mangle; policy accept;
    oifname "wg0" tcp flags syn tcp option maxseg size > 1400 tcp option maxseg size set 1400
    iifname "wg0" tcp flags syn tcp option maxseg size > 1400 tcp option maxseg size set 1400
  }
}
# as root on the customer router: watch the handshake while a client connects
tcpdump -ni any -c 4 'tcp[tcpflags] & tcp-syn != 0 and port 5201'
# output we saw (trimmed)
wg0   In  IP 203.0.113.10.46648 > 198.51.100.10.5201: Flags [S], options [mss 1460,nop,wscale 7]
vB    Out IP 203.0.113.10.46648 > 198.51.100.10.5201: Flags [S], options [mss 1400,nop,wscale 7]

We loaded the rules by hand for the test. To keep them after a reboot, the usual Debian way is to put the table in /etc/nftables.conf and enable nftables.service; we did not reboot-test that step. The clamp value is the tunnel MTU minus 40 for IPv4 and minus 60 for IPv6:

TunnelTunnel MTUIPv4 MSSIPv6 MSS
GRE147614361416
VXLAN145014101390
IPsec, no NAT144614061386
WireGuard on an IPv4 path144014001380
IPsec behind NAT143813981378
WireGuard on an IPv6 path142013801360

How fast BGP notices a dead tunnel: hold time and BFD

A tunnel interface does not go down when the far end dies. We blocked WireGuard's UDP port on the customer router, and wg0 stayed UP the whole time (state UNKNOWN, flags UP and LOWER_UP). We cut only WireGuard this way; GRE, VXLAN and XFRM interfaces have no carrier signal from the far end either. BGP notices through its hold time, the silence it tolerates before giving up:

BGP settingPath died, BGP noticed afterPath back, session up again after
BIRD defaults: hold 240 s, keepalive 80 s166.6 s (up to 240 s)54.8 s
hold time 30, keepalive 10 s21.3 s (up to 30 s)59.5 s
BFD on, BIRD defaults (100 ms, multiplier 5)0.54 s54.1 s

In the first row, traffic kept flowing into a dead tunnel for almost three minutes. The minute-long recovery is BIRD's "error wait time", 60 seconds after a failure. BGP uses the lower of the two hold times, so hold time 30; on the customer side alone gave both routers 30/10 in birdc show protocols all wg. BFD (Bidirectional Forwarding Detection), a light hello protocol, needs both ends, so ask your provider before relying on it. We tested BFD with a simpler BIRD config that used the main routing table; these are the lines we added:

# added to /etc/bird/bird.conf on both routers (edit as root)
protocol bfd { }
# and inside the bgp template:
  bfd on;
# as root, after birdc configure
birdc show bfd sessions
# output we saw
IP address                Interface  State      Since         Interval  Timeout
10.255.2.1                wg0        Up         05:09:47.852    0.100    0.500

Tunnel keepalives do other jobs. WireGuard's PersistentKeepalive holds a NAT mapping open so the provider can reach a router behind NAT. strongSwan's dpd_delay checks that the IKE peer is alive and, with dpd_action = restart, sets the tunnel up again. Linux GRE has none. None of them withdraws a route; that stays the job of the hold time or BFD.

BGP over WireGuard troubleshooting: symptom, cause, fix

Ping the peer's tunnel address first (10.255.2.1 in our lab). If that fails, fix the tunnel before you look at BGP. If it answers, these are the failures we hit or caused in the lab, and what fixed each one:

SymptomCauseFix
BGP is Established, but ping says "Required key not available" and servers get "Destination Host Unreachable"AllowedIPs on your side lists only the peer's /32AllowedIPs = 0.0.0.0/0, ::/0 on your side
Internet hosts get no replies; tcpdump shows them leaving by your normal uplinkNo source rule, so replies follow the main default routeA rule from your prefix to table 100, and rp_filter 1 on wg0
Ping works, but TCP transfers stall or move 0 bytesICMP for path MTU discovery is dropped somewhere, and the MSS is too bigClamp MSS in both directions to the tunnel MTU minus 40
WireGuard runs at half speed and the fragment counter climbs (1.37 million in one test run in our lab)Tunnel MTU 1500 on a 1500-byte pathMTU 1440 over IPv4, 1420 over IPv6
BGP is down for about a minute after a config change; the provider logs "Startup delayed by 60 seconds due to errors"Restarting wg-quick deleted wg0 and sent BIRD into its error waitApply changes with wg syncconf
The tunnel will not start: "wg0 already exists"A PreDown command failed and aborted the previous stopEnd PreDown commands with || true
Routes stay for minutes after the far end diedwg0 stays UP, and BIRD's default hold time is 240 shold time 30;, or BFD if the provider runs it
Traffic from a new prefix vanishes at the providerThe provider's AllowedIPs does not list it, so WireGuard drops it as a receive errorAsk the provider to add the prefix to your peer

Private or public ASN for transit over a tunnel

An ASN is the number that identifies a network in BGP. A private one is enough with a single upstream that accepts it and announces your prefix from its own ASN, stripping yours as RFC 6996 requires. Your ROA, the RPKI record naming who may announce a prefix, then lists the upstream's ASN. Ask before you assume a seller works this way. BIRD handled our 32-bit private ASN, 4200000001, without any special setting.

You need a public ASN once you announce through two upstreams, or one upstream plus exchange peering; otherwise each upstream originates your prefix from a different ASN. That matches RIPE policy (ripe-679), which says a network must be multihomed to qualify for an AS number.

RS Computers is a RIPE NCC member (LIR) and sponsors ASNs for clients directly; see how to get your own ASN and IP addresses. Leased IPv4 works on our transit when the IRR objects and ROAs match your ASN, as our guide on announcing leased IPv4 with an LOA and ROA explains.

Which tunnel, PoP and plan to choose for IP transit over a tunnel

RS Computers (AS213900) sells flat-rate, unmetered IP transit at 1, 5 and 10 Gbit/s, month to month with a free BGP session, delivered by cross-connect or over GRE, IPsec, WireGuard, VXLAN, L2TPv3, EoIP, OpenVPN or IPIP. The PoPs are Neocom DC in Skopje, with a 100G port on IXP.mk and open peering (see our guide to Balkan internet exchanges and peering), and Qupra DC in Amsterdam, with hand-off at Nikhef. Hurricane Electric and GSL are among our upstreams; here is how Hurricane Electric and Cogent compare as upstreams.

Your situationTunnel and MTUTransit PoPIf your router runs on our servers
Network in Kosovo, North Macedonia or Albania, up to 1 Gbit/s, behind NAT or needing encryptionWireGuard, MTU 1440 (1420 over IPv6), Table = offIP transit in Skopje; Prishtina to Skopje is about 3 ms on our own networkVPS Mini in Prishtina: 4 shared vCPU, 4 GB RAM, 80 GB NVMe, 1 Gb/s
Balkan network in a data centre with a public IP, above 1 Gbit/s, clear text is fineGRE, MTU 1476, MSS 1436 both waysSkopjeVDS Small in Prishtina: 4 dedicated vCPU, 8 GB RAM, 240 GB NVMe, 10 Gb/s
Router in a Western European data centre, GRE allowedGRE, or IPIP for IPv4 only (MTU 1480)IP transit in AmsterdamVDS Small, or VDS Medium (8 dedicated vCPU, 16 GB RAM, 480 GB NVMe, 10 Gb/s), in Amsterdam
Already at Qupra or NikhefNo tunnel: a cross-connect beats any tunnelAmsterdamNot needed
A firewall appliance that speaks IKEv2, or a rule that traffic must be encryptedIPsec, route-based, MTU 1446 (1438 behind NAT)Skopje or Amsterdam, whichever is closerVPS Mini or VDS Small near 1 Gbit/s, in Prishtina or Amsterdam
You need layer 2, or your platform already speaks VXLANVXLAN with dstport 4789, MTU 1450, MSS 1410Skopje or AmsterdamVDS Small above 1 Gbit/s
Your prefixes draw DDoS attacksGRE to Amsterdam with filtering on GSLAmsterdam; see remote DDoS protection over GREYour own router works; the guide has the GRE setup
You want to rehearse these configs before you orderAny of themNone yetTwo VPS Nano: 1 shared vCPU, 1 GB RAM, 20 GB NVMe, 1 Gb/s, the size of our lab machines

Size the router to the tunnel: one vCPU moved 5.4 Gbit/s through GRE but under 1 Gbit/s encrypted. VPS plans have 1 Gb/s ports and shared vCPUs; VDS plans have 10 Gb/s ports and dedicated vCPUs, which keep crypto throughput steadier and suit a 5 or 10 Gbit/s transit port. We did not test more than one vCPU, so the bigger plans are headroom, not measured throughput.

All plans run on KVM with NVMe, unmetered traffic and their own IPv4 and IPv6; availability by city is on the plans page. In our ping.pe measurement of 6 October 2026, Frankfurt and London reached our Amsterdam server in 6.6 and 6.7 ms, and Vienna reached our Prishtina server in 18.6 ms. We help with MTU and MSS when the tunnel goes up, so name your tunnel type when you ask for IP transit.

Frequently asked questions

Can you run BGP over WireGuard?

Yes. Treat the WireGuard interface as a point-to-point link with a /30 inside, set Table = off so wg-quick installs no routes, and set AllowedIPs to 0.0.0.0/0, ::/0 on your side. In our Debian 13 lab, BIRD 2 reached Established about 4 seconds after the tunnel came up and carried about 0.9 Gbit/s on one vCPU.

What MTU should I use for GRE, WireGuard, IPsec and VXLAN?

On a 1500-byte IPv4 path we measured 1476 for GRE, 1450 for VXLAN, 1446 for IPsec with AES-GCM (1438 behind NAT) and 1440 for WireGuard; use 1420 for WireGuard over IPv6. Clamp TCP MSS to the MTU minus 40 for IPv4 and minus 60 for IPv6, in both directions.

Is GRE faster than WireGuard for IP transit?

On one vCPU, yes: 5.4 Gbit/s for GRE against 0.9 for WireGuard in our lab, because encryption filled the CPU. When your uplink is the limit the gap shrinks. GRE is unencrypted and travels as IP protocol 47, which has no ports, so it rarely gets through a NAT router; WireGuard's single UDP port does.

Why is my BGP session up but traffic over WireGuard fails?

Usually AllowedIPs on your side lists only the peer's tunnel address. BGP between the tunnel addresses still works, but packets to other destinations fail with "Required key not available", and servers behind the router get "Destination Host Unreachable". Set AllowedIPs to 0.0.0.0/0, ::/0, and check MSS clamping if only large transfers hang.

Do I need a public ASN for BGP over a tunnel?

Not always. A private ASN (64512 to 65534, or 4200000000 to 4294967294) works with a single upstream that accepts it and announces your prefix from its own ASN. With two upstreams, or an upstream plus exchange peering, you need a public ASN. RS Computers is a RIPE NCC member and sponsors ASNs.

How fast does BGP notice a dead tunnel?

With BIRD's default 240-second hold time, our dead WireGuard tunnel kept its routes for 166.6 seconds. A 30-second hold time cut that to 21.3 seconds, and BFD to 0.54 seconds. The tunnel interface stayed up every time, so the BGP timers decide.

Bring your router, we bring the routes

GRE where it passes, WireGuard behind NAT, IPsec when a policy asks for it, VXLAN for layer 2. Then set the MTU, clamp MSS both ways, add a source rule and shorten the hold time. Our IP transit runs from Skopje and Amsterdam over any of these tunnels or a cross-connect, flat rate and month to month. Send your ASN, prefixes and tunnel type on Telegram and we will send back the session details.

← All articles

Chat on Telegram