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 adds a 4-byte header (RFC 2784) plus 20 bytes of IPv4, leaving 1476 bytes on a 1500-byte path. Linux chose 1476 by itself in our lab.
- WireGuard pads each packet to a multiple of 16 bytes before encrypting it (WireGuard protocol page), so it adds 60 bytes plus padding over IPv4 and 80 plus padding over IPv6.
- VXLAN uses UDP port 4789 and an 8-byte header, and RFC 7348 says endpoints "MUST NOT fragment" VXLAN packets. Without
dstport, iproute2 6.15 falls back to Linux's old port 8472. - Private ASNs are 64512 to 65534 and 4200000000 to 4294967294, and they must be stripped before a route reaches the internet (RFC 6996).
- BIRD 2's default hold time is 240 seconds (BIRD 2.17 user's guide), against 90 suggested by RFC 4271. The newest 2.x release is 2.20.0 of 12 October 2026 (BIRD news); Debian 13 ships 2.17.5, which we used.
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.
| Tunnel | Bytes added (IPv4 path) | Tunnel MTU | Throughput, each way | Busier router | vCPU per Gbit/s | Encrypted | Carried as |
|---|---|---|---|---|---|---|---|
| No tunnel (reference) | 0 | 1500 | 9.3 / 8.3 Gbit/s | 95 to 96% | 0.10 to 0.12 | No | Plain IP |
| GRE | 24 | 1476 | 5.4 / 5.4 Gbit/s | 89 to 90% | 0.17 | No | IP protocol 47 |
| IPIP (extra) | 20 | 1480 | 5.6 / 5.6 Gbit/s | 93 to 94% | 0.17 | No | IP protocol 4, IPv4 inside only |
| VXLAN | 50 | 1450 | 5.5 / 5.3 Gbit/s | 97% | 0.18 | No | UDP 4789, layer 2 frames |
| WireGuard | 60 plus padding | 1420 by default, 1440 fits an IPv4 path | 0.88 / 0.91 Gbit/s | 98% (the receiving side) | 1.1 | Yes, ChaCha20-Poly1305 | UDP |
| IPsec, ESP AES-256-GCM | 56, or 64 behind NAT | 1446, or 1438 behind NAT | 0.62 / 0.63 Gbit/s | 99% (the sending side) | 1.6 | Yes | ESP (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:
- GRE (Generic Routing Encapsulation) wraps an IP packet in a 4-byte header, sent as IP protocol 47 in clear text.
- IPIP puts an IPv4 packet straight inside another, as IP protocol 4, so it carries IPv4 only.
- VXLAN carries whole Ethernet frames over UDP port 4789, so ARP runs inside the tunnel.
- WireGuard is an encrypted UDP tunnel (ChaCha20-Poly1305) where each peer has a key pair and an AllowedIPs list.
- IPsec encrypts packets with ESP after an IKEv2 key exchange, done here by strongSwan; behind NAT, ESP travels inside UDP port 4500.
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.
- WireGuard's default of 1420 suits an IPv6 path. Over IPv4 a full packet left at 1480 bytes, while MTU 1440 filled 1500 exactly. Over IPv6, 1420 fitted and 1440 made Linux split every full packet in two.
- WireGuard at 1500 on an IPv4 path raised no error. Its outer packets carry no "don't fragment" flag, so Linux split each 1560-byte packet into 1500 and 80. One test run made about 1.37 million fragments, and throughput fell from 0.89 to 0.45 Gbit/s.
- The IPsec interface reported 1500 while the kernel refused anything above 1446, or 1438 with NAT traversal. Set the real value so every tool sees it.
- VXLAN, bound to the uplink with
dev, set 1450 by itself. 14 of its 50 bytes are the inner Ethernet header.
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.
Return path: why replies leave by the wrong uplink
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 router | Result | What happened |
|---|---|---|
| Loose (2), no source rule | 0 of 6 | tcpdump showed the replies leaving by the local uplink, enp5s0 |
| Strict (1), no source rule | 0 of 6 | Dropped on arrival; TcpExtIPReversePathFilter counted 6 |
| Strict (1), rule "from 198.51.100.0/24 lookup 100" | 6 of 6 | Replies 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 router | Server to internet | Internet to server |
|---|---|---|
| None | Stalled, iperf3 error | 0 bytes |
Outgoing SYNs only, set rt mtu | 0 bytes | 853 Mbit/s |
| Fixed 1400, both directions | 923 Mbit/s | 818 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:
| Tunnel | Tunnel MTU | IPv4 MSS | IPv6 MSS |
|---|---|---|---|
| GRE | 1476 | 1436 | 1416 |
| VXLAN | 1450 | 1410 | 1390 |
| IPsec, no NAT | 1446 | 1406 | 1386 |
| WireGuard on an IPv4 path | 1440 | 1400 | 1380 |
| IPsec behind NAT | 1438 | 1398 | 1378 |
| WireGuard on an IPv6 path | 1420 | 1380 | 1360 |
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 setting | Path died, BGP noticed after | Path back, session up again after |
|---|---|---|
| BIRD defaults: hold 240 s, keepalive 80 s | 166.6 s (up to 240 s) | 54.8 s |
| hold time 30, keepalive 10 s | 21.3 s (up to 30 s) | 59.5 s |
| BFD on, BIRD defaults (100 ms, multiplier 5) | 0.54 s | 54.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:
| Symptom | Cause | Fix |
|---|---|---|
| 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 /32 | AllowedIPs = 0.0.0.0/0, ::/0 on your side |
| Internet hosts get no replies; tcpdump shows them leaving by your normal uplink | No source rule, so replies follow the main default route | A rule from your prefix to table 100, and rp_filter 1 on wg0 |
| Ping works, but TCP transfers stall or move 0 bytes | ICMP for path MTU discovery is dropped somewhere, and the MSS is too big | Clamp 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 path | MTU 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 wait | Apply changes with wg syncconf |
| The tunnel will not start: "wg0 already exists" | A PreDown command failed and aborted the previous stop | End PreDown commands with || true |
| Routes stay for minutes after the far end died | wg0 stays UP, and BIRD's default hold time is 240 s | hold time 30;, or BFD if the provider runs it |
| Traffic from a new prefix vanishes at the provider | The provider's AllowedIPs does not list it, so WireGuard drops it as a receive error | Ask 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 situation | Tunnel and MTU | Transit PoP | If your router runs on our servers |
|---|---|---|---|
| Network in Kosovo, North Macedonia or Albania, up to 1 Gbit/s, behind NAT or needing encryption | WireGuard, MTU 1440 (1420 over IPv6), Table = off | IP transit in Skopje; Prishtina to Skopje is about 3 ms on our own network | VPS 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 fine | GRE, MTU 1476, MSS 1436 both ways | Skopje | VDS Small in Prishtina: 4 dedicated vCPU, 8 GB RAM, 240 GB NVMe, 10 Gb/s |
| Router in a Western European data centre, GRE allowed | GRE, or IPIP for IPv4 only (MTU 1480) | IP transit in Amsterdam | VDS Small, or VDS Medium (8 dedicated vCPU, 16 GB RAM, 480 GB NVMe, 10 Gb/s), in Amsterdam |
| Already at Qupra or Nikhef | No tunnel: a cross-connect beats any tunnel | Amsterdam | Not needed |
| A firewall appliance that speaks IKEv2, or a rule that traffic must be encrypted | IPsec, route-based, MTU 1446 (1438 behind NAT) | Skopje or Amsterdam, whichever is closer | VPS Mini or VDS Small near 1 Gbit/s, in Prishtina or Amsterdam |
| You need layer 2, or your platform already speaks VXLAN | VXLAN with dstport 4789, MTU 1450, MSS 1410 | Skopje or Amsterdam | VDS Small above 1 Gbit/s |
| Your prefixes draw DDoS attacks | GRE to Amsterdam with filtering on GSL | Amsterdam; see remote DDoS protection over GRE | Your own router works; the guide has the GRE setup |
| You want to rehearse these configs before you order | Any of them | None yet | Two 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.