WebSSH 1.6.3 on Ubuntu 24.04 on Azure User Guide
Overview
This guide covers the deployment, secure configuration and day-to-day use of WebSSH 1.6.3 on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. WebSSH is an open-source web-based SSH client: it serves a browser terminal, built on Tornado and xterm.js, where you enter a target host, port, username and a password or private key, and it opens a real SSH session to that host and streams it back to the page. It lets you reach a shell from any modern browser with no local SSH client installed.
WebSSH is an SSH relay, and this image is built to ship it safely. By default the upstream wssh server binds 0.0.0.0:8888 with no authentication, so anyone who reaches the page can attempt an SSH connection to any host and port they type. That is an open pivot, SSRF and brute-force-relay surface. The cloudimg image closes it by design:
- WebSSH is bound to loopback only (
127.0.0.1:8888) — it is never a public listener. - nginx is the only public listener (TCP 80, and optionally 443), and it gates the entire UI and the terminal WebSocket behind per-VM HTTP Basic Auth.
- The password is generated uniquely on first boot and written to
/root/webssh-credentials.txt(mode0600, root only). No shared or default password is ever baked into the image — the shipped image carries only a fail-closed placeholder that denies everyone until first boot mints the real one.
The Security section below is the most important part of this guide: because WebSSH can connect outward to anything you type, you are responsible for restricting who can reach the front door and where it may connect. Read it before you expose the instance.
What is included:
-
WebSSH 1.6.3 (official
huashengdun/webssh, MIT licence) in a dedicated Python virtual environment at/opt/webssh/venv -
webssh.servicesystemd unit runningwsshbound to127.0.0.1:8888(loopback only), as a dedicatedwebsshsystem user -
nginx reverse proxy on TCP 80 fronting the UI + terminal WebSocket behind per-VM HTTP Basic Auth, plus an unauthenticated static
/healthzfor load-balancer probes -
webssh-firstboot.servicesystemd oneshot minting a per-VM Basic Auth password (bcrypt.htpasswd) and proving the front-door gate on first boot -
A fail-closed placeholder credential in the shipped image, so no known password is ever present
-
Tornado CSRF protection retained,
--fbidhttp=falsefor the behind-nginx topology,--origin=samecross-origin protection -
Ubuntu 24.04 LTS base with latest security patches applied at build time,
unattended-upgradesarmed -
Azure Linux Agent for seamless cloud integration and SSH key injection
-
24/7 cloudimg support with a guaranteed 24 hour response SLA
Prerequisites
-
Active Azure subscription, an SSH public key, and a VNet + subnet in your target region
-
Subscription to the WebSSH listing on Azure Marketplace
-
A clear view of which hosts you intend to administer through WebSSH and which client networks should be allowed to reach it — you will lock the instance down to those below
Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) is comfortable — WebSSH is a light, stateless gateway. For many concurrent terminals use Standard_B2ms or Standard_D2s_v5.
Step 1: Deploy from the Azure Portal
Search WebSSH in Marketplace, select the cloudimg publisher, and click Create. Configure the network security group as tightly as you can:
- TCP 22 (SSH) — for administration, from your management network only.
- TCP 80 (WebSSH UI, behind Basic Auth) — from your trusted client networks only, never
0.0.0.0/0on a public instance. - TCP 443 — optional, if you put your own TLS certificate in front of nginx (recommended for production; see Security).
Step 2: Deploy from the Azure CLI
az group create --name webssh-rg --location eastus
az vm create \
--resource-group webssh-rg \
--name webssh-vm \
--image <cloudimg-webssh-image-urn> \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku Standard
Then restrict inbound access to your own management CIDR rather than the whole internet:
az network nsg rule create \
--resource-group webssh-rg --nsg-name webssh-vmNSG \
--name allow-webssh-ui --priority 1010 \
--destination-port-ranges 80 --access Allow --protocol Tcp \
--source-address-prefixes <your-mgmt-cidr>
Step 3: First boot and retrieving your per-VM password
On first boot webssh-firstboot.service mints a unique password for the admin front-door account, writes it to /root/webssh-credentials.txt, starts nginx, and proves the gate. SSH in as azureuser and read it:
sudo cat /root/webssh-credentials.txt
You will see the WEBSSH_URL, the admin username, and the per-VM WEBSSH_PASSWORD, along with the security notes below. Confirm the services are healthy and that WebSSH is bound to loopback only, with nginx the only public listener:
systemctl is-active webssh.service nginx.service
sudo ss -tlnp | grep -E ':8888|:80 '
curl -s -o /dev/null -w 'unauthenticated /healthz -> HTTP %{http_code}\n' http://127.0.0.1/healthz

The credentials file is mode 0600, and the image ships only a fail-closed bcrypt placeholder in /etc/nginx/.webssh.htpasswd — no plaintext password is ever stored in the image:

Step 4: Sign in and open an SSH session
Open WEBSSH_URL (http://<your-vm-public-ip>/) in a browser. Your browser will prompt for the front-door credentials — sign in as admin with the per-VM password from Step 3. You then see the WebSSH connect form:

Enter the target host you want to reach: its hostname or IP, the SSH port (22 by default), the username, and either a password or an uploaded private key (with an optional passphrase and a TOTP code for 2FA servers).

Click Connect. WebSSH opens a real SSH session to that host and drops you into a live xterm.js terminal in the browser:

The terminal is a full interactive shell — run commands exactly as you would in any SSH client:

Step 5: Security — you are running an SSH relay
WebSSH will attempt to connect to any host and port a signed-in user types. Treat the instance as what it is — an SSH relay — and control both ends.
The front door only opens with the per-VM password
The whole UI and the terminal WebSocket sit behind per-VM HTTP Basic Auth. Without the password the front door is closed to everyone; a wrong password is rejected; only the per-VM password reaches the terminal. You can prove this from the VM:
PW=$(sudo grep '^WEBSSH_PASSWORD=' /root/webssh-credentials.txt | cut -d= -f2-)
curl -s -o /dev/null -w 'no password -> HTTP %{http_code}\n' http://127.0.0.1/
curl -s -o /dev/null -w 'wrong password -> HTTP %{http_code}\n' -u admin:wrong-pw http://127.0.0.1/
curl -s -o /dev/null -w 'per-VM pw -> HTTP %{http_code}\n' -u "admin:$PW" http://127.0.0.1/

Restrict WHO can reach it (inbound)
- Keep the per-VM password secret; rotate it if it is ever shared (see below).
- Lock inbound TCP 80/443 to trusted source IPs in the Azure NSG, or keep the instance on a private network reachable only over a VPN or Bastion. Do not expose it wide-open to
0.0.0.0/0.
Restrict WHERE it can connect (outbound / egress)
By design WebSSH lets the browser choose the destination, so an unrestricted instance can be used to reach anything routable from the VM. Constrain egress to only the hosts you administer. An Azure NSG outbound rule is the cleanest control; on the VM itself you can also use UFW, for example allowing SSH only to a specific management subnet:
sudo ufw default deny outgoing
sudo ufw allow out to <your-mgmt-cidr> port 22 proto tcp
sudo ufw allow out 53
sudo ufw enable
Encrypt the front door for production
The image serves the UI over plain HTTP on port 80 by default. For any production or internet-facing use, put your own TLS certificate in front of nginx (port 443) so the Basic Auth password and the terminal traffic are encrypted in transit — cloudimg ships nginx-ssl-certbot-ubuntu-24-04 as a one-step option, or add a listen 443 ssl server block with your certificate to /etc/nginx/sites-available/cloudimg-webssh.
Managing the service
Check status, follow logs, and restart:
systemctl status webssh.service --no-pager
sudo journalctl -u webssh.service --no-pager -n 20
To restart the stack after a configuration change, run sudo systemctl restart webssh.service nginx.service. To follow the logs live, use sudo journalctl -u webssh.service -f.
Rotate the front-door password by writing a fresh bcrypt entry (nginx reads the file per request, so no restart is needed):
NEWPW=$(openssl rand -base64 18)
sudo htpasswd -bB /etc/nginx/.webssh.htpasswd admin "$NEWPW"
echo "New WebSSH admin password: $NEWPW"
Store the new password somewhere safe; it replaces the one in /root/webssh-credentials.txt.
Verifying the install
Confirm the pinned versions and the whole stack, including a real SSH round-trip through the gateway:
/opt/webssh/venv/bin/wssh --version
nginx -v
systemctl is-active webssh.service nginx.service

Troubleshooting
-
The browser never prompts for a password / the page is open — check that you are reaching the instance through nginx on port 80, not WebSSH's loopback
:8888directly. WebSSH must never be published on a public interface; confirmss -tlnp | grep 8888shows127.0.0.1:8888only. -
"Connection refused" or a red status when you click Connect — WebSSH could not reach the target host/port, or your egress rules block it. Confirm the target is reachable from the VM (
ssh <user>@<host>), and review your NSG/UFW outbound rules. -
HTTP 401 with the right password — the password in
/root/webssh-credentials.txtis the current one only if you have not rotated it. If you rotated it, use the new value. -
Host key warning on connect — WebSSH uses the upstream default
warninghost-key policy (trust-on-first-use). Unknown target host keys are accepted with a logged warning, which you can review injournalctl -u webssh.service.
Support
Every cloudimg image includes 24/7 support with a guaranteed 24 hour response SLA. Contact support through the Azure Marketplace listing or at cloudimg.co.uk.