Fed up with VPN providers, I wrote my own server
I did not set out to write a VPN server. I set out to stop being annoyed.
I was paying for a VPN. Then my provider changed its terms suddenly, and customer support was no help in dealing with it. I looked at other providers and did not find one I was satisfied with either.
What I needed was modest. Some sites in Ukraine and Germany only work properly from inside those countries, and I am not always inside them. I did not need thousands of servers or a smart app. I needed a tunnel that answered to me, on terms that could not change overnight.
So on 28 May 2026 I made the first commit to just-another-vpn-server.
Why speak OpenVPN
The easy way to make a VPN is to invent a small private protocol. Then you also own every client, on every device, forever.
Speaking the OpenVPN wire protocol avoids that. A stock openvpn client, Tunnelblick and OpenVPN Connect already exist, and none of them should need to know that the other end is a few thousand lines of Rust. It also gives the project an unforgiving definition of done: either an unmodified client connects, or it does not.
"Written from the wire format up" means the protocol, not the cryptography. Packet framing, the reliability layer, the key exchange, sessions and rekeying are in this codebase. TLS comes from rustls, and the ciphers come from established crates. I wanted a VPN server, not my own crypto.
The first week
The first commit was already large: 31 files and about 6,900 lines, including a Docker harness that tests against a real OpenVPN client. Claude is credited as co-author on 19 of the 29 commits, including the fixes below. The hard part started when real clients on real machines connected to it.
Linux went first. Writes to the TUN device failed with EINVAL, and the cause turned out to be two separate things. The packet-information header behaved unreliably across versions of the tun crate, so the server now handles those four bytes explicitly. And OpenVPN Connect sends internal messages over the data channel that are not IP packets at all. Writing one to the TUN device failed and killed the writer task, so they are now dropped before that point.
Then something less dramatic: every restart added its iptables rules again, without removing the old ones. Nothing broke at first. There were just more and more duplicates.
The day macOS said no
Full-tunnel mode on macOS took a whole day and is my favourite part of the history. On 4 June:
redirect-gateway def1seemed to send traffic the wrong way, because the client added a bypass route for the gateway through the physical interface. So I replaced it with explicit/1routes.- That was not enough, so I switched to point-to-point style addressing.
- That was not it either, and the third attempt of the day went back to the standard subnet format.
The real failure was a chain. An explicit pushed subnet route triggered Tunnelblick's loop protection, which made routing setup fail and retry for over a minute. Meanwhile the server saw no traffic, reached its idle timeout and evicted the session. The client then kept sending packets with a peer_id that no longer existed.
So the fix was mostly to stop being clever: send what a normal OpenVPN server sends, stop pushing that extra route, and give the idle timeout more headroom. When your job is to be compatible, the standard behaviour is usually standard for a reason.
What it is now
Version 0.1.8 does what I set out to build, and a bit more:
- UDP and TCP transport, with mutual TLS on the control channel
- AES-256-GCM on the data channel, with a replay window
tls-authandtls-crypt- Rekeying from either side without dropping packets
- Address pools, IPv6 in the tunnel, NAT and per-client settings
OpenVPN has no standards body behind it, so compatibility here means working with the real thing. It is verified end to end against OpenVPN 2.6.14, and there is a script for overnight stability runs. The repository has CI, a dependency security audit and release builds. The audit has already earned its place: the latest commits update a TLS dependency to fix published security advisories.
It also has limits, and the README lists them:
- It has not been security-audited.
- Authentication is by client certificate only.
- AES-256-GCM is the only data-channel cipher.
- NAT and IPv6 in the tunnel are Linux-only.
It suits self-hosting and learning. It is not a hardened replacement for the OpenVPN daemon, and I would not suggest anyone treat it as one.
Using it
This is the part that makes me happiest. When I am abroad, I connect with an ordinary OpenVPN client and reach the location-limited sites in Ukraine and Germany I built it for, through a server I run myself.
No provider can change the terms on me now. The trade-off is that I have also become customer support, and that department only answers when it feels like it. At least I know who to complain to.
The code is on GitHub, under the MIT licence.