-
-
Notifications
You must be signed in to change notification settings - Fork 43
Tailscale SSH
Tailscale SSH is optional and disabled by default. Every authorized WebSSH user uses the WebSSH node's same Tailscale identity. This is a stronger trust boundary than ordinary per-user SSH credentials.
Use Tailscale SSH only when you have:
- a dedicated Tailscale tag for the WebSSH node;
- narrow tailnet ACL and SSH rules;
- trusted WebSSH administrators or an explicit non-admin allowlist;
- exact target allowlists where practical;
- exact remote operating-system username allowlists;
- a tested local WebSSH administrator and recovery path;
- persistent Tailscale node state.
Hiding the UI is not authorization. The backend enforces WebSSH user, target, remote-user, and tailnet policy.
TAILSCALE_SSH_ENABLED=true
TAILSCALE_SSH_ALLOWED_WEBSSH_USERS=operator
TAILSCALE_SSH_ALLOWED_TARGETS=tiny-server,100.64.0.10
TAILSCALE_SSH_ALLOWED_REMOTE_USERS=root,ubuntuAdministrators are authorized by role when the feature is enabled. The user allowlist adds specifically trusted non-admin WebSSH accounts. Empty target or remote-user lists remove that extra application-level restriction, so define them for a shared multi-user deployment.
Use a dedicated source tag such as tag:webssh and target tag such as
tag:servers. Grant only TCP/22 and only the remote users that WebSSH should
reach.
Illustrative policy:
{
"tagOwners": {
"tag:webssh": ["autogroup:admin"],
"tag:servers": ["autogroup:admin"]
},
"grants": [
{
"src": ["tag:webssh"],
"dst": ["tag:servers"],
"ip": ["tcp:22"]
}
],
"ssh": [
{
"action": "accept",
"src": ["tag:webssh"],
"dst": ["tag:servers"],
"users": ["root"]
}
]
}Adapt this to the current Tailscale policy schema and organizational controls. The example is intentionally narrow.
A Tailscale sidecar can share its network namespace with WebSSH. Persist
/var/lib/tailscale so the node identity survives updates.
services:
tailscale:
image: tailscale/tailscale:stable
hostname: webssh
restart: unless-stopped
ports:
- "5000:5000"
environment:
- TS_AUTHKEY=${TS_AUTHKEY}
- TS_AUTH_ONCE=true
- TS_STATE_DIR=/var/lib/tailscale
- TS_USERSPACE=false
- TS_EXTRA_ARGS=--advertise-tags=tag:webssh
volumes:
- tailscale_state:/var/lib/tailscale
- /dev/net/tun:/dev/net/tun
cap_add:
- NET_ADMIN
- NET_RAW
webssh:
image: ghcr.io/bifrost0x/webssh:latest
restart: unless-stopped
network_mode: service:tailscale
depends_on:
- tailscale
environment:
- HOST=0.0.0.0
- PORT=5000
- CORS_ORIGINS=*
- ALLOW_CORS_WILDCARD=true
- SESSION_COOKIE_SECURE=false
- TAILSCALE_SSH_ENABLED=false
- TAILSCALE_SSH_ALLOWED_WEBSSH_USERS=
- TAILSCALE_SSH_ALLOWED_TARGETS=tiny-server
- TAILSCALE_SSH_ALLOWED_REMOTE_USERS=root
volumes:
- webssh_data:/app/data
volumes:
tailscale_state:
webssh_data:The example starts with Tailscale SSH disabled.
- Start the sidecar deployment on a trusted network with
TAILSCALE_SSH_ENABLED=false. - Create the first WebSSH administrator explicitly with the CLI.
- Keep ordinary registration closed unless deliberately needed.
- Configure tailnet policy and exact WebSSH allowlists.
- Enable Tailscale SSH.
- Recreate the service and test one narrow target/user combination.
Replace homelab CORS and cookie settings with:
environment:
- CORS_ORIGINS=https://ssh.example.com
- SESSION_COOKIE_SECURE=trueRemove wildcard CORS. If a reverse proxy on the host terminates TLS, bind the published port to loopback:
ports:
- "127.0.0.1:5000:5000"For a containerized proxy, remove the public port and use a private shared network.
Do not commit TS_AUTHKEY. Supply it through an environment file or secret
manager. Prefer a tagged reusable or OAuth-issued credential with only the
permission needed to own tag:webssh. TS_AUTH_ONCE=true avoids unnecessary
reauthentication after persistent node state exists.
Authorized profiles can select Tailscale SSH instead of a password or stored key. A saved profile can launch directly when no interactive credential is needed. Manual reconnect can also proceed directly while authorization remains valid.
Check the feature flag and whether the WebSSH account is an administrator or is
listed in TAILSCALE_SSH_ALLOWED_WEBSSH_USERS.
Check the exact WebSSH target allowlist, DNS/Tailscale name, tailnet grants, SSH rules, and remote-user allowlist.
Confirm /var/lib/tailscale is persistent and TS_STATE_DIR points to it.
That is the design boundary: Tailscale sees the shared WebSSH node identity. Use WebSSH audit logs and strict user/target/remote-user controls, or do not enable the feature for users who should have separate tailnet identities.
WebSSH documentation - Project - Issues - Discussions - Security
WebSSH Wiki
Install and operate
- Installation from Source
- Docker and Docker Compose
- Production Deployment
- Reverse Proxy and Subfolder Deployment
- Upgrading, Rollback, and FAQ
Identity and access
- Users and Account Management
- Authentication Overview
- LDAP and Active Directory
- OpenID Connect
- Passkeys and Recovery Codes
SSH and files
- SSH Connections and Host Keys
- Profiles, Jump Hosts, and Commands
- Terminal and Persistent tmux Sessions
- SFTP File Workspace and Transfers
- Tailscale SSH
Reference and administration