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.
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.
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.
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.
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.
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.
curl -fsSL https://tunnelnet.io/uninstall.sh | sudo bash -s -- --yes
Or, from the machine itself: sudo tnet leave.
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 uses the Linux installer above, not the Windows one. The Windows installer is for Windows itself.
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.
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
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
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.
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.)
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.
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.
macOS has no kernel WireGuard, so the tunnel runs as a userspace process and
the kernel picks the interface name — utun4,
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.
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\.
& ([scriptblock]::Create((irm https://tunnelnet.io/uninstall-windows.ps1))) -Yes
Or tnet leave from an elevated prompt.
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.
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
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.
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:
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 only — tnet 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.
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.
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.
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.
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.
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.
sudo for everything.sudo for everything except tnet status and
tnet version.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.
tnet doctor is the first thing to run. It reports three distinct states,
and the third one matters:
tnet status says down, or there is no handshake. Start with
sudo tnet up, then sudo tnet doctor.
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.
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
$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.
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.
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.
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.
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.
Uninstall first, then install with a fresh token. Enrolling twice without uninstalling leaves the old device holding an address you are still paying for.
Your WSL DNS forwarder, not us. See WSL.
You ran it without root. See Root?.
Reconnect your session; a new terminal is not enough. See Windows.
Mail support@minakilabs.com with the
full output of tnet doctor. We reply within one business day — the
whole output saves a round trip.