Sshwifty is a web-based SSH (and Telnet) client: it puts a terminal in your browser so you can reach a remote server without an SSH client on the local machine. Handy from a locked-down laptop, a tablet, or a borrowed computer.

This deploys it behind Traefik for TLS.

Read this before exposing it

A web SSH client on the public internet is a doorway into every host it can reach. If your Docker host is on Tailscale, that’s your entire tailnet. If Sshwifty’s own auth is off, anyone who finds the URL is one login away from your servers. Treat this as one of the most sensitive things you’ll ever host — gate it hard, or prefer the alternative below.

docker-compose.yml

# /srv/ssh.example.com/docker-compose.yml
name: ssh-example-com
 
services:
  ssh:
    image: niruix/sshwifty:latest
    container_name: ssh.example.com
    restart: always
    mem_limit: 256M
 
    environment:
      # REQUIRED. Empty = the web UI is PUBLIC (no login). Set a long random key.
      - SSHWIFTY_SHAREDKEY=${SSHWIFTY_SHAREDKEY}
 
    labels:
      - com.centurylinklabs.watchtower.enable=true
      - traefik.enable=true
      - traefik.http.routers.ssh.rule=Host(`ssh.example.com`)
      - traefik.http.routers.ssh.entrypoints=https
      - traefik.http.routers.ssh.tls=true
      - traefik.http.routers.ssh.tls.certresolver=lets-encrypt
      # Sshwifty listens on :8182 inside the container — Traefik needs this
      - traefik.http.services.ssh.loadbalancer.server.port=8182

Two additions to a bare setup:

  • loadbalancer.server.port=8182 — Sshwifty’s default port; without it Traefik has nothing to route to (502).
  • entrypoints=https — pin to the TLS entrypoint.

Lock it down (do all of these)

  1. Set SSHWIFTY_SHAREDKEY. Generate one and keep it out of the compose file:
    openssl rand -base64 32     # put the result in a .env / env_file as SSHWIFTY_SHAREDKEY
    With it empty, Sshwifty bypasses its login page entirely — public web SSH.
  2. Put it behind SSO too — defense in depth. Add the Traefik forward-auth (Google SSO) middleware to the router so a Google login + email allowlist sits in front of Sshwifty’s own shared key. One leaked secret shouldn’t be game over.
  3. Prefer not to expose it publicly at all. Bind it to your tailnet/VPN and reach it privately. A public ssh.example.com is a permanent, high-value target.
  4. On the servers behind it: key-only SSH, no root login, and fail2ban — as if the internet can knock on the door, because now it can.

A more secure alternative: Tailscale SSH

Since your host is already on Tailscale, you likely don’t need a public web SSH client at all. Tailscale SSH solves the same problem without the exposed attack surface:

tailscale up --ssh
  • Identity-based, not password-based — access is granted by your tailnet ACLs (who can SSH to which nodes as which user), authenticated via your SSO.
  • No public endpoint — nothing to find, scan, or brute-force from the internet.
  • Re-auth on demand — ACLs can force a fresh browser check before a session.
  • Connect with a normal ssh user@machine-name over the tailnet — no client-side keys to manage, Tailscale handles the crypto.

Rule of thumb: use Tailscale SSH for yourself and your team (secure by default); reach for Sshwifty only when you genuinely need a browser terminal from an untrusted device — and even then, keep it behind SSO + a shared key.