-
-
Notifications
You must be signed in to change notification settings - Fork 43
Production Deployment
Use this profile for an Internet-facing or otherwise untrusted multi-user deployment. WebSSH treats production settings as a validated security contract: unsafe combinations stop startup instead of merely logging warnings.
A recommended deployment has these boundaries:
Browser --HTTPS--> trusted reverse proxy --private HTTP--> WebSSH
|
+--SSH/SFTP--> targets
Only the trusted reverse proxy should reach WebSSH's backend port. Protect the Docker host, data volume, logs, and backups as privileged infrastructure.
- Docker Compose 2.24.4 or newer. The production overlay uses
!overrideto replace the homelab port binding. - A public DNS name and trusted TLS certificate.
- A reverse proxy that supports WebSocket upgrade.
- A persistent
/app/datavolume. - A recorded backup and rollback image before updates.
curl -O https://raw.githubusercontent.com/bifrost0x/webssh/main/docker-compose.yml
curl -O https://raw.githubusercontent.com/bifrost0x/webssh/main/docker-compose.production.yml
export WEBSSH_ORIGIN=https://ssh.example.com
docker compose \
-f docker-compose.yml \
-f docker-compose.production.yml \
config
docker compose \
-f docker-compose.yml \
-f docker-compose.production.yml \
up -dThe overlay binds WebSSH to 127.0.0.1:5000 by default. A reverse proxy on the
same host can use that address. For a containerized proxy, remove public port
publishing and connect both services to a private Docker network.
Browser bootstrap and public registration are disabled in production. Create or promote an administrator from the trusted host:
docker compose \
-f docker-compose.yml \
-f docker-compose.production.yml \
exec webssh \
/app/entrypoint.sh flask --app start:app create-admin --username adminFor non-interactive automation, use --password-file with a private regular,
non-symlink file mounted into the container. The command removes one trailing
newline and never prints the password.
The supplied overlay configures:
DEPLOYMENT_PROFILE: production
CORS_ORIGINS: ${WEBSSH_ORIGIN}
ALLOW_CORS_WILDCARD: "false"
SESSION_COOKIE_SECURE: "true"
REGISTRATION_ENABLED: "False"
BOOTSTRAP_REGISTRATION_ENABLED: "false"
BLOCK_INTERNAL_SSH: "true"
TRUSTED_PROXIES: ${TRUSTED_PROXIES:-1}Production startup rejects:
- debug mode;
- wildcard or missing browser origins;
- insecure browser cookies;
- browser bootstrap or open registration;
- disabled internal-target blocking;
- an unspecified proxy trust boundary.
Set TRUSTED_PROXIES=0 explicitly only when WebSSH accepts no proxy headers.
When the value is non-zero, a client that can bypass the proxy may spoof
forwarded information, so restrict the backend network path.
The proxy must:
- terminate HTTPS;
- preserve the public
Hostheader; - forward
X-Forwarded-Proto; - forward the client address through the expected number of trusted layers;
- support WebSocket
UpgradeandConnectionheaders; - avoid exposing the plain backend to clients.
See Reverse Proxy and Subfolder Deployment for nginx, Traefik, and Caddy examples.
Local accounts remain the recovery foundation.
- Keep at least one local administrator with tested recovery material.
- OIDC requires explicit stable
(issuer, subject)linking; it never trusts email alone. - LDAP/Active Directory requires explicit stable identity linking; linked users are non-admin and cannot fall back to local passwords or alternative factors.
- Passkeys require the exact public RP ID and HTTPS origin.
Enable optional identity only after the base local-admin deployment works.
Production enables BLOCK_INTERNAL_SSH=true, blocking loopback, link-local,
private, and reserved targets after DNS resolution. This reduces the risk that
untrusted users use WebSSH as an SSRF or internal network pivot.
If the real use case requires private targets, do not simply relax the flag on a broadly accessible service. Restrict users and network access, isolate the instance, and document the exception.
Keep exactly one Gunicorn worker. Defaults are:
GUNICORN_THREADS=64MAX_SOCKET_CONNECTIONS=48MAX_SOCKET_CONNECTIONS_PER_USER=8- at least four threads reserved for HTTP routes and streamed transfers.
The bounded runtime executor defaults to the required minimum derived from cleanup loops, allowed SSH readers, and background jobs. Enabling LDAP adds one permanent revalidation job to that minimum.
After deployment:
curl -fsS https://ssh.example.com/health
curl -fsS https://ssh.example.com/ready
docker compose ps
docker compose logs --tail=200 websshVerify:
- HTTPS redirects and certificate validation.
- Secure cookies in the browser.
- The registration page is unavailable.
- Administrator login and recovery.
- A direct SSH connection and host-key trust.
- SFTP listing and a small transfer.
- Graceful container restart and readiness recovery.
- Create and download a verified backup.
- Record the deployed immutable image digest.
- Review release notes and persistent-data compatibility.
- Keep the previous image available.
- Confirm that the restart policy is
unless-stoppedor an equivalent managed restart policy.
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