Getting started
jssh gives you audited SSH to devices behind NAT. Each device runs a small agent that dials out to the relay (no inbound ports); you open a point-in-time SSH session to one device at a time.
1 · Get a device online
Create an enrollment token in the dashboard (Tokens) — it hands you a copy-paste one-liner with the token baked in. Run it on the device:
# Linux — works from a root shell or a sudo-capable user: curl -fsSL https://get.jssh.io | JSSH_ENROLL_TOKEN=<ENROLLMENT_TOKEN> sh
It auto-detects the CPU + init system, installs the agent as a service, enrolls the device, and (on Linux) enables signed auto-update — the update trust anchor ships inside the binary, so the token is all you need. Windows: from an elevated PowerShell:
iwr https://get.jssh.io/latest/install.ps1 -OutFile install.ps1 powershell -ExecutionPolicy Bypass -File .\install.ps1
Without -Token the installer prints a code you approve in the dashboard; add -Token <ENROLLMENT_TOKEN> for unattended installs. macOS: the same one-liner as Linux — it asks for your sudo password and installs a launchd service.
Android (Termux; no root — install the packages, open a new session, then run the one-liner; sshd listens on 8022):
pkg install termux-services openssh curl -fsSL https://get.jssh.io | JSSH_ENROLL_TOKEN=<ENROLLMENT_TOKEN> sh
Docker: use the Docker tab on the token page — or run the image without a token and approve the code it prints to docker logs.
2 · Connect from your computer
curl -fsSL https://get.jssh.io/cli | sh # install the operator CLI jssh login # opens your browser to authorize this machine jssh devices # list your fleet jssh ssh <device> # or native: ssh <device>.jssh.dev
Windows (PowerShell, no elevation needed):
iwr https://get.jssh.io/cli.ps1 -OutFile install-cli.ps1 powershell -ExecutionPolicy Bypass -File .\install-cli.ps1 jssh login
jssh login also wires up your SSH config, so you can use native tooling with no jssh prefix — ssh <device>.jssh.dev, and the same host works with scp, rsync, and VS Code Remote-SSH:
ssh <device>.jssh.dev # native shell (no jssh prefix) scp file.txt <device>.jssh.dev:~/ # copy a file rsync -a ./dir <device>.jssh.dev:/opt/ # sync a directory
jssh only carries the encrypted tunnel — authentication is the device's own SSH. Put your public key in the device's ~/.ssh/authorized_keys (or use a password) first, or SSH returns Permission denied (publickey).
3 · Troubleshooting
- Device offline — the agent isn't connected. On the device check
systemctl status jssh-agent(orsystemctl --user status jssh-agenton Ubuntu Core) andjournalctl -u jssh-agent. The agent dials out overwss://on 443. - Permission denied — your SSH key isn't on the device yet (see step 2).
- Behind a corporate firewall — allowlist the relay host's SNI, and export
HTTPS_PROXYbefore the install command if outbound goes through a proxy. - Device won't connect at all — confirm it can reach
https://app.jssh.ioover HTTPS (443); the relay is the only outbound endpoint the agent needs. - Doctor — on the device,
jssh-agent doctorchecks DNS / 443 / TLS / proxy; from your computer,jssh doctor <device>checks your config, token, and the device's live status.
4 · Services beyond SSH
A device can expose more than sshd: declare [[service]] entries in its agent config, or enable a built-in (rdp, vnc, http, https, postgres, redis) from the device page — the device resolves the target itself; the relay never picks a destination. Connect with jssh rdp <device>, jssh vnc <device> or jssh connect <device> --service <name>; each command prints the localhost address to point your client at. Every service is end-to-end encrypted between the CLI and the agent — not just SSH — so the relay carries ciphertext it cannot read. The final hop on each machine is a local connection in the clear, as with an SSH port-forward.
5 · Manual install (unsupported init)
The installer supports systemd, OpenRC, procd, SysV, launchd, Termux and Batocera — on anything else it refuses BEFORE enrolling, so your token isn't spent. Manually: download the binary for your arch from get.jssh.io/latest, run jssh-agent enroll (browser approval), then wire jssh-agent run into your init with a restart-on-failure policy. Service templates live in the client repo's deploy/ directory.