W1
Networking AWS

WebSSH 1.6.3 on AWS User Guide

| Product: WebSSH 1.6.3 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using WebSSH 1.6.3 on Ubuntu 24.04 from cloudimg on Amazon EC2. It is one version of the cloudimg Hardened WireGuard VPN & VPN, Proxy and Remote Access AMIs listing on AWS Marketplace; you pick it as the software version when you launch.

WebSSH is a small web application that opens SSH sessions from your browser. You enter a server's address, port, user name and password or private key, and WebSSH connects from the instance and gives you a full terminal (xterm.js) in the page. It is handy as a browser-only jump point into a private network, or for reaching servers from a machine where you cannot install an SSH client.

What the cloudimg image adds:

  • A password in front of WebSSH, generated on your instance. Upstream WebSSH has no login of its own. On this image nginx asks for a user name and password before it shows any page, script or terminal connection. The password is generated on your instance's first boot and stored only as a bcrypt hash; the image itself contains no password and no user at all.
  • HTTPS on port 443, from the first boot. nginx serves WebSSH on port 443 with a certificate generated on your instance. Port 80 only redirects to HTTPS and never asks for, or accepts, the password. A request for a host name the instance does not serve is dropped.
  • WebSSH on localhost only. WebSSH listens on 127.0.0.1:8888, accepts requests only from nginx on the same machine, and is not reachable from the network. Port 8888 is not in the listing's security group.
  • Cross-site requests refused. Opening an SSH session takes a form post and a WebSocket. nginx refuses both when they come from another web site, and WebSSH itself refuses a foreign origin and a post without its anti-forgery token. Every page carries a strict Content-Security-Policy and may not be framed by another site.
  • Host keys checked. WebSSH remembers each server's SSH host key the first time you connect and refuses the connection if that key later changes (upstream's default only logs a warning).
  • Instance metadata blocked. WebSSH connects out to whatever host and port you type. Neither WebSSH nor nginx can reach the EC2 instance metadata service (169.254.169.254 and fd00:ec2::254), so a session aimed at it cannot read this instance's role credentials.
  • Current, audited dependencies. WebSSH 1.6.3 is installed unmodified (every file checked against the upstream release), with Tornado 6.6 and paramiko 5.1.0, versions outside every published advisory at the time of the build, all installed from hash-pinned packages.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

What is on the image: WebSSH 1.6.3 in a Python virtual environment on Ubuntu's Python 3.12, nginx 1.24 (from Ubuntu 24.04), cloud-init and the AWS Systems Manager agent.

Default user: ubuntu · Architecture: x86_64 · Root volume: 8 GiB gp3

Read this first: WebSSH is an SSH relay

Anyone who signs in to WebSSH can open a TCP connection from this instance to any host and port the instance can reach: every server in its VPC and peered networks, and the internet if the subnet routes there. WebSSH then speaks SSH to it with the credentials that person types. Treat the WebSSH password like a bastion host's key:

  • Restrict port 443 in the security group to the addresses you work from. Do not open it to 0.0.0.0/0.
  • Limit where the instance can connect with security group outbound rules or network ACLs, and optionally with the egress allow list in Step 7.
  • Connections to this instance's own SSH server (127.0.0.1:22) need a key, because password logins are disabled on the image.

Security posture

  • Per-instance secrets only. The WebSSH password and the TLS key are generated on your instance's first boot. Two instances launched from the same AMI share neither.
  • Fails closed. WebSSH and nginx start only after first boot has written this instance's password, certificate and a ready marker. The password file in the image holds no user, so even a forced start would refuse everyone.
  • End-entity certificate. The self-signed certificate can only vouch for this instance's own names (it is not a certificate authority). Compare its fingerprint with the one in the credentials file, or replace it with a CA-issued certificate (Step 8).
  • Sandboxed service. WebSSH runs as its own webssh account with no capabilities, a read-only view of the system, write access to /var/lib/webssh only and core dumps disabled. A start-up check refuses to start it if its program files become writable or its host-key file is replaced by a link.
  • Real client addresses. nginx passes WebSSH the address of the machine that connected, never a value a client sends, and does not pass your password on to WebSSH.
  • Per-instance identity. SSH host keys and the machine ID are generated fresh on every instance.

Prerequisites

  1. An AWS account and permission to launch EC2 instances
  2. An EC2 key pair in the Region you launch in
  3. A security group that allows TCP 22 (SSH) from your admin network and TCP 443 (the WebSSH page) from the networks you browse from, plus TCP 80 if you want plain-HTTP visitors redirected. Never open 8888: WebSSH listens on localhost only.
  4. A subnet with a route to the servers you want to reach

Recommended instance type: m5.large. WebSSH itself is light (each open terminal is one SSH connection); size up only for many concurrent sessions.

Connecting to your instance

Version Operating system SSH user
WebSSH 1.6.3 on Ubuntu 24.04 Ubuntu 24.04 LTS ubuntu

Step 1: Launch the instance

Option A: AWS Marketplace console

  1. Open the cloudimg Hardened WireGuard VPN & VPN, Proxy and Remote Access AMIs listing on AWS Marketplace and choose View purchase options, then Subscribe. One subscription covers every version in the listing.
  2. Once the subscription is active, choose Launch your software.
  3. Choose One-click launch from AWS Marketplace (or Launch from EC2 Console for the full launch wizard). In Version, choose WebSSH 1.6.3 on Ubuntu 24.04, then choose your Region.
  4. Pick an instance type (m5.large recommended) and your key pair.
  5. Security group: the listing's default group allows SSH (22), HTTP (80) and HTTPS (443) from anywhere. Restrict SSH and 443 to your own addresses before you launch.
  6. Choose your VPC and a subnet with a route to the servers you will reach (and a public IP if you browse to WebSSH over the internet), leave the metadata defaults (the image already requires IMDSv2), then choose Launch.

Option B: AWS CLI

Run this on your own workstation. It creates a security group that allows SSH and HTTPS from your management address, then launches the instance. Replace the AMI ID with the one shown for your Region on the Marketplace launch page, and the VPC, subnet, key pair and CIDR range with your own.

SG=$(aws ec2 create-security-group --group-name webssh \
  --description "WebSSH" --vpc-id vpc-0123456789abcdef0 \
  --query GroupId --output text)
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 22  --cidr <your-mgmt-cidr>
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 443 --cidr <your-mgmt-cidr>
aws ec2 run-instances \
  --image-id ami-0123456789abcdef0 \
  --instance-type m5.large \
  --key-name my-key-pair \
  --security-group-ids "$SG" \
  --subnet-id subnet-0123456789abcdef0 \
  --associate-public-ip-address \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=webssh}]'

You do not need --metadata-options: the AMI already makes IMDSv2 required. First boot takes a few seconds after the instance is running.

Step 2: Connect and check the services

Connect as ubuntu with the key pair you chose:

ssh -i ~/.ssh/my-key-pair.pem ubuntu@<public-ip>
systemctl is-active webssh nginx
sudo test -f /var/lib/cloudimg/webssh-firstboot.done && echo FIRSTBOOT_DONE
active
active
FIRSTBOOT_DONE

webssh-firstboot ran once on the first boot; webssh is WebSSH and nginx serves it. Only SSH and nginx listen on network ports; WebSSH listens on the loopback address:

sudo ss -lntp | awk 'NR>1 {print $4}' | grep -v '127.0.0.5' | sort -u
0.0.0.0:22
0.0.0.0:443
0.0.0.0:80
127.0.0.1:8888
[::]:22
[::]:443
[::]:80

Step 3: Get your URL and password

The URL and the password are in a file only root can read:

sudo cat /root/webssh-credentials.txt
# WebSSH 1.6.3 - generated on first boot by webssh-firstboot.service.
# Every value below is unique to this instance; nothing was baked into the image. Keep this file private.

# Web terminal (HTTPS, self-signed certificate unique to this instance): sign in as admin with the password below,
# then enter the SSH host, port, user and password or private key of the server you want to reach.
WEBSSH_URL=https://203.0.113.10/
WEBSSH_USERNAME=admin
WEBSSH_PASSWORD=<your-password>
WEBSSH_HOST=203.0.113.10
# The certificate served on 443 (/etc/nginx/cloudimg/tls/webssh.crt), to compare with your browser's warning:
TLS_CERT_SHA256=3A:1F:...:9C

# Anyone with this password can open SSH connections FROM this instance to any host it can reach: restrict port 443
# in the security group to your own addresses. New password: sudo webssh-new-password
# Self-test: sudo webssh-selftest      Serve WebSSH under your own DNS name: sudo webssh-set-host <dns-name>
# Guide: https://www.cloudimg.co.uk/guides/webssh-1-6-3-on-ubuntu-24-04-aws/

The URL uses the instance's public IPv4 address (its private address when it has no public one). WebSSH also answers on the instance's public DNS name.

Step 4: Sign in

Open WEBSSH_URL in your browser. The certificate is self-signed, so the browser warns once: compare the fingerprint it shows with TLS_CERT_SHA256 from the credentials file, then continue. The browser then asks for a user name and password: enter admin and WEBSSH_PASSWORD. The browser remembers them until you close it.

You now see WebSSH's connection form:

The WebSSH connection form

Step 5: Open an SSH session

  1. Hostname and Port: the server you want to reach, as this instance sees it (a private IP inside your VPC works). The port defaults to 22.
  2. Username: the account on that server.
  3. Password, or Private Key (choose the key file; add its Passphrase if it has one). RSA, ECDSA and Ed25519 keys work; DSA keys are not supported. Totp is for servers that ask for a one-time code.
  4. Choose Connect. The terminal opens in the page; closing the tab ends the session.

An SSH session in WebSSH

To try it on the instance itself, connect to Hostname 127.0.0.1, Port 22, Username ubuntu and choose your EC2 key pair's private key file (.pem). The key travels to the instance over HTTPS and is used for that one connection; WebSSH does not store it.

Host keys. The first time you connect to a server, WebSSH records its host key in /var/lib/webssh/known_hosts. If the key later changes (the server was rebuilt, or something is intercepting the connection), WebSSH refuses with Bad host key. After you have confirmed the change is genuine, remove the old entry and restart WebSSH (open sessions are closed):

sudo -u webssh ssh-keygen -R '[10.0.1.25]:22' -f /var/lib/webssh/known_hosts
sudo systemctl restart webssh

For port 22 the entry is just the host (10.0.1.25); for other ports it is [host]:port.

Instance metadata. A session aimed at the metadata service fails before any data is exchanged:

WebSSH refusing a connection to the instance metadata service

Step 6: Change the password

sudo webssh-new-password

This generates a new random password, writes it to WEBSSH_PASSWORD in /root/webssh-credentials.txt and stores its bcrypt hash for nginx. The old password stops working at once; your browser asks for the new one on the next page load. To pick your own password instead, write a bcrypt entry for admin to /etc/nginx/cloudimg/webssh.htpasswd (mode 0640, owner root, group www-data).

Step 7: Limit where WebSSH can connect

Security group outbound rules are the simplest control. To restrict WebSSH itself to a list of networks, add a systemd drop-in (here: this instance's own SSH server and one VPC range):

sudo tee /etc/systemd/system/webssh.service.d/60-egress-allowlist.conf >/dev/null <<'EOF'
[Service]
IPAddressAllow=localhost 10.0.0.0/16
IPAddressDeny=any
EOF
sudo systemctl daemon-reload && sudo systemctl restart webssh

Never put 169.254.0.0/16, 0.0.0.0/0 or ::/0 in IPAddressAllow: an allow entry overrides the metadata block. For a strict list of servers instead of networks, run WebSSH with --policy=reject: it then connects only to servers whose host keys you have added to /var/lib/webssh/known_hosts (or /etc/ssh/ssh_known_hosts) and refuses every other target before opening a connection:

ssh-keyscan -p 22 10.0.1.25 | sudo -u webssh tee -a /var/lib/webssh/known_hosts
sudo sed -i 's/--policy=autoadd/--policy=reject/' /etc/systemd/system/webssh.service
sudo systemctl daemon-reload && sudo systemctl restart webssh

Check each host key you add (for example against ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub on that server).

Step 8: Your own DNS name and certificate

Point a DNS A record at the instance, then:

sudo webssh-set-host webssh.example.com

This issues a new self-signed certificate for that name, updates nginx and the credentials file, and prints the new fingerprint. sudo webssh-set-host with no name returns to the instance's own address.

To use a CA-issued certificate instead, put the certificate chain and key in /etc/nginx/cloudimg/tls/webssh.crt and /etc/nginx/cloudimg/tls/webssh.key (key mode 0600, owned by root), then run sudo nginx -t && sudo systemctl reload nginx. Do not add the self-signed certificate to your computer's trust store; compare its fingerprint, or replace it.

Step 9: Security checks

Run the built-in self-test. It opens a real SSH session through the HTTPS front door to a temporary SSH server on 127.0.0.1:2222 and checks that WebSSH cannot reach the metadata service:

sudo webssh-selftest
WebSSH 203.0.113.10: webssh + nginx up, WebSSH 127.0.0.1:8888 only, HTTPS OK, anonymous requests refused, SSH session through the web front door OK, metadata service unreachable
SELFTEST_OK

Without the password every request is refused, the page may not be framed, and port 8888 is not reachable from the network:

H=$(sudo sed -n 's/^WEBSSH_HOST=//p' /root/webssh-credentials.txt)
sudo curl -s -o /dev/null -D - --cacert /etc/nginx/cloudimg/tls/webssh.crt --resolve "$H:443:127.0.0.1" "https://$H/" | tr -d '\r' | grep -iE '^(HTTP|WWW-Authenticate|X-Frame-Options)'
P=$(hostname -I | awk '{print $1}')
timeout 5 bash -c "exec 3<>/dev/tcp/$P/8888" 2>/dev/null && echo "8888 OPEN" || echo "8888 refused"
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="WebSSH (cloudimg)"
X-Frame-Options: DENY
8888 refused

WebSSH and nginx are denied the instance metadata service:

systemctl show -p IPAddressDeny webssh nginx
IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128

IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128

The password file holds one bcrypt entry and is readable by root and nginx only:

sudo stat -c '%a %U:%G' /etc/nginx/cloudimg/webssh.htpasswd
sudo grep -cE '^admin:\$2b\$12\$' /etc/nginx/cloudimg/webssh.htpasswd
640 root:www-data
1

Step 10: Versions, provenance and file locations

head -3 /usr/share/doc/webssh/BUILD-INFO.txt
/opt/webssh/venv/bin/wssh --version
Path What it is
/opt/webssh/venv WebSSH 1.6.3 and its Python packages, read-only to the service (no pip in it)
/var/lib/webssh/known_hosts the host keys WebSSH has accepted
/etc/nginx/conf.d/cloudimg-webssh.conf, /etc/nginx/cloudimg/ the nginx server blocks, the password file and this instance's certificate
/etc/systemd/system/webssh.service the service and WebSSH's options (--policy, --maxconn, ...)
/usr/share/doc/webssh/ the MIT licence, build information and the hashed package lock
/root/webssh-credentials.txt the first-boot URL, password and certificate fingerprint

Step 11: Maintenance

WebSSH keeps no data besides known_hosts; back that file up if you rely on the recorded host keys. Ubuntu security updates install automatically. WebSSH and its Python packages are updated by moving to a newer cloudimg version of this listing (the virtual environment deliberately has no pip).

Step 12: Security recommendations

  • Restrict ports 22 and 443 in the security group to your own networks, and limit outbound traffic to the servers you need.
  • Rotate the password with sudo webssh-new-password when someone no longer needs access, and do not share it more widely than you would an SSH key.
  • Prefer private keys to passwords for the servers you reach, and check a host key before the first connection.
  • Replace the self-signed certificate with a CA-issued one for wider use.

Support and licensing

WebSSH is open-source software under the MIT licence (/usr/share/doc/webssh/LICENSE). Its Python dependencies keep their own licences, recorded in /usr/share/doc/webssh/BUILD-INFO.txt: Tornado (Apache-2.0), cryptography (Apache-2.0 or BSD-3-Clause), bcrypt and PyNaCl (Apache-2.0), and paramiko (LGPL-2.1, shipped unmodified from PyPI; source at github.com/paramiko/paramiko). The browser terminal uses xterm.js, jQuery and Bootstrap (MIT). cloudimg provides the hardened image and support for its configuration at support@cloudimg.co.uk. WebSSH is a project of its own author; cloudimg is not affiliated with or endorsed by them.