Back to Blog

Why Go Is a Good Fit for VPN and Remote Desktop Tools

#golang#vpn#security
Why Go Is a Good Fit for VPN and Remote Desktop Tools

Alongside the desktop app I write in Avalonia/C#, most of the networking logic across my projects actually gets written in Go. Not because I’m a fanboy of any particular language — more because every time I’ve compared them head to head, Go is the easiest fit for this specific use case: lots of connections, needs to be reliable, needs to be easy to deploy onto machines I don’t fully control.

Goroutines make concurrency feel “free”

The most concrete reason: goroutines and channels make writing code that handles many simultaneous connections — VPN tunnels, remote desktop sessions, or just a proxy — much easier than thread-based concurrency in other languages. I once tried prototyping a similar feature in C# using plain Task and async/await, and it worked functionally, but once the number of simultaneous connections went up (I stress-tested around 500 fake connections at once), the overhead of .NET’s thread pool started showing, and I had to start reaching for SemaphoreSlim for manual throttling. The Go version, with the same number of goroutines, needed no extra tuning at all — the scheduler is built for this exact workload pattern from the ground up.

A single binary isn’t a small feature

Go compiles down to a single binary with no extra runtime dependencies. For tools that need to be deployed across different client environments — sometimes an old Ubuntu VPS, sometimes a Windows Server locked down by IT policy — that matters a lot. No worrying about which .NET runtime version is installed, no pushing an installer that fails because a dependency is missing. scp one file, chmod +x, done.

A standard library that’s already “enough”

Go’s standard library is also solid for networking — net, crypto/tls, down to raw socket handling — so there’s not much need for external dependencies for the basics. That reduces the attack surface, which matters a lot for tools dealing with secure connections like VPNs. After writing a few checklists about npm supply-chain incidents lately, I’ve gotten more aware of this: the fewer external dependencies I install in tools that touch sensitive connections, the less I have to worry about whenever a new advisory shows up.

The trade-off I actually feel

Go’s error handling is fairly verbose — lots of repeated if err != nil in nearly every function, and coming from C#’s exceptions, it felt like a step backward at first. Generics are also still relatively new compared to other languages, so some patterns I’m used to from C# (complex generic constraints) have to get rewritten more verbosely, or fall back to interface{}/any, which is less type-safe.

But for networking tools that need to be reliable and easy to deploy onto machines I don’t always control, Go remains my first choice. The verbosity is a price I’m willing to pay for not having to think about runtime dependencies every time I deploy to a new server.