<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="../assets/xml/rss.xsl" media="all"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>TinyComputers.io (Posts about wireguard)</title><link>https://tinycomputers.io/</link><description></description><atom:link href="https://tinycomputers.io/categories/wireguard.xml" rel="self" type="application/rss+xml"></atom:link><language>en</language><copyright>Contents © 2026 A.C. Jokela 
&lt;!-- div style="width: 100%" --&gt;
&lt;a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"&gt;&lt;img alt="" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/80x15.png" /&gt; Creative Commons Attribution-ShareAlike&lt;/a&gt;&amp;nbsp;|&amp;nbsp;
&lt;!-- /div --&gt;
</copyright><lastBuildDate>Fri, 17 Jul 2026 16:29:10 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>Banana Pi BPI-R4 Pro Review: A 10-Gigabit Router Board I Put to Work as a Bastion</title><link>https://tinycomputers.io/posts/banana-pi-bpi-r4-pro-review.html?utm_source=feed&amp;utm_medium=rss&amp;utm_campaign=rss</link><dc:creator>A.C. Jokela</dc:creator><description>&lt;div class="audio-widget"&gt;
&lt;div class="audio-widget-header"&gt;
&lt;span class="audio-widget-icon"&gt;🎧&lt;/span&gt;
&lt;span class="audio-widget-label"&gt;Listen to this article&lt;/span&gt;
&lt;/div&gt;
&lt;audio controls preload="metadata"&gt;
&lt;source src="https://tinycomputers.io/banana-pi-bpi-r4-pro-review_tts.mp3" type="audio/mpeg"&gt;
&lt;/source&gt;&lt;/audio&gt;
&lt;div class="audio-widget-footer"&gt;40 min · AI-generated narration&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Most of the single-board computers I review here are compute boards: you install Debian or Ubuntu, you build software on them, you measure how fast they crunch. The &lt;a href="https://amzn.to/4wTOedz"&gt;Banana Pi BPI-R4 Pro&lt;/a&gt; is a different animal entirely. It is a &lt;em&gt;router&lt;/em&gt; - a networking board built around MediaTek's MT7988A (Filogic 880) SoC, shipping with OpenWRT rather than a general-purpose Linux distribution, and designed from the silicon up to move packets rather than compile code. Reviewing it by the same yardstick I use for a Raspberry Pi 5 would be like reviewing a delivery truck by its quarter-mile time. So I did something a little different: I deployed the R4 Pro as a real piece of infrastructure - an internet-facing SSH bastion and TLS reverse proxy sitting in front of a small fleet of machines - and then benchmarked the things a router actually has to be good at.&lt;/p&gt;
&lt;p&gt;The short version: the BPI-R4 Pro is an impressively capable networking platform with genuine 10-gigabit ambitions and a crypto engine strong enough to terminate encrypted tunnels at line rate. Its quad Cortex-A73 CPU is modest by 2026 compute-board standards, but that CPU is not the point. The board's constraints - OpenWRT instead of Debian, a tiny writable overlay, a stock kernel with some conspicuous gaps - are exactly the constraints of the router-appliance world it comes from. Here is where it shines, where it frustrated me, and who should actually buy one.&lt;/p&gt;
&lt;h3&gt;Hardware Architecture: The MediaTek MT7988A (Filogic 880)&lt;/h3&gt;
&lt;p&gt;At the heart of the R4 Pro is the &lt;a href="https://www.mediatek.com/products/broadband-wifi/mediatek-filogic-880"&gt;MediaTek MT7988A&lt;/a&gt;, marketed as the Filogic 880. Where a typical SBC SoC is a Rockchip or Broadcom part with a strong CPU cluster and networking bolted on, the MT7988A inverts that priority: it is a networking processor first, with a competent application CPU attached.&lt;/p&gt;
&lt;p&gt;The CPU cluster is four ARM Cortex-A73 cores running at 1.8 GHz. Reading &lt;code&gt;/proc/cpuinfo&lt;/code&gt; confirms CPU part &lt;code&gt;0xd09&lt;/code&gt; (Cortex-A73) with the full ARMv8 crypto feature set: &lt;code&gt;aes&lt;/code&gt;, &lt;code&gt;pmull&lt;/code&gt;, &lt;code&gt;sha1&lt;/code&gt;, &lt;code&gt;sha2&lt;/code&gt;, and &lt;code&gt;crc32&lt;/code&gt;. That crypto flag list matters enormously for a router, and I will return to it.&lt;/p&gt;
&lt;p&gt;The Cortex-A73, released by ARM in 2016, is architecturally interesting for a networking chip. It sits between the Cortex-A72 (2015, found in the &lt;a href="https://amzn.to/4gJ6BNw"&gt;Banana Pi CM5-Pro&lt;/a&gt;) and the Cortex-A76 (2018, found in the &lt;a href="https://amzn.to/4ysoGFQ"&gt;Raspberry Pi 5&lt;/a&gt; and &lt;a href="https://amzn.to/4f6p9WF"&gt;Orange Pi 5 Max&lt;/a&gt;). The A73 was ARM's efficiency-focused big core - narrower than the A72 in some respects but with better power characteristics and, in practice, comparable or slightly better per-clock throughput. Four of them at 1.8 GHz is a sensible choice for a router: enough headroom to run a full firewall, a reverse proxy, and userspace daemons, without the thermal and power budget of a compute-grade cluster.&lt;/p&gt;
&lt;p&gt;What surrounds that CPU is where the money went:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Dual 10 Gigabit Ethernet MACs.&lt;/strong&gt; Two of the board's interfaces (&lt;code&gt;eth0&lt;/code&gt; and &lt;code&gt;eth2&lt;/code&gt;) report a link capability of 10000 Mbps.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A hardware packet-processing engine (PPE)&lt;/strong&gt; for NAT/flow offload, so routed traffic need not touch the CPU on the fast path.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An inline crypto engine&lt;/strong&gt; exposed through the ARMv8 AES/SHA instructions, which the kernel and OpenSSL use transparently.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An external MaxLinear switch&lt;/strong&gt; (the &lt;code&gt;mxl_lan&lt;/code&gt;* DSA ports in Linux), which is how the "Pro" and "8X" board variants fan out their many gigabit and multi-gigabit ports.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A WiFi 7 radio&lt;/strong&gt; (the "BE14" in the stock image name, driven by MediaTek's &lt;code&gt;mt76&lt;/code&gt; stack).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My unit is the BPI-R4-PRO-8X, the higher-tier variant, with 8 GB of RAM - &lt;code&gt;MemTotal&lt;/code&gt; reports 8,136,024 kB, roughly 7.76 GiB usable. For a router that is a genuinely generous memory allotment; most consumer routers ship with a fraction of it, and it means the R4 Pro can hold large connection-tracking tables, run containers, or cache aggressively without breaking a sweat.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Banana Pi BPI-R4-PRO-8X board" src="https://tinycomputers.io/images/r4pro/IMG_4589.jpeg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The Banana Pi BPI-R4-PRO-8X - the MediaTek MT7988A router board reviewed here, with dual 10-gigabit MACs, a MaxLinear switch, and WiFi 7, running OpenWRT&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;The Board Itself: Ports, Storage, and the Console&lt;/h3&gt;
&lt;p&gt;Physically, the R4 Pro follows the BPI-R4 family form factor: a router-style board rather than a credit-card SBC, with a bank of Ethernet jacks, SFP+ cages for the 10-gigabit optics, an M.2 slot, USB, and the usual pin headers. The "8X" designation refers to its port fan-out via the MaxLinear switch. In Linux I see &lt;code&gt;eth0&lt;/code&gt; and &lt;code&gt;eth2&lt;/code&gt; as 10G-capable MACs, &lt;code&gt;eth1&lt;/code&gt; as a 1G interface, and a family of &lt;code&gt;mxl_lan0&lt;/code&gt; through &lt;code&gt;mxl_lan5&lt;/code&gt; DSA switch ports hanging off &lt;code&gt;eth2&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Storage is the first place the router DNA shows. The board boots from onboard flash - a squashfs &lt;code&gt;/rom&lt;/code&gt; (100 MB, read-only) overlaid with a UBI/NAND overlay for persistent writes. That overlay is &lt;em&gt;small&lt;/em&gt;: 48.5 MB total, of which only around 42 MB was free on my unit. There is eMMC on board as well (I booted from it during setup earlier in the board's life), but the running configuration lives on a constrained flash budget by design. Offsetting this, OpenWRT mounts a 3.9 GB &lt;code&gt;tmpfs&lt;/code&gt; at &lt;code&gt;/tmp&lt;/code&gt;, RAM-backed and fast, which is where any scratch work belongs.&lt;/p&gt;
&lt;p&gt;This is not a criticism so much as a category marker. Routers do not have roomy filesystems; they have just enough flash to hold firmware and config, plus RAM for working state. If you come to the R4 Pro expecting to &lt;code&gt;apt install&lt;/code&gt; a development toolchain and a few hundred megabytes of dependencies, you will run out of overlay in about four packages. Plan accordingly.&lt;/p&gt;
&lt;p&gt;The console deserves a note for anyone deploying one headless. The board exposes a USB-C serial console (an HT42B534-based UART bridge), so a single USB-C cable gets you a boot log and a login shell at 115200 baud without needing a separate TTL adapter. It is a small quality-of-life win that matters the first time a firewall rule locks you out over the network.&lt;/p&gt;
&lt;h3&gt;Software: OpenWRT, Not Debian&lt;/h3&gt;
&lt;p&gt;The single most important fact about the R4 Pro - the fact that colors every other observation in this review - is that it runs &lt;strong&gt;OpenWRT&lt;/strong&gt;, not a general-purpose Linux distribution. My unit shipped the &lt;code&gt;BPI-R4Pro-8X-BE14-MT76-OpenWRT24.10&lt;/code&gt; image, kernel 6.6.93.&lt;/p&gt;
&lt;p&gt;For anyone who has only used SBCs as tiny Linux PCs, OpenWRT is a different world with its own idioms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;opkg&lt;/code&gt;&lt;strong&gt;, not&lt;/strong&gt; &lt;code&gt;apt&lt;/code&gt;&lt;strong&gt;.&lt;/strong&gt; Packages come from OpenWRT feeds. The catalog is large - &lt;code&gt;opkg list&lt;/code&gt; returned 9,695 packages on my unit - but it is a router-firmware catalog, so you find &lt;code&gt;iperf3&lt;/code&gt;, &lt;code&gt;coremark&lt;/code&gt;, &lt;code&gt;stress-ng&lt;/code&gt;, &lt;code&gt;nginx&lt;/code&gt;, and &lt;code&gt;wireguard-tools&lt;/code&gt;, not &lt;code&gt;build-essential&lt;/code&gt; and &lt;code&gt;python3-dev&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UCI configuration.&lt;/strong&gt; Networking, firewall, DHCP, and services are configured through OpenWRT's Unified Configuration Interface (&lt;code&gt;/etc/config/&lt;/code&gt;*) and the &lt;code&gt;uci&lt;/code&gt; command, not by hand-editing &lt;code&gt;/etc/network/interfaces&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fw4&lt;/code&gt;&lt;strong&gt;/nftables firewall.&lt;/strong&gt; Modern OpenWRT builds the firewall on nftables via the &lt;code&gt;fw4&lt;/code&gt; layer, organized into zones (LAN, WAN) with per-zone input/forward/output policies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DSA switch model.&lt;/strong&gt; The MaxLinear switch ports appear as individual &lt;code&gt;mxl_lanN&lt;/code&gt; netdevs bridged together, the modern Distributed Switch Architecture model, rather than the old &lt;code&gt;swconfig&lt;/code&gt; VLAN approach.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ash&lt;/code&gt;&lt;strong&gt;, not&lt;/strong&gt; &lt;code&gt;bash&lt;/code&gt;&lt;strong&gt;.&lt;/strong&gt; The shell is BusyBox &lt;code&gt;ash&lt;/code&gt;; the userland is BusyBox. Little things bite you - &lt;code&gt;nproc&lt;/code&gt; does not exist, &lt;code&gt;dd&lt;/code&gt; rejects &lt;code&gt;conv=fdatasync&lt;/code&gt; - until you internalize that you are on an embedded system.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of this is bad. It is, in fact, exactly what you want if your goal is a router: OpenWRT is battle-tested router firmware with a coherent configuration model, atomic sysupgrades, and a security posture built for internet-facing deployment. But it does mean the R4 Pro is not a drop-in for someone who wanted "a fast little Linux box." It wants to be a network appliance, and it is happiest when you let it.&lt;/p&gt;
&lt;h3&gt;Performance: How Do You Benchmark a Router?&lt;/h3&gt;
&lt;p&gt;My standard SBC benchmark is a from-clean Rust compilation of a moderately large &lt;a href="https://github.com/ajokela/ballistics-engine"&gt;ballistics-simulation engine&lt;/a&gt;, run three times and averaged. It is a great CPU-and-I/O stress test for a compute board. It is also completely inapplicable here: the R4 Pro has no Rust toolchain, no C compiler in the base image, and a 42 MB writable overlay that could not hold the dependency tree, let alone the build artifacts. Installing a cross-toolchain onto router flash to run a compile benchmark would be measuring the wrong thing anyway.&lt;/p&gt;
&lt;p&gt;So I measured what actually determines whether this board is good at its job: raw CPU throughput in a portable benchmark, cryptographic performance (the workload of a bastion, a VPN endpoint, or a TLS proxy), network throughput, and thermals under load. Every number below was measured on the board as configured, running its shipping OpenWRT image.&lt;/p&gt;
&lt;h4&gt;CPU: CoreMark&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/eembc/coremark"&gt;CoreMark&lt;/a&gt; is the portable, compiler-neutral-ish CPU benchmark that actually runs on embedded systems. Installed straight from the OpenWRT feed (built with GCC 13.3, &lt;code&gt;-O3 -flto&lt;/code&gt;), the R4 Pro turned in:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CoreMark run&lt;/th&gt;
&lt;th&gt;Iterations/sec&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Single thread&lt;/td&gt;
&lt;td&gt;9,533&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Four threads (aggregate)&lt;/td&gt;
&lt;td&gt;37,952&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Two things stand out. First, multi-core scaling is essentially perfect - 37,952 is 99.5% of 4x the single-thread figure, which tells you the four A73 cores are genuinely independent and the memory subsystem is not choking under parallel load. Second, the per-clock efficiency is good: 9,533 CoreMark at 1.8 GHz works out to about &lt;strong&gt;5.3 CoreMark/MHz&lt;/strong&gt;, a strong showing for a Cortex-A73 and comfortably ahead of the A53-class cores that populate cheaper networking boards.&lt;/p&gt;
&lt;p&gt;How does that translate against the compute boards I have benchmarked? Honestly - and this is the important framing - the R4 Pro would land near the back of that pack for a heavy parallel workload. My compile-benchmark table runs from the Orange Pi 5 Max (RK3588, eight A76/A55 cores, 62 seconds) down through the Banana Pi CM5-Pro (eight A72/A53 cores, 167 seconds) to the Horizon X3 CM (four A53 at 1.5 GHz, 379 seconds). Four A73 cores at 1.8 GHz sit architecturally between the A72 and A76 on a per-core basis, but with only four cores and a lower clock, the R4 Pro's aggregate compute is modest - comfortably ahead of the A53 boards, well behind the eight-core A76 machines. That is completely fine. A router does not compile Linux kernels for a living; it forwards packets and terminates crypto, and it has plenty of CPU for both.&lt;/p&gt;
&lt;h4&gt;Cryptography: The Metric That Actually Matters&lt;/h4&gt;
&lt;p&gt;For a board destined to be a bastion, a VPN concentrator, or a TLS reverse proxy, cryptographic throughput is the benchmark that decides whether it can do its job at line rate. I ran &lt;code&gt;openssl speed&lt;/code&gt; (OpenSSL 3.0.21 from the feed) across the ciphers a real deployment uses. Figures below are for 16 KB blocks, the regime that matters for bulk transfer:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Algorithm&lt;/th&gt;
&lt;th&gt;Throughput&lt;/th&gt;
&lt;th&gt;Hardware accelerated?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AES-128-CBC&lt;/td&gt;
&lt;td&gt;1,432 MB/s (~11.5 Gb/s)&lt;/td&gt;
&lt;td&gt;Yes (ARMv8 AES)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AES-128-GCM&lt;/td&gt;
&lt;td&gt;1,190 MB/s (~9.5 Gb/s)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AES-256-GCM&lt;/td&gt;
&lt;td&gt;1,023 MB/s (~8.2 Gb/s)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SHA-256&lt;/td&gt;
&lt;td&gt;917 MB/s (~7.3 Gb/s)&lt;/td&gt;
&lt;td&gt;Yes (ARMv8 SHA2)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ChaCha20-Poly1305&lt;/td&gt;
&lt;td&gt;310 MB/s (~2.5 Gb/s)&lt;/td&gt;
&lt;td&gt;No (software)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RSA-2048 sign / verify&lt;/td&gt;
&lt;td&gt;195 / 7,395 ops/s&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ECDSA P-256 sign / verify&lt;/td&gt;
&lt;td&gt;8,774 / 3,206 ops/s&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The headline: &lt;strong&gt;the R4 Pro can encrypt AES-256-GCM at over a gigabyte per second&lt;/strong&gt;, which is more than 8 Gb/s of authenticated encryption. That is enough to terminate a fully saturated 10-gigabit TLS or IPsec/AES tunnel on the CPU, before you even reach for the SoC's dedicated crypto offload. The ARMv8 AES and SHA-2 instructions are doing exactly what they were added to the architecture to do.&lt;/p&gt;
&lt;p&gt;The most instructive line in that table is ChaCha20-Poly1305. At 310 MB/s it is more than &lt;em&gt;four times slower&lt;/em&gt; than AES-256-GCM - not because ChaCha is a bad cipher, but because the A73 has no hardware acceleration for it, so it runs in software while AES rides dedicated instructions. This has a concrete deployment consequence: &lt;strong&gt;WireGuard uses ChaCha20-Poly1305 exclusively.&lt;/strong&gt; So on this board, a WireGuard tunnel tops out around 2.5 Gb/s of crypto, while an AES-GCM-based tunnel (IPsec, OpenVPN in AES mode, or TLS) can push three to four times that. For most home and small-office links - where the WAN is a gigabit or two - 2.5 Gb/s of WireGuard is still far more than the pipe, so it is academic. But if you were dreaming of the R4 Pro saturating a 10G link over WireGuard specifically, temper that expectation; point it at AES instead, or lean on hardware offload.&lt;/p&gt;
&lt;p&gt;The asymmetric numbers (RSA and ECDSA) govern TLS handshake rate and SSH key exchange. 8,774 ECDSA P-256 signatures per second is plenty for a bastion fronting a fleet - you are gated by human login rate, not the CPU. RSA-2048 signing at 195/s is unremarkable but, again, not a bottleneck for anything short of a busy public web frontend.&lt;/p&gt;
&lt;h4&gt;Network Throughput&lt;/h4&gt;
&lt;p&gt;I measured real network throughput with &lt;code&gt;iperf3&lt;/code&gt; between the R4 Pro and another fleet host on the same LAN segment. An important caveat up front: while the board's &lt;code&gt;eth0&lt;/code&gt; and &lt;code&gt;eth2&lt;/code&gt; MACs are 10-gigabit capable, my deployment wiring negotiated both the LAN and WAN bridges at 1 Gb/s - the R4 Pro was standing in for an older gigabit box, on gigabit infrastructure. So these numbers reflect gigabit wiring, not the board's 10G ceiling.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Direction&lt;/th&gt;
&lt;th&gt;Throughput&lt;/th&gt;
&lt;th&gt;Retransmits&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Download (board receives)&lt;/td&gt;
&lt;td&gt;933 Mb/s&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Upload (board transmits)&lt;/td&gt;
&lt;td&gt;720 Mb/s&lt;/td&gt;
&lt;td&gt;3,372&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The receive path hits gigabit line rate cleanly - 933 Mb/s with zero retransmissions is about as good as TCP over a 1 Gb link gets. The transmit path came in lower at 720 Mb/s with a burst of retransmissions on a single stream, which is the kind of thing that usually improves with parallel streams or a little TCP tuning, and which I would not read too much into on a router whose fast path is hardware-offloaded routing rather than host-terminated TCP. The meaningful takeaway is that the R4 Pro handles gigabit host traffic without breaking stride, and its silicon is built to go far beyond that once you feed it 10-gigabit optics and peers - the ports and PPE offload are there; my wiring simply was not.&lt;/p&gt;
&lt;h4&gt;Thermals&lt;/h4&gt;
&lt;p&gt;Routers are frequently fanless, sealed, and left running for years, so sustained thermal behavior is a real concern. Here the R4 Pro was almost boring, in the best way. Idle SoC temperature read 48.9 C. After hammering all four cores flat-out through the CoreMark and OpenSSL runs, it reached 50.3 C - a 1.4-degree rise under full load. That is the signature of a chip with power headroom to spare and a competent thermal solution; there is no throttling drama here, and a passively cooled deployment is entirely realistic.&lt;/p&gt;
&lt;h4&gt;A Note on Memory and Storage Benchmarks&lt;/h4&gt;
&lt;p&gt;I tried to characterize memory and flash bandwidth with &lt;code&gt;dd&lt;/code&gt;, but BusyBox's &lt;code&gt;dd&lt;/code&gt; lacks the flags (&lt;code&gt;conv=fdatasync&lt;/code&gt;) my normal harness relies on, and the persistent storage is NAND-with-overlay rather than a benchmarkable block device. The practical picture is straightforward: 8 GB of LPDDR4 is ample working memory for a router, &lt;code&gt;/tmp&lt;/code&gt; is RAM-fast, and the flash is small-but-adequate firmware storage rather than a performance surface. If you need real bulk storage, that is what the M.2 slot is for.&lt;/p&gt;
&lt;h3&gt;The Bastion Build: What I Actually Did With It&lt;/h3&gt;
&lt;p&gt;Benchmarks characterize a board; deployment reveals it. I put the R4 Pro into production as the replacement for an aging Linux box that had been serving two roles: an internet-facing SSH bastion and a TLS reverse proxy for a handful of internal services.&lt;/p&gt;
&lt;p&gt;The topology is a classic small-infrastructure setup. The board carries two addresses at once: a WAN address (192.168.3.21) on the &lt;code&gt;br-wan&lt;/code&gt; bridge facing the fiber modem, and a LAN address (10.1.1.13) on &lt;code&gt;br-lan&lt;/code&gt; facing the internal network. The fiber modem port-forwards inbound SSH from its public IP to the R4 Pro's WAN address, and forwards ports 80 and 443 there as well. From the board, two very different things happen to those flows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Port 22&lt;/strong&gt; is the bastion login. It terminates on the board's own OpenSSH daemon - there is no proxy in front of it. This is the front door to the whole fleet.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ports 80 and 443&lt;/strong&gt; hit nginx running on the board, which reverse-proxies each virtual host to the correct backend on the 10.1.1.0/24 network - a web app on one machine, an LLM inference endpoint (a token-gated proxy in front of Ollama) on another.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Standing this up on OpenWRT was instructive about the board's strengths and its one real weakness.&lt;/p&gt;
&lt;p&gt;On the strengths side, the security posture came together cleanly. I hardened SSH to OpenSSH only, disabling the default &lt;code&gt;dropbear&lt;/code&gt; daemon, enforcing key-only authentication, and setting &lt;code&gt;root&lt;/code&gt; to &lt;code&gt;prohibit-password&lt;/code&gt;. The &lt;code&gt;fw4&lt;/code&gt;/nftables WAN zone got an input policy of DROP with explicit allows only for 22, 80, and 443. nginx installed straight from the feed and proxied TLS to internal backends without fuss. The 8 GB of RAM meant I never thought about memory, and the crypto numbers above meant TLS termination was never going to be a bottleneck. As a hardened, internet-facing appliance, the R4 Pro is squarely in its element - this is the job the hardware and firmware were built for.&lt;/p&gt;
&lt;p&gt;The one genuine frustration was WireGuard. I wanted to migrate an existing WireGuard hub onto the board, and hit a wall: the stock 6.6.93 kernel in the shipping image did not include WireGuard support, and the &lt;code&gt;kmod-wireguard&lt;/code&gt; package from the feed was built against a different kernel revision and refused to load. This is a recurring hazard with vendor OpenWRT images - the kernel is pinned, the module ABI is strict, and out-of-tree kmods have to match exactly. It is fixable (a matching image build, or a sysupgrade to a community build with WireGuard baked in), but it is the sort of speed bump that reminds you that you are on a vendor router image, not a rolling general-purpose distro. Given the ChaCha20 software-crypto ceiling I measured, WireGuard was never going to be this board's showcase feature anyway - but the inability to even load the module out of the box is worth knowing before you plan around it.&lt;/p&gt;
&lt;p&gt;Everything else I asked of it, the R4 Pro did without complaint, and it has been a quietly reliable piece of infrastructure since.&lt;/p&gt;
&lt;h3&gt;Comparative Context&lt;/h3&gt;
&lt;p&gt;The R4 Pro does not slot neatly into my usual comparison table, because nothing else I have reviewed is a router. The closest relative in my fleet is the &lt;a href="https://amzn.to/4prBx7a"&gt;Banana Pi R2 Pro&lt;/a&gt; (Rockchip RK3568), a previous-generation dual-gigabit router board - but the R4 Pro is a generation and a tier above it in every networking dimension: quad A73 instead of quad A55, 10-gigabit MACs instead of gigabit, WiFi 7 instead of WiFi 5, and hardware offload built for far higher throughput.&lt;/p&gt;
&lt;p&gt;Against the compute SBCs, the comparison is really about &lt;em&gt;category&lt;/em&gt;, not winner-and-loser. A Raspberry Pi 5 or an Orange Pi 5 Max will compile code two to six times faster than the R4 Pro's CPU could, and they cost less. But neither has 10-gigabit MACs, a hardware packet-processing engine, a bank of switch ports, SFP+ cages, or router firmware. You would spend more time and money bolting networking onto a Pi 5 than you would save on the sticker price, and you would still not have a hardware fast path. Conversely, if your workload is desktop use, AI inference, or software builds, the R4 Pro is the wrong tool and its A73 cluster will feel slow. These boards are not substitutes; they are answers to different questions.&lt;/p&gt;
&lt;p&gt;On price, the R4 Pro commands a substantial premium over general-purpose SBCs, and over the base BPI-R4 as well - street pricing varies by region and configuration but lands well north of a typical Raspberry Pi, reflecting the 10-gigabit silicon, the switch, and the WiFi 7 radio rather than the CPU. If you need what it offers, it is reasonable value for a 10G-capable OpenWRT platform with this much RAM. If you do not, none of that premium is buying you anything.&lt;/p&gt;
&lt;h3&gt;Use Cases and Recommendations&lt;/h3&gt;
&lt;h4&gt;Choose the BPI-R4 Pro if you:&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Are building a serious home or small-office router or firewall.&lt;/strong&gt; This is the core use case and the board excels at it: OpenWRT, hardware NAT offload, 10-gigabit uplinks, a switch, generous RAM, and a CPU with power to spare for firewall and userspace services. It is a genuine alternative to a commercial 10G router or a x86 firewall appliance, at lower power.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Want a hardened, internet-facing bastion or reverse proxy.&lt;/strong&gt; I proved this one in production. Key-only SSH, an nftables WAN zone, nginx TLS termination, and over a gigabyte-per-second of hardware AES make it an excellent front door - and it sips power doing it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Need 10-gigabit networking on an ARM/OpenWRT platform.&lt;/strong&gt; The MT7988A's dual 10G MACs plus SFP+ cages put line-rate 10G within reach, with the PPE keeping routed traffic off the CPU. Few boards in this class and price bracket offer that.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Value RAM headroom on a router.&lt;/strong&gt; 8 GB is a lot for a networking box. Large connection tables, containerized services (Docker/Podman on OpenWRT), or aggressive caching all become practical.&lt;/p&gt;
&lt;h4&gt;Choose something else if you:&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Want a general-purpose Linux computer.&lt;/strong&gt; The R4 Pro runs OpenWRT with a tiny writable overlay and BusyBox userland. It is a router, not a workstation. A Raspberry Pi 5, Orange Pi 5 Max, or the Banana Pi CM5-Pro will be far happier and far faster at desktop, development, and compute tasks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Are prioritizing raw CPU throughput or AI inference.&lt;/strong&gt; Four A73 cores at 1.8 GHz are modest, and there is no NPU. The compute-focused boards in my reviews leave it well behind on those axes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Specifically need line-rate WireGuard.&lt;/strong&gt; The A73's lack of ChaCha20 hardware acceleration caps WireGuard crypto around 2.5 Gb/s, and the stock image did not ship a loadable WireGuard module. Both are surmountable, but if a 10G WireGuard hub is your exact goal, do your homework first - or plan to use an AES-based tunnel.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Want a plug-and-play, beginner-friendly experience.&lt;/strong&gt; OpenWRT, UCI, DSA, and vendor image quirks reward network-literate users. If you have never configured a firewall zone, the learning curve is real.&lt;/p&gt;
&lt;h3&gt;Build Quality and Physical Characteristics&lt;/h3&gt;
&lt;p&gt;The R4 Pro is a well-built router-class board with the port density and connector variety its role demands. The SFP+ cages, M.2 slot, multiple Ethernet jacks, and header banks are laid out sensibly, and the onboard USB-C serial console is a thoughtful touch for a device that will inevitably lock you out over the network at least once. My unit arrived cleanly manufactured with no obvious defects, and as the thermal numbers show, it runs cool enough for fanless or lightly-cooled enclosures. As always with a board that carries 10-gigabit PHYs and a switch, plan for some airflow if you enclose it under sustained heavy load - but my measurements suggest thermal headroom is not a concern at typical router duty cycles.&lt;/p&gt;
&lt;h3&gt;Conclusion: The Right Tool, Precisely Aimed&lt;/h3&gt;
&lt;p&gt;The Banana Pi BPI-R4 Pro is not trying to be a fast little Linux PC, and judging it as one would miss the point entirely. It is a purpose-built 10-gigabit router platform, and by that measure it is very good. The MT7988A pairs a modest-but-efficient quad Cortex-A73 with the networking silicon that matters - dual 10G MACs, hardware NAT offload, a switch, and a crypto engine that pushes AES-256-GCM past a gigabyte per second. It runs real router firmware, it carries a generous 8 GB of RAM, it stays cool under full load, and - as I proved by putting it into production - it makes an excellent hardened bastion and TLS reverse proxy.&lt;/p&gt;
&lt;p&gt;Its limitations are the honest limitations of its category. The CPU is slow for compute work; OpenWRT and a tiny overlay make it a poor general-purpose machine; ChaCha20 runs in software, so WireGuard specifically will not saturate 10G; and vendor-image kernel pinning bit me when I tried to add an out-of-tree module. None of these are surprises once you accept what the board is: a network appliance, not a computer you happen to network with.&lt;/p&gt;
&lt;p&gt;If you are building a router, a firewall, a 10-gigabit homelab core, or an internet-facing bastion, the BPI-R4 Pro deserves a hard look - it delivers networking capability and cryptographic throughput that are genuinely hard to find at its price and power envelope on an ARM/OpenWRT platform. If you want to compile code, run models, or use a desktop, buy a compute board instead. The R4 Pro knows exactly what it is, and so should you before you buy one.&lt;/p&gt;
&lt;h3&gt;Specifications Summary&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Processor:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MediaTek MT7988A (Filogic 880)&lt;/li&gt;
&lt;li&gt;4x ARM Cortex-A73 @ 1.8 GHz&lt;/li&gt;
&lt;li&gt;ARMv8 crypto extensions: AES, PMULL, SHA-1, SHA-2, CRC32&lt;/li&gt;
&lt;li&gt;Hardware packet-processing engine (NAT/flow offload)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Memory &amp;amp; Storage:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;8 GB RAM (~7.76 GiB usable) - BPI-R4-PRO-8X unit&lt;/li&gt;
&lt;li&gt;Onboard NAND (squashfs + UBI overlay) + eMMC&lt;/li&gt;
&lt;li&gt;M.2 slot for expansion storage&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Networking:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2x 10 Gigabit Ethernet MACs (SFP+), 1x Gigabit Ethernet&lt;/li&gt;
&lt;li&gt;External MaxLinear switch (DSA), multiple gigabit/multi-gig ports&lt;/li&gt;
&lt;li&gt;WiFi 7 (BE14, MediaTek mt76)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Software:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OpenWRT 24.10, Linux kernel 6.6.93 (aarch64)&lt;/li&gt;
&lt;li&gt;opkg feeds (9,695 packages), UCI config, fw4/nftables, BusyBox userland&lt;/li&gt;
&lt;li&gt;USB-C serial console (115200 baud)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Benchmark Performance (as-shipped OpenWRT image):&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CoreMark: 9,533 single-thread / 37,952 four-thread (5.3 CoreMark/MHz, ~99% scaling)&lt;/li&gt;
&lt;li&gt;AES-256-GCM: 1,023 MB/s; AES-128-CBC: 1,432 MB/s; SHA-256: 917 MB/s (all HW-accelerated)&lt;/li&gt;
&lt;li&gt;ChaCha20-Poly1305: 310 MB/s (software)&lt;/li&gt;
&lt;li&gt;RSA-2048: 195 sign/s, 7,395 verify/s; ECDSA P-256: 8,774 sign/s&lt;/li&gt;
&lt;li&gt;iperf3 (gigabit wiring): 933 Mb/s RX, 720 Mb/s TX&lt;/li&gt;
&lt;li&gt;Thermal: 48.9 C idle, 50.3 C under full four-core load&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Recommendation:&lt;/strong&gt; An excellent 10-gigabit OpenWRT router and hardened-bastion platform; not recommended as a general-purpose or compute SBC.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Review Date: July 16, 2026&lt;/p&gt;
&lt;p&gt;Hardware Tested: Banana Pi BPI-R4-PRO-8X (MediaTek MT7988A) with 8 GB RAM, deployed as SSH bastion + nginx reverse proxy&lt;/p&gt;
&lt;p&gt;OS Tested: OpenWRT 24.10 (BPI-R4Pro-8X-BE14-MT76 image), Linux kernel 6.6.93&lt;/p&gt;
&lt;p&gt;Conclusion: A purpose-built 10-gigabit router board that excels at networking and cryptographic workloads and makes a superb hardened bastion - reviewed, and recommended, as the network appliance it is rather than the compute SBC it is not.&lt;/p&gt;</description><category>10 gigabit ethernet</category><category>armv8 crypto extensions</category><category>banana pi</category><category>bastion host</category><category>benchmarks</category><category>bpi-r4 pro</category><category>coremark</category><category>cortex-a73</category><category>crypto acceleration</category><category>filogic 880</category><category>hardware review</category><category>mediatek mt7988a</category><category>nftables</category><category>openwrt</category><category>reverse proxy</category><category>router</category><category>single board computers</category><category>wireguard</category><guid>https://tinycomputers.io/posts/banana-pi-bpi-r4-pro-review.html</guid><pubDate>Fri, 17 Jul 2026 00:46:59 GMT</pubDate></item><item><title>The Tunnel Was the Easy Part: Site-to-Site WireGuard Between My House and a VPC</title><link>https://tinycomputers.io/posts/the-tunnel-was-the-easy-part-site-to-site-wireguard-home-to-vpc.html?utm_source=feed&amp;utm_medium=rss&amp;utm_campaign=rss</link><dc:creator>A.C. Jokela</dc:creator><description>&lt;div class="audio-widget"&gt;
&lt;div class="audio-widget-header"&gt;
&lt;span class="audio-widget-icon"&gt;🎧&lt;/span&gt;
&lt;span class="audio-widget-label"&gt;Listen to this article&lt;/span&gt;
&lt;/div&gt;
&lt;audio controls preload="metadata"&gt;
&lt;source src="https://tinycomputers.io/the-tunnel-was-the-easy-part-site-to-site-wireguard-home-to-vpc_tts.mp3" type="audio/mpeg"&gt;
&lt;/source&gt;&lt;/audio&gt;
&lt;div class="audio-widget-footer"&gt;25 min · AI-generated narration&lt;/div&gt;
&lt;/div&gt;

&lt;h2&gt;The Tunnel Was the Easy Part: Site-to-Site WireGuard Between My House and a VPC&lt;/h2&gt;
&lt;p&gt;For about three years, a PostgreSQL database that runs dozens of projects — portfolio data, home-automation history, a dozen scraped feeds — had its port 5432 listening on the public internet. Not wide open: it sat behind a long password and TLS, the rest of the host was hardened, and nothing had ever gone wrong. But "open to the internet behind a password" is the kind of sentence that sounds fine right up until it doesn't, and I'd been meaning to fix it for months.&lt;/p&gt;
&lt;p&gt;The fix I wanted wasn't a bastion host, and it wasn't a clutch of &lt;code&gt;/32&lt;/code&gt; firewall exceptions I'd have to update every time my home IP changed. I wanted the database to simply &lt;em&gt;not be reachable&lt;/em&gt; from the public internet — and to reach it anyway, from my house, as if the AWS VPC it lives in were just another room in the building.&lt;/p&gt;
&lt;p&gt;That's a site-to-site VPN. And the moment you say "VPN" in 2026 you mean &lt;a href="https://www.wireguard.com/"&gt;WireGuard&lt;/a&gt;, because the alternatives — IPsec, OpenVPN — are heavier, slower, and far more painful to reason about. WireGuard is a few thousand lines of kernel code, a dozen lines of config per side, and a security model you can hold in your head: every peer is a public key, and a packet is either from a key you trust or it's dropped.&lt;/p&gt;
&lt;p&gt;So I expected the WireGuard part to be the work. It wasn't. The WireGuard part took about fifteen minutes and came up on the first handshake. The other day and a half went to an IP-address collision I'd created years earlier, a load-balancing router that kept quietly breaking the tunnel's return path, and the slow realization that &lt;strong&gt;a VPN doesn't connect two networks so much as force you to confront every assumption each of them made while pretending the other didn't exist.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As an aside, my last dabbling with VPNs and such was during my FreeBSD phase in the early 2000s.  To be more precise, February of 2005.  I had an article published in &lt;a href="https://web.archive.org/web/20050718081300/http://ezine.daemonnews.org/200502/ipsec.html"&gt;Daemon News&lt;/a&gt; on using Racoon and X.509 certificates for IPSEC.&lt;/p&gt;
&lt;p&gt;This is the story of the parts that weren't WireGuard.&lt;/p&gt;
&lt;h3&gt;The shape of the thing&lt;/h3&gt;
&lt;p&gt;Two networks. At home, a flat &lt;code&gt;10.1.1.0/24&lt;/code&gt; LAN behind a MikroTik router — laptops, a large handful of Linux hosts, a few BSD hosts, the usual home-lab sprawl. In the cloud, an AWS VPC on &lt;code&gt;10.0.0.0/16&lt;/code&gt; with the database sitting on a private address, &lt;code&gt;10.0.0.76&lt;/code&gt;, that I'd very much like to be the &lt;em&gt;only&lt;/em&gt; way to reach it.&lt;/p&gt;
&lt;p&gt;The naive instinct is to put WireGuard directly on the database host. Don't. The endpoint that terminates a site-to-site tunnel becomes a router — it forwards packets for an entire subnet, runs &lt;code&gt;ip_forward&lt;/code&gt;, owns firewall and NAT rules — and you do not want that responsibility tangled up with your most important stateful service. Give it its own box.&lt;/p&gt;
&lt;p&gt;So the tunnel terminates on a dedicated &lt;strong&gt;gateway instance&lt;/strong&gt;: a &lt;code&gt;t4g.nano&lt;/code&gt; (the smallest Graviton box AWS sells, a few dollars a month, comfortably inside the free tier) with an Elastic IP so the home side always has a fixed address to dial, and — critically — with its EC2 &lt;strong&gt;source/destination check disabled&lt;/strong&gt;, because by default AWS drops any packet whose source or destination isn't the instance itself. A router's entire job is to handle packets addressed to &lt;em&gt;other&lt;/em&gt; machines; leave that check on and the gateway silently swallows everything it's supposed to forward.&lt;/p&gt;
&lt;p&gt;The home end of the tunnel lives on one of my always-on Linux boxes. It dials out — the house is behind NAT and a dynamic-ish IP, so it can't be dialed &lt;em&gt;into&lt;/em&gt; — and the AWS gateway, with its stable Elastic IP, waits to be reached.&lt;/p&gt;
&lt;p&gt;Between them I gave the tunnel its own little address space, a &lt;code&gt;/30&lt;/code&gt; carved out of a range that exists nowhere else in either network: &lt;code&gt;10.99.99.0/30&lt;/code&gt;. The gateway is &lt;code&gt;10.99.99.1&lt;/code&gt;, the house is &lt;code&gt;10.99.99.2&lt;/code&gt;. That deliberate, unused &lt;code&gt;/30&lt;/code&gt; turns out to matter enormously, for reasons that won't be obvious until the routing breaks. Hold onto it.&lt;/p&gt;
&lt;h3&gt;WireGuard, the easy twenty percent&lt;/h3&gt;
&lt;p&gt;Here is essentially the entire VPN. On the AWS gateway:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="k"&gt;[Interface]&lt;/span&gt;
&lt;span class="na"&gt;Address&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;10.99.99.1/30&lt;/span&gt;
&lt;span class="na"&gt;ListenPort&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;51820&lt;/span&gt;
&lt;span class="na"&gt;PrivateKey&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;gateway private key&amp;gt;&lt;/span&gt;
&lt;span class="na"&gt;PostUp&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;iptables -A FORWARD -i wg0 -j ACCEPT&lt;/span&gt;&lt;span class="c1"&gt;; iptables -A FORWARD -o wg0 -j ACCEPT&lt;/span&gt;
&lt;span class="na"&gt;PostDown&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;iptables -D FORWARD -i wg0 -j ACCEPT&lt;/span&gt;&lt;span class="c1"&gt;; iptables -D FORWARD -o wg0 -j ACCEPT&lt;/span&gt;

&lt;span class="k"&gt;[Peer]&lt;/span&gt;
&lt;span class="c1"&gt;# the house&lt;/span&gt;
&lt;span class="na"&gt;PublicKey&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;home public key&amp;gt;&lt;/span&gt;
&lt;span class="na"&gt;AllowedIPs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;10.99.99.2/32&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And on the home box:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="k"&gt;[Interface]&lt;/span&gt;
&lt;span class="na"&gt;Address&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;10.99.99.2/30&lt;/span&gt;
&lt;span class="na"&gt;PrivateKey&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;home private key&amp;gt;&lt;/span&gt;
&lt;span class="na"&gt;PostUp&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE&lt;/span&gt;
&lt;span class="na"&gt;PostDown&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;iptables -t nat -D POSTROUTING -o wg0 -j MASQUERADE&lt;/span&gt;

&lt;span class="k"&gt;[Peer]&lt;/span&gt;
&lt;span class="c1"&gt;# the AWS gateway&lt;/span&gt;
&lt;span class="na"&gt;PublicKey&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;lt;gateway public key&amp;gt;&lt;/span&gt;
&lt;span class="na"&gt;Endpoint&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;203.0.113.10:51820&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="c1"&gt;# the gateway's Elastic IP&lt;/span&gt;
&lt;span class="na"&gt;AllowedIPs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;10.0.0.0/16, 10.99.99.1/32&lt;/span&gt;
&lt;span class="na"&gt;PersistentKeepalive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;25&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;wg-quick up wg0&lt;/code&gt; on each end — or &lt;code&gt;systemctl enable --now wg-quick@wg0&lt;/code&gt; to make it survive reboots — and within a second or two &lt;code&gt;wg show&lt;/code&gt; reports a handshake. Ping &lt;code&gt;10.99.99.1&lt;/code&gt; from the house; it answers. The cryptographic part, the part everyone is nervous about, is done.&lt;/p&gt;
&lt;p&gt;Two fields carry almost all the meaning. &lt;strong&gt;&lt;code&gt;AllowedIPs&lt;/code&gt; is doing double duty&lt;/strong&gt; — it's both an access-control list (packets arriving from this peer are only accepted if their source falls inside it) and a routing table (packets you send to those destinations get encrypted and shipped to this peer). On the home side I list &lt;code&gt;10.0.0.0/16&lt;/code&gt; because that's the VPC I want to reach; that single line installs a route sending all VPC-bound traffic into the tunnel. &lt;strong&gt;&lt;code&gt;PersistentKeepalive = 25&lt;/code&gt;&lt;/strong&gt; is on the home side only, and it's there because the house initiates from behind NAT: without a packet every 25 seconds, the NAT mapping that lets return traffic find its way home expires, and the tunnel goes deaf until the next outbound packet wakes it. The side with the stable public address doesn't need it; the side hiding behind NAT does.&lt;/p&gt;
&lt;p&gt;That's the whole VPN. If your two networks were sensibly addressed and your path were clean, you would be done, and this would be a very short post.&lt;/p&gt;
&lt;h3&gt;The first wall: you can't route to your own house&lt;/h3&gt;
&lt;p&gt;The tunnel was up, but a tunnel is just a wire. For a machine &lt;em&gt;inside&lt;/em&gt; the VPC to send a reply back to my house, the VPC has to know that house-bound packets go to the gateway instance. That's a route-table entry: destination &lt;code&gt;10.1.1.0/24&lt;/code&gt; (my home LAN), target the gateway's network interface. I added it.&lt;/p&gt;
&lt;p&gt;AWS rejected it:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;Route destination doesn't match any subnet CIDR blocks
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It took me an embarrassing minute to understand, and then a much longer one to forgive my past self. Years ago, for reasons that made sense at the time and that I have completely forgotten, I had given this VPC a &lt;em&gt;secondary&lt;/em&gt; CIDR block of &lt;code&gt;10.1.0.0/16&lt;/code&gt;. My home LAN, &lt;code&gt;10.1.1.0/24&lt;/code&gt;, lives entirely inside that &lt;code&gt;/16&lt;/code&gt;. As far as the VPC is concerned, &lt;code&gt;10.1.1.0/24&lt;/code&gt; is &lt;strong&gt;local&lt;/strong&gt; — it's part of the VPC's own address space — and AWS will not let you install a route that's more specific than a local one. You cannot route to a place the network already believes it &lt;em&gt;is&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This is the oldest mistake in site-to-site networking and I'd walked straight into it: &lt;strong&gt;the two address spaces overlapped.&lt;/strong&gt; RFC 1918 hands you &lt;code&gt;10.0.0.0/8&lt;/code&gt; — sixteen million addresses — and the one discipline that makes joining networks painless is to carve home and cloud out of ranges that can never collide. I hadn't. And re-addressing a production VPC, or renumbering my entire house, to fix a self-inflicted overlap was not a Tuesday-afternoon project.&lt;/p&gt;
&lt;p&gt;So I didn't fix the overlap. I routed &lt;em&gt;around&lt;/em&gt; it — and this is where that deliberate, unused &lt;code&gt;/30&lt;/code&gt; earns its place.&lt;/p&gt;
&lt;h3&gt;One address to stand for a whole house&lt;/h3&gt;
&lt;p&gt;Look again at the home config: &lt;code&gt;PostUp = iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE&lt;/code&gt;. That one line is doing the heavy lifting. It tells the home box to &lt;strong&gt;source-NAT every packet leaving the tunnel&lt;/strong&gt; so it appears to originate from the tunnel interface's own address, &lt;code&gt;10.99.99.2&lt;/code&gt;. From the VPC's point of view, my entire house — every laptop, every Linux box, the whole &lt;code&gt;10.1.1.0/24&lt;/code&gt; — collapses into a single client at &lt;code&gt;10.99.99.2&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;And &lt;code&gt;10.99.99.0/30&lt;/code&gt; lives &lt;em&gt;outside&lt;/em&gt; every CIDR the VPC claims. So the route AWS refused to accept for &lt;code&gt;10.1.1.0/24&lt;/code&gt; is perfectly legal for &lt;code&gt;10.99.99.0/30&lt;/code&gt;: destination &lt;code&gt;10.99.99.0/30&lt;/code&gt;, target the gateway interface. Accepted instantly. The VPC never has to route to the contested &lt;code&gt;10.1.1.0/24&lt;/code&gt; at all, because it never &lt;em&gt;sees&lt;/em&gt; &lt;code&gt;10.1.1.0/24&lt;/code&gt; — every reply goes to &lt;code&gt;10.99.99.2&lt;/code&gt;, an address it's allowed to know about, and the gateway tunnels it home.&lt;/p&gt;
&lt;p&gt;The cost of this trick is honesty about its asymmetry. &lt;strong&gt;Masquerade collapses a network into a point, and points don't have interior structure.&lt;/strong&gt; Traffic the &lt;em&gt;house&lt;/em&gt; initiates to the cloud works beautifully; the VPC sees one client and answers it. Traffic the &lt;em&gt;cloud&lt;/em&gt; initiates toward an individual home machine has nowhere to land — to AWS, &lt;code&gt;10.99.99.2&lt;/code&gt; is the whole house, with no way to address the laptop behind it. For my purpose — reaching cloud services from home — that's exactly the direction I needed, and the asymmetry is free. If you need the VPC to open connections &lt;em&gt;into&lt;/em&gt; specific home hosts, you're back to real routes and real subnets, which means back to not overlapping your address spaces in the first place. Pick your ranges so that future-you never has to make this choice.&lt;/p&gt;
&lt;h3&gt;The second wall: a router doing exactly what I told it&lt;/h3&gt;
&lt;p&gt;With routing sorted, the tunnel worked — and then, intermittently, didn't. The handshake was solid. Small things — a ping, a quick &lt;code&gt;psql&lt;/code&gt; connection — went through. But sessions would stall, and throughput was a coin flip. The classic shape of a working control path and a broken data path, which usually screams MTU (more on that later). It wasn't MTU. It was my own router being obedient.&lt;/p&gt;
&lt;p&gt;My house has two internet connections, load-balanced on the MikroTik with per-connection-classifier (PCC) mangle rules — the standard dual-WAN recipe that pins each new connection to one uplink or the other so a single flow doesn't get split across two public IPs. Which is the entire problem. WireGuard is one long-lived UDP flow to a fixed &lt;code&gt;Endpoint&lt;/code&gt;. The PCC rules looked at the tunnel's packets, classified them like any other connection, and cheerfully load-balanced them — sending some out WAN 1 and some out WAN 2. But the gateway had completed its handshake with whichever public IP came first, and a reply arriving from the &lt;em&gt;other&lt;/em&gt; WAN's address didn't match the cryptographic session. WireGuard, correctly, dropped it.&lt;/p&gt;
&lt;p&gt;The fix is a single exemption at the top of the mangle chain: traffic to or from the WireGuard endpoint is &lt;strong&gt;accepted before the PCC rules ever see it&lt;/strong&gt;, pinning the entire tunnel to one WAN. In MikroTik terms, a mangle rule matching the WireGuard port and destination with an &lt;code&gt;action=accept&lt;/code&gt; ahead of the connection-marking rules. The moment that landed, the flakiness vanished.&lt;/p&gt;
&lt;p&gt;The lesson generalizes past MikroTik: &lt;strong&gt;a VPN tunnel over a multi-WAN uplink must be pinned to one path.&lt;/strong&gt; And the deeper lesson is the one I keep relearning — this bug didn't come from the new thing. The router was doing precisely what I had configured it to do, years ago, for a completely unrelated reason. The tunnel just walked into a decision that had been sitting there, correct and forgotten, waiting for a single long-lived UDP flow to make it wrong.&lt;/p&gt;
&lt;h3&gt;Making it useful: split-horizon DNS&lt;/h3&gt;
&lt;p&gt;A tunnel that routes private addresses is only half a win. My home cron jobs and scripts didn't connect to &lt;code&gt;10.0.0.76&lt;/code&gt;; they connected to a hostname, and that hostname resolved — on the public internet, as it must — to the database's &lt;em&gt;public&lt;/em&gt; address. Routing it privately is pointless if the name still points at the front door.&lt;/p&gt;
&lt;p&gt;The answer is split-horizon DNS: the same name resolving differently depending on where you're standing. On the LAN I want &lt;code&gt;db.example.net&lt;/code&gt; to mean &lt;code&gt;10.0.0.76&lt;/code&gt;; everywhere else it should keep meaning the public address. Two small overrides do it. A static DNS entry on the MikroTik (&lt;code&gt;db.example.net → 10.0.0.76&lt;/code&gt;) catches anything using the router as its resolver, and a matching &lt;code&gt;local-data&lt;/code&gt; line in the Unbound instance that actually serves the &lt;code&gt;10.1.1.0/24&lt;/code&gt; segment catches the rest:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;local-zone: "db.example.net." transparent
local-data: "db.example.net. IN A 10.0.0.76"
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now every home machine reaches the database over the tunnel, by name, with &lt;strong&gt;zero application changes&lt;/strong&gt; — the code doesn't know or care that the address it gets back is private. The backup job that used to copy dumps across the public internet now runs entirely inside the tunnel.&lt;/p&gt;
&lt;p&gt;One caveat worth stating because it bit me for ten confused minutes: split-horizon is &lt;em&gt;per resolver&lt;/em&gt;. My main workstation sits on a different subnet — &lt;code&gt;192.168.0.x&lt;/code&gt;, behind a different router with a different upstream resolver — and it never sees the LAN's Unbound, so it kept resolving the public address and connecting the old way. A DNS horizon only covers the clients that actually ask the resolver you edited. Map your resolvers before you trust the trick.&lt;/p&gt;
&lt;h3&gt;The gotcha everyone eventually hits: MTU&lt;/h3&gt;
&lt;p&gt;I promised to come back to MTU, because it is the single most common way a WireGuard tunnel "works but doesn't," and the symptom is exactly the one I &lt;em&gt;thought&lt;/em&gt; the dual-WAN bug was: the handshake succeeds, small requests fly, and then a large transfer hangs forever with no error anywhere.&lt;/p&gt;
&lt;p&gt;WireGuard wraps every packet in roughly 60–80 bytes of encapsulation, which drops the usable MTU inside the tunnel to about 1420 bytes. On a clean path this is invisible. On a path that drops oversized packets without sending the ICMP "fragmentation needed" message back — and plenty of the internet does, because plenty of the internet firewalls ICMP into oblivion — full-size data packets vanish silently while the small control packets sail through. The cure is &lt;strong&gt;MSS clamping&lt;/strong&gt;: an &lt;code&gt;iptables&lt;/code&gt; rule on the forwarding chain that rewrites the TCP maximum-segment-size of passing connections down to fit the tunnel.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
  -j TCPMSS --clamp-mss-to-pmtu
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In the interest of honesty: my home-to-AWS tunnel didn't need this. A 330 MB database dump rsync'd across it on the first attempt, full speed, no clamp. But a &lt;em&gt;second&lt;/em&gt; tunnel I stood up later — a cloud-to-cloud hop over a different path, joining a second provider into the same private fabric — reproduced the "connects fine, transfers hang" symptom perfectly, and the clamp on both ends fixed it instantly. It's path-dependent, which is exactly why it's so maddening to diagnose. Reach for it the instant authentication succeeds but bulk data stalls; it is cheap insurance and an even cheaper first guess.&lt;/p&gt;
&lt;h3&gt;What it bought&lt;/h3&gt;
&lt;p&gt;With the tunnel carrying traffic and the name resolving privately, the actual goal became a one-line change. The database's security group dropped its &lt;code&gt;0.0.0.0/0&lt;/code&gt; rule on 5432 and replaced it with the gateway and the VPC. The port that had been answering the entire internet for a year now answers exactly one place: the wire to my house. The home backup runs over the private path. My laptop talks to the database as if AWS were a closet down the hall.&lt;/p&gt;
&lt;p&gt;The bill for all of this is a &lt;code&gt;t4g.nano&lt;/code&gt; and an Elastic IP — call it a few dollars a month, much of it inside the free tier — plus a &lt;code&gt;/30&lt;/code&gt; of address space and a handful of config lines that now live in version control next to everything else I'd need to rebuild the setup from scratch. And the VPC stopped being a remote, walled-off place I reach through public endpoints. It became a routable extension of my own network, which changes what feels reasonable to build there next.&lt;/p&gt;
&lt;h3&gt;The tunnel was the easy part&lt;/h3&gt;
&lt;p&gt;Add it up and the proportions are absurd. The WireGuard configuration — the cryptography, the key exchange, the part that is genuinely hard to get right and that brilliant people spent years making bulletproof — was a dozen lines per side and worked on the first handshake. Every hour after that went to problems that had nothing to do with WireGuard: an address-space overlap I'd authored years earlier and forgotten, a load-balancing router executing old instructions faithfully into a new failure, a DNS horizon that stopped at a subnet boundary, an MTU gremlin lying in wait on the next path over.&lt;/p&gt;
&lt;p&gt;None of those are VPN problems. They're the problems a VPN &lt;em&gt;reveals&lt;/em&gt;. Two networks each grew up as the center of their own universe — chose their address ranges, their routing policies, their resolvers — with no expectation of ever meeting. The tunnel is just the introduction. Everything afterward is the negotiation between two designs that were each, locally, perfectly correct, and that turn out to disagree the moment you ask them to share a routing table.&lt;/p&gt;
&lt;p&gt;So the practical advice compresses to almost nothing. Give the tunnel its own box and its own unused &lt;code&gt;/30&lt;/code&gt;. Keep a keepalive on the side behind NAT. Pin the flow to one WAN if you have more than one. Clamp the MSS the moment large transfers stall. And, above all the rest: &lt;strong&gt;choose your private address ranges, on day one, so that home and cloud can never collide&lt;/strong&gt; — because the fifteen minutes WireGuard costs you is real, and so is the day and a half that an overlapping &lt;code&gt;/16&lt;/code&gt; will cost you somewhere down the line, long after you've forgotten you created it.&lt;/p&gt;
&lt;p&gt;The encryption is solved. The hard part was never the secret. The hard part was the addresses.&lt;/p&gt;</description><category>aws</category><category>home lab</category><category>infrastructure</category><category>iptables</category><category>mikrotik</category><category>mtu</category><category>nat</category><category>networking</category><category>routing</category><category>self-hosting</category><category>site-to-site</category><category>split-horizon dns</category><category>vpc</category><category>vpn</category><category>wireguard</category><guid>https://tinycomputers.io/posts/the-tunnel-was-the-easy-part-site-to-site-wireguard-home-to-vpc.html</guid><pubDate>Mon, 15 Jun 2026 21:30:00 GMT</pubDate></item></channel></rss>