Skip to content

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):

ssh-keygen -t ed25519 -f ~/.ssh/nekotopia_jump

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:

ssh -p 2222 \
    -R 10080:localhost:8000 \
    -i ~/.ssh/nekotopia_jump \
    tunnel@jump.ring.nekotopia.io

Breakdown:

  • -p 2222 — jumphost SSH listens on 2222 (not 22)
  • -R 10080:localhost:8000 — bind port 10080 on the jumphost, forward to localhost:8000 (your local service)
  • -i ~/.ssh/nekotopia_jump — use the dedicated key
  • tunnel@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:

http://10.255.9.241:10080

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.241 isn't reachable from the public internet. If you want public reach, you want nSolo Inbound, not this.
  • Protocols: anything TCP works. SSH -R does 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 -R forward.
  • 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_jump key 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.