Browser SSH

btop running over browser SSH
btop running over browser SSH

Browser SSH opens a terminal to any machine on the tailnet, straight from Northplane, with nothing to install. For each session, the browser runs a temporary Tailscale node of its own that joins the tailnet and connects to the machine with Tailscale SSH.

Requirements

  • Headscale 0.28 or newer, except 0.29.0 through 0.29.1, which break it.
  • Tailscale SSH on the target machine, turned on with tailscale up --ssh. A regular SSH server doesn't work, because the browser signs in through Tailscale rather than with a password or key.
  • The Northplane Agent, turned on.
  • An account linked to a Headscale user, since the session connects as that user. That takes SSO or proxy authentication; API key sessions can't use Browser SSH.
  • Headscale reachable from people's browsers, with WebSocket upgrades forwarded, and CORS headers if Northplane is on another domain. See Reverse proxy.

Opening a session

Choose SSH on a machine that has Tailscale SSH turned on. The session opens in a new window, which asks for the Linux user to log in as.

The username prompt for a browser SSH session
Choosing who to log in as

How it works

  1. The browser loads a Tailscale client built for WebAssembly, which joins the tailnet with a pre-auth key for the person's Headscale user.
  2. It connects to the machine over the tailnet, and Tailscale SSH on the machine checks the ssh rules in the ACL policy to decide whether that user may log in as the requested Linux user.
  3. The terminal is drawn by restty, a Ghostty-based terminal emulator. Sessions use TERM=xterm-256color, and a JetBrains Mono Nerd Font is included, so icons render without setup.

The agent removes the temporary machines once they go offline.

fastfetch with Nerd Font icons
fastfetch with Nerd Font icons

Troubleshooting

SSH not available

Error: "This build of Northplane doesn't include browser SSH."

The build is missing the WebAssembly files, hp_ssh.wasm and wasm_exec.js. The official Docker images include them. For a native install, ./build.sh builds them unless other parts are picked with flags; ./build.sh --wasm builds only them.

Agent required

Error: "Turn on the Northplane agent to use browser SSH."

Browser SSH needs the agent to join the tailnet and clean up after sessions. See Northplane Agent.

OIDC required

Error: "Sign in with SSO to use browser SSH."

The session connects as the signed-in person's Headscale user, and an API key session has none. Sign in with SSO or proxy authentication instead.

User not linked

Error: "Browser SSH connects as your Headscale user."

The account isn't linked to a Headscale user yet. If Headscale uses SSO too, signing in to Headscale from a device creates the user, and Northplane links it automatically. Otherwise, an admin links it from the Users page. See Linking by hand.

Node not found

Error: "There's no machine named …"

No machine in Headscale has the name in the URL. It may have been renamed or removed, so go back to the machine list and open it again.

Node offline

Northplane checks that the machine is connected before starting a session. If it isn't, an error page offers Try again once the machine is back online.

Connection fails or hangs

  • A 405 Method Not Allowed for /ts2021 in the browser console means Headscale 0.29.0 through 0.29.1. Upgrade to 0.29.2 or newer.
  • server_url in Headscale's config has to include the port when it isn't 443, such as https://hs.example.com:8443, since the embedded DERP server advertises its port from it.
  • Northplane's headscale.public_url, or headscale.url when it's unset, is the address the browser connects to, so it has to be Headscale's public URL.
  • The proxy in front of Headscale has to forward WebSocket upgrades.
  • On separate domains, the browser console shows CORS errors when Headscale's responses lack the CORS headers.

"unable to authenticate, attempted methods [none]"

The machine answered with a regular SSH server instead of Tailscale SSH. Turn on Tailscale SSH on the machine with tailscale up --ssh.

"failed to look up local user *"

Some Tailscale versions read "users": ["*"] in an SSH rule as a user literally named *. Use autogroup:nonroot or explicit usernames instead:

// Before
{ "action": "accept", "src": ["autogroup:member"], "dst": ["autogroup:self"], "users": ["*"] }

// After
{ "action": "accept", "src": ["autogroup:member"], "dst": ["autogroup:self"], "users": ["autogroup:nonroot"] }

Sign in to continue

SSH rules with "action": "check" make people sign in again before a session opens. Northplane shows a Sign in link above the terminal, and the session opens once that sign-in is done. Headscale 0.28 treats check like accept, so this starts with Headscale 0.29.

The session closes right away

Either the ssh rules in the ACL policy don't let this person log in as the chosen user, or that Linux user doesn't exist on the machine.

On this page