TunnelNet docs

A public IPv4 address for your machine, and an IPv6 network of your own. The IPv4 address is a real, globally routable address from our own block, announced by BGP from AS396500. It works from behind NAT with no port forwarding: a WireGuard tunnel carries traffic between your machine and our hub, and the hub routes your public IP to you.

Alongside it you get an IPv6 /56 — not one address, a network: 256 subnets, enough to give every container or VM behind your machine its own address. See Your addresses.

Everything is closed until you open it. These pages are public — you do not need to sign in to read them with a broken machine at 9pm.

On this page

Getting started

One account, as many machines as you want

This is not a one-machine product. Each machine is a seat at $8/month: press Add a machine on the dashboard, it adds a seat and gives you a fresh enrolment token, and you run the installer on that machine. Repeat as often as you like, up to your account's machine limit.

Every token is single-use and enrols exactly one machine. There is no separate “token page”; the token appears when you add the machine.

Machines on the same account can reach each other

Devices belonging to one account can talk to each other over the mesh, on both their public and their mesh addresses, without opening any ports to the internet. Your port rules still apply — opening nothing to the world does not stop your own machines reaching one another.

Devices on different accounts cannot reach each other at all. That is enforced at the hub, not by your machine.

Installing

Take the token from the dashboard and run your platform's installer below. Enrolment takes about a minute: the installer generates a WireGuard key, asks us for addresses, writes the tunnel config and brings it up, then waits for both of our hubs to add your device.

The installer releases your public IP back to the pool if you uninstall properly, which is why you should use the uninstaller rather than deleting the config by hand.

Tested distributions

We test on Ubuntu 24.04, Debian 12, Fedora 43, AlmaLinux 9 and Rocky 9. Other distributions are untested rather than unsupported — the installer needs wireguard-tools, curl and systemd, and where those exist it generally works. If yours does not, tell us what happened; that is how the list grows.

Linux

Install

curl -fsSL https://tunnelnet.io/install.sh | sudo bash -s -- <your-token>

This installs the tnet CLI to /usr/bin/tnet and writes /etc/minaki/device.conf.

Uninstall

curl -fsSL https://tunnelnet.io/uninstall.sh | sudo bash -s -- --yes

Or, from the machine itself: sudo tnet leave.

Commands that work here

tnet down pauses the tunnel and billing continues; it is tnet leave that stops the charge. See The CLI.

sudo tnet status            is the tunnel up, and how much has crossed it
sudo tnet doctor            every check, with what to do about each
sudo tnet up                bring the tunnel up
sudo tnet down              take the tunnel down
sudo tnet ports             list this device's open ports
sudo tnet ports allow tcp 443
sudo tnet ports allow 443 --tcp        (same thing; both forms work here)
sudo tnet ports deny 443                (deny takes the PORT ONLY here)
sudo tnet ports mode filtered|allow_all
sudo tnet leave             release the address and unenrol
sudo tnet info              device id, addresses, config paths
sudo tnet upgrade           fetch and install the current CLI (Linux only)
sudo tnet check             one-shot reachability probe
tnet version

The interface is wg0. Only Linux installs a new CLI in place; macOS and Windows offer tnet upgrade --check, which reports and changes nothing.

WSL

WSL uses the Linux installer above, not the Windows one. The Windows installer is for Windows itself.

WSL2 must be in mirrored networking mode

In WSL2's default NAT mode, WireGuard cannot work: the tunnel comes up and looks healthy, and no traffic ever reaches it, because WSL sits behind a NAT of its own inside a separate network namespace. The installer checks and refuses rather than leaving you with a tunnel that half runs. Put this in %USERPROFILE%\.wslconfig:

[wsl2]
networkingMode=mirrored

then wsl --shutdown from Windows and start WSL again. Mirrored mode needs Windows 11 22H2 or later.

SSH on WSL is usually 2222, and we do not assume it

Windows' own OpenSSH server takes port 22, so a WSL sshd is normally moved to 2222. We do not guess: at enrolment we look at where sshd is actually listening and open that port. Connect with:

ssh -p 2222 you@<your-public-ip>

The installer does not assume either port: it checks what is listening and opens that. If your WSL sshd is on 2222, ssh -p 2222 is how you connect and 22 is closed — deliberately, because on WSL anything answering on 22 is the Windows OpenSSH server, not your Linux machine, and opening 22 would publish a host you did not ask us to expose.

This is not WSL-only. We open whatever port sshd is on, so a plain Linux machine with sshd moved to 2222 gets 2222 too. What is WSL-specific is the reason it is usually moved.

Check which port Windows holds, from PowerShell:

Get-Service sshd

The trap: Windows OpenSSH set to Automatic but stopped

If Windows' sshd is set to start Automatic but happens to be stopped when you enrol, port 22 looks free inside WSL, your WSL sshd may be on 22, and we open 22 — correctly, from what we could see at the time.

After the next reboot Windows' sshd starts, takes 22, and your WSL sshd cannot bind it. SSH stops working with nothing on the Linux side changed to explain it. Check with Get-Service sshd in PowerShell: if StartType is Automatic, move your WSL sshd to 2222 permanently and open that port instead.

and which port WSL's sshd is on, from WSL:

sudo ss -ltnp | grep sshd
sudo tnet ports

“could not resolve host” during install

WSL's DNS forwarder stops answering fairly often, and the first thing that notices is a curl that cannot resolve a name. That is your WSL installation, not our service — the same request works from Windows at the same moment, and we were never contacted. Check and fix:

cat /etc/resolv.conf         what WSL currently thinks DNS is
ping -c1 1.1.1.1             does the network work at all?
wsl.exe --shutdown           then start WSL again

If it keeps happening, pin a resolver: put nameserver 1.1.1.1 in /etc/resolv.conf, and stop WSL regenerating it by adding [network] / generateResolvConf=false to /etc/wsl.conf.

macOS (Apple Silicon)

Install

curl -fsSL https://tunnelnet.io/install-macos.sh | sudo bash -s -- <your-token>

The CLI goes to /usr/local/bin/tnet and the config to /etc/tunnelnet/device.conf. (Linux uses /etc/minaki/; the two platforms genuinely differ, which is worth knowing if you support both.)

Uninstall

curl -fsSL https://tunnelnet.io/uninstall-macos.sh | sudo bash -s -- --yes

There is no tnet leave on macOS. Use the uninstaller; it is what releases the address.

Commands that work here

tnet status                 is the tunnel up, and how healthy
sudo tnet up
sudo tnet down
sudo tnet doctor
sudo tnet ports
sudo tnet ports allow 443 tcp          note: PORT first on macOS
sudo tnet ports deny  443 tcp
sudo tnet ports mode  filtered|allow_all
tnet version

tnet status and tnet version do not need sudo here; everything else does.

macOS does not self-upgrade either. tnet upgrade --check reports whether a newer CLI is available and changes nothing; taking it means re-running the installer above. Your tunnel is unaffected either way — this is the CLI only.

The port comes first on macOS. The Linux form tnet ports allow tcp 443 does not work on macOS — it reads tcp as the port. This differs between platforms and we should unify it; until then, use the form shown for the platform you are on.

The interface is utunN, not wg0

macOS has no kernel WireGuard, so the tunnel runs as a userspace process and the kernel picks the interface nameutun4, utun7, whatever is free — and it can change between reboots. Anything telling you to run ifconfig wg0 on macOS is wrong; that interface does not exist. tnet status resolves the current name for you; if you need it directly it is recorded in /var/run/wireguard/tnet0.name.

Windows

Install

In an elevated PowerShell (Win+X, Terminal as Administrator):

& ([scriptblock]::Create((irm https://tunnelnet.io/install-windows.ps1))) -Token <your-token>

The CLI goes to C:\Program Files\TunnelNet\ and the config to C:\ProgramData\TunnelNet\.

Uninstall

& ([scriptblock]::Create((irm https://tunnelnet.io/uninstall-windows.ps1))) -Yes

Or tnet leave from an elevated prompt.

Commands that work here

tnet status
tnet doctor
tnet up
tnet down
tnet ports
tnet ports allow 443              note: PORT first, tcp is the default
tnet ports allow 19132 udp
tnet ports deny  443
tnet ports mode  filtered|allow_all
tnet leave
tnet info
tnet version

These need Administrator. Without it the CLI says so and stops — it does not guess.

Windows does not self-upgrade. tnet upgrade --check tells you whether a newer CLI is being served and changes nothing; taking it means re-running the installer. The check is report-only on purpose — replacing the running CLI unattended means verifying the delivered bytes first, and that is not built yet.

tnet is a .cmd, and that is deliberate

The CLI is PowerShell, but a default Windows install has ExecutionPolicy Restricted and will not run a .ps1 at all. So the tnet on your PATH is tnet.cmd, a shim that runs the script with -ExecutionPolicy Bypass for that one process only. It changes no machine-wide policy. If tnet is not found, call it by path:

& "C:\Program Files\TunnelNet\tnet.cmd" status

After installing: reconnect your session — a new terminal is not enough

The installer adds C:\Program Files\TunnelNet to the machine PATH. A new terminal opened from your existing session inherits that session's environment, read when the session began, so tnet is still not found and it looks like the install failed.

Sign out and back in, or disconnect and reconnect your RDP or SSH session. Over SSH especially, opening another shell changes nothing: the whole session carries the old environment. Until you reconnect, use the full path above.

Ports & the firewall

The default: SSH, and nothing else

A newly enrolled device is set to filtered mode with a single rule: SSH open, and nothing else. Every other inbound port to your public IP is dropped at our edge — not on your machine. So on a fresh box, SSH to your public IP works, but a web server on port 80 will not, until you open it.

We open the port sshd is actually on, not a fixed number. At enrolment the installer asks the machine where sshd is listening and opens that one TCP port — 22 on most machines, 2222 inside WSL, and whatever you moved it to if you moved it. Hardcoding 22 is wrong on any machine where sshd has moved.

If sshd is not running, or is bound only to 127.0.0.1, nothing is opened at all. A loopback-only socket cannot be reached through the tunnel however the firewall is set, so a rule for it would be theatre: the dashboard would show an open port with nothing to answer on it.

We do not open whatever else your machine happens to be listening on. A box running Docker listens on dozens of ports, and opening them because they exist is the opposite of closed-by-default. You open the rest yourself.

This happens once, at enrolment. We look at where sshd is on the day you enrol; we do not watch it afterwards. Move sshd later and you must open the new port yourself — nothing on our side notices the move.

Two things always pass at the edge, by design:

Opening a port

Use tnet ports allow, in your platform's form (see your platform's section above — the argument order differs):

            open                       close
Linux:      sudo tnet ports allow tcp 443    sudo tnet ports deny 443
macOS:      sudo tnet ports allow 443 tcp    sudo tnet ports deny 443 tcp
Windows:    tnet ports allow 443             tnet ports deny 443

The argument order differs by platform, and on Linux deny takes the port onlytnet ports deny tcp 443 there answers “'tcp' is not a port”. We should unify these; until we do, use the form for the platform you are on.

Or from the dashboard, which calls /api/ports/allow, /api/ports/deny and /api/ports/mode for you.

Changes reach the edge within 60 seconds: the hub agents apply policy on a one-minute timer. If a port looks shut immediately after opening it, wait a cycle before assuming it failed.

allow_all — and why you probably shouldn't

mode allow_all opens every port on your address. It is a real option and occasionally the right one, but it means anything listening on your machine is reachable from the internet, including things you forgot were running. Prefer naming the ports you want.

Two firewalls, and only one of them is ours

Our edge firewall drops closed ports at our edge, so they time out rather than refusing — a refusal tells a scanner something is there.

Your machine has its own firewall as well: ufw or firewalld on Linux, the Windows Firewall, the application firewall on macOS. Opening a port with tnet ports allow does nothing about those. If a port is open here and still unreachable, your machine's own firewall is the next thing to check.

Your addresses: one IPv4, and an IPv6 /56

Every device gets a public IPv4 address. That is the thing you are buying and it works everywhere.

Devices enrolled from 21 August 2026 also get IPv6 — and not a single address. You get a /56: 256 separate /64 subnets, enough to give every container, VM or jail behind your machine its own address, without asking us for anything and without NAT.

Both families are governed by the same port policy. Opening tcp 443 opens it on IPv4 and IPv6 together; there is no second firewall to configure and no second set of rules to keep in step.

sudo tnet status        shows both addresses

Existing devices still work exactly as they did. If you enrolled before that date you have IPv4 only and we have not touched it. To pick up IPv6, uninstall and install again with a fresh token; your account keeps the same /56 either way, so you come back inside the same range.

The CLI

Each platform's commands are listed in its own section above, because they are not identical — tnet leave does not exist on macOS, and the argument order for ports allow differs.

Only Linux can upgrade itself. tnet upgrade on Linux fetches and installs the current CLI. On macOS and Windows the same verb accepts only --check: it reports whether a newer version is being served and replaces nothing, because taking a new CLI on those platforms means re-running the installer. A check that reported "up to date" when it could not reach the server would be worse than no check, so on any failure it says the version is unknown and names the HTTP status.

Root?

Yes, for almost everything. The CLI reads the tunnel's live state from files and sockets only root can open. Run it unprivileged and it cannot see the interface, so tnet doctor reports the tunnel down when it is up and blames your machine for a fault that is not there — a confident wrong answer rather than a refusal.

down pauses, it does not cancel

tnet down takes the tunnel down and leaves the address yours — billing continues, and tnet up brings it back. If you want to stop paying, that is tnet leave (or the uninstaller), which releases your public IP back to the pool. See Billing.

Checking it works

tnet doctor is the first thing to run. It reports three distinct states, and the third one matters:

1. Tunnel down

tnet status says down, or there is no handshake. Start with sudo tnet up, then sudo tnet doctor.

2. Tunnel up, port closed at our edge

The handshake is good and traffic crosses, but a connection to your public IP times out. The port is not open: sudo tnet ports, then open it.

3. Port open, but nothing listening (or blocked locally)

The port is open here and the connection still fails. Either there is nothing listening on your machine, or your own firewall is dropping it. Check both:

sudo ss -ltnp                        Linux and WSL
sudo lsof -nP -iTCP -sTCP:LISTEN     macOS
Get-NetTCPConnection -State Listen   Windows

Billing

$8/month per device, billed to the card on file. Adding a device adds a seat; releasing one removes it.

If you cancel, your device keeps working until the end of the period you have paid for — there are no prorated refunds, and equally we do not cut you off the moment you cancel.

If a card fails, the subscription goes past due. We do not cut you off straight away: you have a 14-day grace period from the first failure, during which everything keeps working while the card is retried. After that the subscription is cancelled and the address is released.

Cancel from the dashboard. Removing a device with tnet leave or the uninstaller releases its address and drops the seat at the same time.

Troubleshooting

Enrolled but unreachable

The dashboard shows the device and the address, and nothing answers. In order: is the tunnel up (sudo tnet status), is the port open (sudo tnet ports), is something listening, is your own firewall allowing it. sudo tnet doctor walks all four and says which one failed.

Stuck pending

Enrolment finished but the device stays pending. Both of our hubs must add your device before it goes active, and they do that on a 60 second timer, so give it two minutes. If it is still pending after that, send us the output of sudo tnet doctor.

Tunnel up with no handshake

The interface exists but no handshake has completed. Almost always the outbound path to our hub is blocked — hotel wifi and some corporate networks block UDP. Test from the same machine and send us the result.

Port opened but still refused

A refusal (rather than a timeout) means something answered. That is your machine's own firewall or the service itself, not our edge — ours drops, it does not refuse.

Re-enrolling the same machine

Uninstall first, then install with a fresh token. Enrolling twice without uninstalling leaves the old device holding an address you are still paying for.

“could not resolve host” on WSL

Your WSL DNS forwarder, not us. See WSL.

tnet says the tunnel is down but it is up

You ran it without root. See Root?.

tnet: command not found on Windows, right after installing

Reconnect your session; a new terminal is not enough. See Windows.

Still stuck

Mail support@minakilabs.com with the full output of tnet doctor. We reply within one business day — the whole output saves a round trip.

MinakiLabs · AS396500 · 23.187.152.0/24
Terms · AUP · Privacy · Dashboard
Support: support@minakilabs.com — we reply within one business day