Jumphost tunnel¶
Publish a service from a private machine to other Torus members — no public IP, no nSolo, no port forwarding on your home router. The platform's SSH jumphost terminates the tunnel; other members reach your service at a mesh-internal IP.
Available to any Torus member (Basic, Plus, Pro).
Where it lives
Sidebar: Jumphost Tunnel (under Reachability). Section ID: reverse-tunnel-section.
What it actually does¶
You run a service on a machine that's not publicly reachable — e.g. a development laptop, a Raspberry Pi behind a home router, an IRIX box in your bedroom. With one SSH command, you publish that service's port to the Torus jumphost. Other members of the mesh can then reach it at 10.255.9.241:<port> (or via the jumphost's hostname).
Under the hood it's plain ssh -R (SSH reverse port forwarding) — the same trick people have been using since the 1990s. We've just put a managed jumphost at the other end and a UI in front of it so you don't have to provision your own.
┌──────────────────────┐
your laptop ssh │ jump.ring.nekotopia │ other mesh
behind NAT ────► │ .io:2222 │ ◄──── members
│ = 10.255.9.241 │ (TCP to port
└──────────────────────┘ on jumphost)
Step-by-step¶
1 — Upload your SSH public key¶
Open the Jumphost Tunnel section in the dashboard. Top card: SSH Public Key. Paste your key.
Don't have one? Generate a dedicated key (recommended — don't reuse your GitHub key):
Paste the .pub half (cat ~/.ssh/nekotopia_jump.pub). Optional description: name the machine so you can identify it later (MacBook Pro, home-pi, etc.).
Hit Save Key. The card flips to CONFIGURED and the How to Connect + Registered Ports cards appear.
2 — Pick a port¶
Choose any TCP port in the range 10000–60000. Outside that range the jumphost will refuse the forward. Pick something memorable for the service:
| Service | Suggested port |
|---|---|
| Web (HTTP) | 10080 |
| Web (alt) | 18080 |
| SSH | 10022 |
| IRC | 16667 |
| Generic TCP | 10000+ |
3 — Open the tunnel¶
From the machine running the service:
Breakdown:
-p 2222— jumphost SSH listens on 2222 (not 22)-R 10080:localhost:8000— bind port10080on the jumphost, forward tolocalhost:8000(your local service)-i ~/.ssh/nekotopia_jump— use the dedicated keytunnel@jump.ring.nekotopia.io— the only user account permitted on the jumphost; the tunnel is the only thing this user can do (no shell)
When it connects, the session sits idle — keep it running. Closing the SSH session tears the tunnel down.
4 — Share the address with mesh members¶
Anyone on Torus can now reach your service at:
10.255.9.241 is the jumphost's mesh IP. If you'd rather give a friendlier address, register a DNS record in DNS Manager pointing to that mesh IP — e.g. myapp.ring.nekotopia.io → 10.255.9.241. Members then visit http://myapp.ring.nekotopia.io:10080.
5 — (Optional) Register the port¶
The dashboard's Registered Ports card lets you record which port maps to which service. This is purely an inventory tool — it doesn't change tunnel behaviour. Worth filling in if you have multiple services or want others to discover what you're running. Click + Register Port, enter the port + description, save.
Keeping the tunnel alive¶
The bare ssh -R command above dies if your network blips or you close the terminal. For something you want to stay up, run it under a wrapper:
macOS / Linux — autossh¶
brew install autossh # macOS
# apt install autossh # Debian/Ubuntu
autossh -M 0 \
-o "ServerAliveInterval 30" \
-o "ServerAliveCountMax 3" \
-p 2222 -R 10080:localhost:8000 \
-i ~/.ssh/nekotopia_jump \
tunnel@jump.ring.nekotopia.io
autossh auto-restarts the SSH session if it dies. Server keepalives ensure that we notice when the jumphost goes away (not just the other side noticing us).
Linux — systemd unit¶
For a server you don't want to babysit:
# /etc/systemd/system/nekotopia-tunnel.service
[Unit]
Description=Nekotopia jumphost tunnel
After=network-online.target
[Service]
ExecStart=/usr/bin/autossh -M 0 -N \
-o ServerAliveInterval=30 \
-o ExitOnForwardFailure=yes \
-p 2222 -R 10080:localhost:8000 \
-i /home/youruser/.ssh/nekotopia_jump \
tunnel@jump.ring.nekotopia.io
Restart=always
RestartSec=10
User=youruser
[Install]
WantedBy=multi-user.target
systemctl enable --now nekotopia-tunnel.service.
Limits and behaviour¶
- Port range: 10000–60000 only. Lower ports are reserved for the platform.
- Access: mesh-internal only —
10.255.9.241isn't reachable from the public internet. If you want public reach, you want nSolo Inbound, not this. - Protocols: anything TCP works. SSH
-Rdoes not natively forward UDP — use a different tool if you need that. - Concurrent tunnels: no enforced cap, but please be reasonable. Two or three per member is normal.
- The jumphost is shared infrastructure — don't push gigabits through it. It runs on the docker host alongside other community services.
Differences from nSolo Inbound¶
| Jumphost tunnel | nSolo Inbound | |
|---|---|---|
| Required tier | Any (Basic / Plus / Pro) | Plus with nSolo add-on, or Pro |
| Public IP | No — mesh-only, via the jumphost | Yes — your nSolo 193.143.16.x |
| Setup | Upload SSH key + run ssh -R from the source machine |
Form in the dashboard, no client-side anything |
| Direction | Outbound SSH from your machine to the jumphost | Inbound NAT from the public internet to your mesh |
| Stays up if your machine sleeps | No — your machine holds the connection | Yes — it's static NAT on a router |
| Good for | Quick ad-hoc shares, services on devices behind NAT, dev-environment demos | Production services on a public IP |
Rule of thumb: if your service is meant to be reached from the public internet and stay up 24/7, use nSolo Inbound. If it's a service you want to share with other mesh members from a machine you control directly, jumphost tunnel is simpler.
Security notes¶
- The
tunnel@user on the jumphost is restricted — it has no shell, no file access, no command execution. The only thing it can do is hold open the SSH transport for your-Rforward. - Your service is reachable by any authenticated Torus member at the jumphost IP. There's no auth at the platform layer for the tunnel itself — your service is responsible for its own access control.
- The dedicated
nekotopia_jumpkey recommendation matters: if you reuse your GitHub key and someone compromises this machine, they have your GitHub identity. Don't.
Troubleshooting¶
ssh: connect to host jump.ring.nekotopia.io port 2222: Connection refused
You're not on the Torus mesh. The jumphost only accepts connections from Torus IPs. Check your WireGuard is up: wg show should list jump.ring.nekotopia.io's endpoint as connected.
Permission denied (publickey)
Either you uploaded the wrong key (paste .pub, not the private half), or the SSH client is offering a different key. Force the right one with -i ~/.ssh/nekotopia_jump.
Tunnel up but port unreachable
Most often: localhost:8000 on the source machine refers to nothing. Check the service is actually running and listening on 127.0.0.1:8000 from the machine running ssh -R. curl -v http://localhost:8000 on that machine should respond. If it says connection refused, fix the service first.
Works locally, but other members can't reach it
Your service might be bound to 127.0.0.1 only inside the SSH session — that's fine for ssh -R. But if you're seeing an HTTP service return wrong content (or 502), check that the service speaks Host header 10.255.9.241 correctly. Some HTTPS frameworks enforce hostname.
Tunnel disconnects every few minutes
NAT-keepalive issue. Add ServerAliveInterval=30 ServerAliveCountMax=3 to your SSH options, or use autossh -M 0 ... -o ServerAliveInterval=30. Long-idle SSH sessions get reaped by some home routers.
Removing the tunnel¶
Just close the SSH session. The forward dies, port frees up. No platform-side cleanup needed.
To revoke the SSH key entirely (e.g. machine compromised or no longer using the feature): Jumphost Tunnel section → SSH Public Key card → Remove Key. Existing tunnel sessions are killed immediately.