A web-based SSH client with updated security patches based on WebSSH.
- Alpine Linux 3.22.2 (latest stable, updated from 3.22.1)
- Python 3.13.8 (updated from 3.13.7)
- Paramiko 4.0.0 (updated from 3.5.1)
- Tornado 6.5.2 (updated from 6.5.1)
- OpenSSL 3.5.4 with CVE-2025-9230, CVE-2025-9231, CVE-2025-9232 patches
- Updated CA certificates (20250911)
- All Alpine packages updated to latest security patches
- Base:
python:3.13-alpine3.22
- WebSSH Version: 1.6.3
- Size: ~83MB (optimized from 268MB)
- Architecture: ARM64 (aarch64)
docker run -d -p 8888:8888 --name webssh monzal/webssh:1.6.3-secure
Access WebSSH at: http://localhost:8888
docker run -d \
-p 7773:8888 \
--name webssh \
--restart unless-stopped \
monzal/webssh:1.6.3-secure
- Apache2 installed
- Domain name (e.g.,
ssh.yourdomain.com
) - Certbot for Let's Encrypt SSL certificates
docker run -d \
-p 127.0.0.1:7773:8888 \
--name webssh \
--restart unless-stopped \
monzal/webssh:1.6.3-secure
Note: Binding to 127.0.0.1
ensures WebSSH is only accessible via reverse proxy.
sudo a2enmod proxy proxy_http proxy_wstunnel ssl headers rewrite
sudo systemctl restart apache2
Create /etc/apache2/sites-available/webssh.conf
:
<VirtualHost *:80>
ServerName ssh.yourdomain.com
ServerAdmin webmaster@localhost
# Redirect to HTTPS
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
ErrorLog ${APACHE_LOG_DIR}/webssh_error.log
CustomLog ${APACHE_LOG_DIR}/webssh_access.log combined
</VirtualHost>
Enable the site:
sudo a2ensite webssh.conf
sudo systemctl reload apache2
sudo certbot --apache -d ssh.yourdomain.com --non-interactive --agree-tos --redirect
This creates /etc/apache2/sites-available/webssh-le-ssl.conf
Edit /etc/apache2/sites-available/webssh-le-ssl.conf
:
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerName ssh.yourdomain.com
ServerAdmin webmaster@localhost
# Reverse Proxy to WebSSH
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:7773/
ProxyPassReverse / http://127.0.0.1:7773/
# WebSocket support for terminal
RewriteEngine On
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule ^/?(.*) "ws://127.0.0.1:7773/$1" [P,L]
# Security headers
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Content-Type-Options nosniff
Header always set X-Frame-Options DENY
Header always set X-XSS-Protection "1; mode=block"
ErrorLog ${APACHE_LOG_DIR}/webssh_error.log
CustomLog ${APACHE_LOG_DIR}/webssh_access.log combined
SSLCertificateFile /etc/letsencrypt/live/ssh.yourdomain.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/ssh.yourdomain.com/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>
sudo apache2ctl configtest
sudo systemctl reload apache2
Open your browser: https://ssh.yourdomain.com
To protect WebSSH with password authentication:
sudo htpasswd -c /etc/apache2/.htpasswd yourusername
<Location />
AuthType Basic
AuthName "SSH Access Required"
AuthUserFile /etc/apache2/.htpasswd
Require valid-user
</Location>
Reload Apache2:
sudo systemctl reload apache2
- SSH over HTTPS - Secure web-based terminal
- Password Authentication - Standard SSH password login
- Public Key Authentication - RSA, DSA, ECDSA, Ed25519 keys supported
- Encrypted Keys - Passphrase-protected keys supported
- Two-Factor Authentication - TOTP support
- WebSocket Support - Real-time terminal interaction
- Fullscreen Mode - Distraction-free terminal
- Customizable - Font size, colors, encoding
docker run -d \
-p 7773:8888 \
-e "POLICY=reject" \
-e "MAXCONN=20" \
-e "DELAY=3" \
--name webssh \
monzal/webssh:1.6.3-secure
Available options:
POLICY
- Host key policy:warning
(default),autoadd
,reject
MAXCONN
- Maximum connections per IP (default: 20)DELAY
- Delay between connections in seconds (default: 3)
- SSH private keys uploaded via browser are NOT stored on disk
- Keys are kept temporarily in container memory only
- Keys are automatically deleted when session ends
- All communication must use HTTPS to protect keys in transit
- Always use HTTPS in production
- Use HTTP Basic Auth or firewall rules to restrict access
- Bind to localhost when using reverse proxy
- Regular updates - Rebuild image monthly for security patches
- Monitor logs - Check Apache and Docker logs regularly
Create docker-compose.yml
:
version: '3.8'
services:
webssh:
image: monzal/webssh:1.6.3-secure
container_name: webssh
restart: unless-stopped
ports:
- "127.0.0.1:7773:8888"
environment:
- POLICY=reject
- MAXCONN=20
- DELAY=3
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
Start with:
docker-compose up -d
git clone https://github.com/monzal/webssh
cd webssh/build
docker build -t webssh:1.6.3-secure .
Check for updates monthly:
docker pull monzal/webssh:latest
docker stop webssh
docker rm webssh
docker run -d -p 7773:8888 --name webssh --restart unless-stopped monzal/webssh:latest
docker logs webssh
- Check SSH server is running:
systemctl status ssh
- Verify firewall allows SSH:
sudo ufw status
- Test SSH directly:
ssh user@hostname
- Ensure
proxy_wstunnel
module is enabled in Apache2 - Check browser console for errors (F12)
- Verify Apache2 configuration syntax:
apache2ctl configtest
sudo certbot renew --dry-run
sudo certbot certificates
MIT License - Based on WebSSH
- WebSSH on PyPI: https://pypi.org/project/webssh/
- GitHub: https://github.com/henosch/webssh
- Docker Hub: https://hub.docker.com/r/monzal/webssh
See CHANGELOG.md for version history.