Forget VPNs. The Real Problem Is Remote Router Management

When I started building TunGuard, the goal was simple: create a WireGuard server that anyone could deploy without fighting kernel modules, package managers, or complicated Linux networking. I wanted a userspace VPN that was portable, easy to install, and simple to manage.

That vision worked.

But as I kept using TunGuard in real deployments, I realized the VPN wasn't actually the hardest problem.

The hardest problem was managing remote infrastructure.

If you've ever deployed MikroTik routers for a WISP, installed devices behind CGNAT, or supported customers remotely, you've probably experienced the same frustration. The VPN is only one piece of the puzzle. Provisioning routers, reaching devices behind NAT, and managing them securely are the real challenges.

That changed the direction of TunGuard.

Today, TunGuard is no longer just a WireGuard server.

It has evolved into a platform for remote network management.

One of my favorite features is the SSH Jump Host. Instead of logging into a VPN server and then figuring out how to reach a router, I can connect directly from my computer:

ssh -J tanguard@server:2222 admin@10.100.0.2

There are no extra login sessions on the server. The server simply becomes a secure bridge between my laptop and the remote device. It feels as if the router is sitting on my local network.

That single feature completely changed how I manage remote MikroTik deployments.

The next step was automation.

I built MikroTik provisioning on top of TunGuard so routers could be configured with almost zero manual setup. Instead of spending time creating WireGuard peers, exchanging keys, writing firewall rules, or configuring tunnels by hand, the router can be provisioned automatically.

For many deployments, it becomes close to zero configuration.

The result is that TunGuard is no longer just about VPN connectivity. It's becoming the control plane for remote routers.

1. Automated MikroTik provisioning

2. Zero-config WireGuard deployment

3. SSH jump-host access

4. Remote router management

5. Browser-based dashboard

6. Simple API for automation

7. Runs entirely in userspace

What started as "a better WireGuard server" is gradually becoming infrastructure for anyone managing remote MikroTik routers.

Sometimes the most interesting software isn't built by following the original roadmap. It's built by solving the next problem you discover after the first one is solved.

For TunGuard, that next problem wasn't VPNs.

It was making remote network management effortless.

Ok, forgot VPN... and [What[mod edited link to game]?

:face_with_monocle:

Domain Name: ``TUNPROJECT.ORG
Registry Expiry Date: 2027-07-07T15:35:56Z
Creation Date: 2025-07-07T15:35:56Z
Updated Date: 2026-07-07T15:40:08Z
Domain Status: client transfer prohibited
Domain Status: auto renew period
Registrar: HOSTINGER operations, UAB

dude probably put more effort into creating accounts in github and forum with this name than in this ai slop glorified script placeholder replacer

Which part do you consider "AI slop" or a "script placeholder replacer"? I'd genuinely like to know what technical issues you see so I can improve the project.

place-before=0 on source.
For me is a critical error just at the start.

Why not put everything in the first post? For indexing reasons? Ridiculous.

Now you have two specific comments.
Let's see if you pretend not to understand.

I think there's a misunderstanding. The provisioning site isn't standalone it requires the TunGuard server binary to be running. It uses the TunGuard API for provisioning, peer management, and VPN functionality.

I don’t want to dampen your enthusiasm, but the idea of building a control plane around WireGuard has already been implemented. This type of solution is generally referred to as SD-WAN.

Tailscale is one well-known example that uses WireGuard. Another similar solution already integrated into RouterOS is ZeroTier, which is also open source and supports both Layer 2 and Layer 3 networking.

Other comparable open-source solutions include Headscale, Netmaker, Nebula, and several others.

I think there's a bit of confusion about what I mean by userspace. TunGuard isn't just another WireGuard control plane it implements the WireGuard server entirely in userspace, so it doesn't depend on the WireGuard kernel module or kernel networking components. The remote management and provisioning features are built on top of that.

I wasn’t referring to the userspace implementation. I meant the broader concept of combining WireGuard connectivity with a control plane, automated provisioning, and remote device management.

Implementing the WireGuard protocol and control plane entirely in userspace has already been done in all the previously mentioned solutions, and it’s just a matter of how you link it: as a static binary, a dynamic library, or through a kernel module.

I agree the control-plane pattern exists. The difference is the target and trade-offs: TunGuard focuses on a lightweight, self-hosted userspace WireGuard server for constrained environments and MikroTik deployments, where avoiding kernel dependencies and minimizing complexity matters. I’d encourage you to try it out and see the lightweight approach I’m referring to.

The goal was not to hide details, but to explain the use case first. TunGuard is intentionally minimal: a userspace WireGuard server with remote management features. I appreciate the technical feedback and will review the specific points you raised.

i don't understand this part ....since this is the code that generates the provision script and if you look keenly This script only creates:

  • WireGuard interface
  • WireGuard peer
  • IP address
  • Route
  • DNS settings

There is no firewall rule, NAT rule, mangle rule, queue rule, or any RouterOS command that uses place-before.

import type { WireGuardConfig } from "./types"

export function parseWireGuardConfig(config: string): WireGuardConfig {
const values: Record<string, string> = {}
let section = ""

for (const raw of config.split("\n")) {
const line = raw.trim()
if (!line || line.startsWith("#")) continue

const sectionMatch = line.match(/^\[(.+)\]$/)
if (sectionMatch) {
  section = sectionMatch[1].toLowerCase()
  continue
}

const eq = line.indexOf("=")
if (eq === -1) continue

const key = line.slice(0, eq).trim().toLowerCase()
values[`${section}_${key}`] = line.slice(eq + 1).trim()

}

const endpoint = values["peer_endpoint"] || ""
const lastColon = endpoint.lastIndexOf(":")
const endpointHost = lastColon === -1 ? endpoint : endpoint.slice(0, lastColon).replace(/^[|]$/g, "")
const endpointPort = lastColon === -1 ? 13231 : parseInt(endpoint.slice(lastColon + 1), 10)

return {
private_key: values["interface_privatekey"] || "",
address: values["interface_address"] || "",
dns: values["interface_dns"] || "1.1.1.1",
server_public_key: values["peer_publickey"] || "",
endpoint_host: endpointHost,
endpoint_port: isNaN(endpointPort) ? 13231 : endpointPort,
allowed_ips: values["peer_allowedips"] || "",
persistent_keepalive: parseInt(values["peer_persistentkeepalive"] || "25", 10),
}
}

export function generateScript(
interfaceName: string,
config: WireGuardConfig
): string {
const ip = config.address.split("/")[0].trim()
const octets = ip.split(".")
const subnet =
octets.length === 4
? ${octets.slice(0, 3).join(".")}.0/24
: ${ip}/24

return `/interface wireguard
:if ([:len [/interface wireguard find name="${interfaceName}"]] > 0) do={
/interface wireguard remove [find name="${interfaceName}"]
}

/interface wireguard
add name="${interfaceName}" mtu=1420 private-key="${config.private_key}"

/interface wireguard peers
add allowed-address=${subnet} endpoint-address=${config.endpoint_host} \
endpoint-port=${config.endpoint_port} interface=${interfaceName} \
persistent-keepalive=${config.persistent_keepalive} \
public-key="${config.server_public_key}"

/ip address
add address=${ip}/24 interface=${interfaceName}

/ip route
add disabled=no dst-address=${subnet} gateway=${interfaceName}

/ip dns
set servers=${config.dns}

:log info "TunGuard management tunnel completed successfully"
:log info "Assigned IP: ${ip} | Endpoint: ${config.endpoint_host}:${config.endpoint_port}"
`
}

Okay, that’s a selling point I can get behind, especially the focus on constrained environments. You should highlight that clearly in the opening of the first post.

Thanks, I appreciate the feedback. I’ll make the lightweight constrained-environment goal clearer in the first post. If you have time, I’d be interested in your thoughts after trying it out. The project is open source, so feel free to open issues for any bugs, concerns, or improvement ideas. I’m always open to technical feedback.

Stop spam the links again and again for indexing purpose only.

And what kind of f-king response is this? Generated by artificial deficiency. Out of context.

We’re just discussing technical points here. I welcome criticism and suggestions, but let’s keep it focused on the technology and implementation. Open-source feedback is always welcome.