Networking Azure

Termix on Ubuntu 24.04 on Azure User Guide

| Product: Termix on Ubuntu 24.04 LTS on Azure

Overview

Termix is a self hosted server management platform that runs in your browser. It gives you a full SSH terminal with tabs and split panes, an SFTP file manager, port forwarding tunnels, and RDP, VNC and Telnet remote desktop sessions, all from one interface and all against servers you own. Connections live in a central inventory with folders and tags, so a team can keep one organised view of its fleet instead of scattering host entries across everyone's laptops. Credentials, including SSH private keys, are stored in an encrypted database rather than in plain configuration files. The cloudimg image serves Termix 2.5.1 on a hardened, fully patched Ubuntu 24.04 LTS base, behind nginx on port 80, with the guacamole proxy included so remote desktop works out of the box.

The image is secure by default: it ships with no user account at all, so the very first time you open it you create your own administrator with your own password. There are no shared or default credentials anywhere in the image, and the encryption keys that protect your stored credentials are generated on your own VM at first boot, never baked into the image. Backed by 24/7 cloudimg support.

What is included:

  • Termix 2.5.1, built from the pinned upstream release, managed by systemd
  • Browser based SSH terminal, SFTP file manager, tunnels, and RDP, VNC and Telnet sessions
  • nginx serving the web interface on :80 and proxying every backend service
  • guacd, the remote desktop proxy, so RDP and VNC work without extra setup
  • No default account: you create the first administrator on the first visit
  • Four independent 256 bit secrets generated per VM at first boot, never shared between VMs
  • An encrypted credential store, with each user's data key unlocked only by their own password
  • Every backend service bound to loopback, so only SSH and the web interface are reachable
  • Trust on first use host key verification before any session opens
  • termix.service, nginx.service and guacd.service as enabled systemd units
  • 24/7 cloudimg support

Prerequisites

An active Azure subscription, an SSH key pair, and a VNet plus subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is a sensible starting point; size up for more concurrent sessions. NSG inbound: allow 22/tcp from your management network and 80/tcp for the web interface.

Read this before you boot the VM. Termix stores SSH credentials and private keys for your other machines and gives shell access to them through a browser, so treat this VM as a privileged host. Because the image deliberately ships with no account, Termix's create administrator form is available to whoever reaches port 80 first, and the first account created is granted administrator. Scope the inbound rule for 80/tcp to your own IP address before you boot the VM, and create your administrator immediately after first boot. Once your administrator exists, sign ups can be closed from Settings.

Step 1 - Deploy from the Azure Marketplace

Sign in to the Azure Portal, choose Create a resource, search the Marketplace for Termix by cloudimg, and select Create. On Basics pick your subscription, resource group, region and size; under Administrator account choose SSH public key and paste your key; under Inbound port rules allow SSH (22), then add a custom rule for 80 scoped to your own IP. Then Review + create then Create.

Step 2 - Deploy from the Azure CLI

az vm create \
  --resource-group <your-rg> \
  --name termix \
  --image <marketplace-image-urn> \
  --size Standard_B2s \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard

Then open the web interface port, restricted to your own address:

az network nsg rule create \
  --resource-group <your-rg> \
  --nsg-name termixNSG \
  --name allow-termix-ui \
  --priority 900 \
  --source-address-prefixes <your-ip>/32 \
  --destination-port-ranges 80 \
  --access Allow --protocol Tcp

Step 3 - Confirm the services are running

SSH in as azureuser and confirm the stack is active. Termix's own services all listen on loopback and are reached through nginx, so the only ports open to the network are 22 for SSH and 80 for the web interface.

systemctl is-active termix nginx guacd
sudo ss -tlnH | awk '{print $4}' | grep -vE '^(127\.|\[::1\])' | sort -u
sudo ss -tlnH | awk '{print $4}' | grep -E '^127\.0\.0\.1:(300[0-9][0-9]|4822)$' | sort -t: -k2 -n

The termix, nginx and guacd services active, only ports 22 and 80 bound publicly, and every Termix backend service bound to 127.0.0.1

Step 4 - The first visit info note

Termix ships with no user account. On the first boot of every VM a one shot service writes an instance info note recording the web interface URL and the security steps to take. There is no password in it, because you set your own on first visit.

sudo sed -n '1,12p' /root/termix-credentials.txt

You can confirm from the command line that no account is baked into the image. setup_required is true only while the user count is zero, which is exactly the create administrator state:

curl -s -o /dev/null -w 'GET / -> HTTP %{http_code}\n' http://127.0.0.1/
curl -s http://127.0.0.1/users/setup-required; echo

The four secrets that protect your credential store are generated on this VM at first boot, not shipped in the image. They live in a file readable only by the termix service account:

sudo stat -c 'mode=%a owner=%U:%G  %n' /var/lib/termix/.env

The Termix instance info note with its security warnings, the health check returning HTTP 200, setup_required true proving zero users ship, and the four per VM secrets with their values redacted

Step 5 - Create your administrator on first visit

Browse to http://<vm-public-ip>/. Because the image ships with no account, Termix opens on its Create Account form. Choose a username and a strong password of at least six characters, confirm it, and select Sign Up. This one screen is where you secure the instance: there are no default credentials to change, because none ship. The first account created is automatically the administrator.

The Termix first visit Create Account form, where you set your own administrator username and password - the image ships with no default account

Step 6 - The dashboard

Sign in with the account you just created. The dashboard shows the running version, uptime, database health and your host totals, with quick actions for adding a host or a credential. The sidebar on the left carries every feature: Hosts, Credentials, Connections, Quick Connect, Serial, SSH Tools, Snippets, History, Session Logs, Split Screen, Network Graph, Alerts and Admin.

The Termix dashboard after sign in, showing version 2.5.1, database health, zero hosts configured and the quick actions panel

Step 7 - Add your first server

Select Add Host. On the General tab choose the SSH protocol, enter the address or hostname of a server you want to manage and give it a friendly name. On the SSH tab set the port, the username to log in as, and the authentication method: either a password or an SSH private key. Save the host and it appears in the Hosts list on the left.

Nothing is preconfigured in the image, so this list starts empty. The host you add here is your own server, reached from this VM, which means the VM needs network access to it on the relevant port.

Step 8 - Open a shell in the browser

Select the host you just added. The first time you connect, Termix shows a Verify SSH host key dialog with the server's SHA256 fingerprint. This is trust on first use verification and it is what protects you from a man in the middle on your own network: check the fingerprint matches the server you expect, then choose Accept & Continue. Termix remembers it for future connections.

The terminal then opens as a tab and you have a full interactive shell. The screenshot below shows a live session with real command output after running uname -a && whoami && uptime.

A live SSH session inside the Termix browser terminal, showing the output of uname, whoami and uptime from the connected server

Step 9 - Store credentials centrally

Open Credentials to keep SSH keys and passwords in one place and reuse them across hosts, instead of repeating them in every host entry. Credentials are written to the encrypted database, and each user's data key is unlocked only by their own password when they sign in, so the server cannot read your stored credentials while you are signed out.

The Termix credential manager, where SSH keys and passwords are stored encrypted and shared across hosts

Step 10 - Close sign ups

Self registration is enabled by default so that you can create that first administrator. Once you have, open Admin and turn user registration off unless you intend to let other people sign up. This is the single most important thing to do after your first sign in.

Step 11 - Confirm access control

Every route and every websocket refuses an anonymous caller. These checks send complete, well formed requests, so a refusal is a real authorisation decision and not a validation error:

curl -s -o /dev/null -w 'GET  /host/db/host -> HTTP %{http_code}\n' http://127.0.0.1/host/db/host
curl -s -o /dev/null -w 'GET  /users/me     -> HTTP %{http_code}\n' http://127.0.0.1/users/me
curl -s -o /dev/null -w 'GET  /credentials/ -> HTTP %{http_code}\n' http://127.0.0.1/credentials/

The websocket endpoints are the ones that matter most for a browser terminal, because that is where an interactive shell would be handed out. They verify your session inside the connection handler and close the connection with policy violation code 1008 when it is missing.

curl cannot show you this. A websocket rejection happens after the HTTP handshake succeeds, so curl reports HTTP 101 and exits regardless of whether the application then accepted or slammed the connection shut - it would look identical if the endpoint were wide open. The check below completes the handshake and then reports what the application actually did with the connection, which is the part that matters:

node -e 'const h=require("http"),c=require("crypto");const r=h.request({host:"127.0.0.1",port:80,path:"/ssh/websocket/",headers:{Connection:"Upgrade",Upgrade:"websocket","Sec-WebSocket-Key":c.randomBytes(16).toString("base64"),"Sec-WebSocket-Version":"13"}});let d=0;const f=s=>{if(!d){d=1;console.log("terminal websocket with no session -> "+s);process.exit(0)}};const cc=b=>b&&b.length&&(b[0]&15)===8?"refused, close code "+b.readUInt16BE(2):null;r.on("upgrade",(x,s,hd)=>{const e=cc(hd);if(e)return f(e);s.setTimeout(6000);s.on("data",b=>{const v=cc(b);if(v)f(v)});s.on("close",()=>f("refused, socket closed"));s.on("timeout",()=>f("OPEN - NOT REFUSED"))});r.on("response",x=>f("refused, HTTP "+x.statusCode));r.end();setTimeout(()=>f("TIMEOUT"),9000)'

Unauthenticated REST requests returning HTTP 401 and all three websocket endpoints refusing the connection with close code 1008

Step 12 - Verify the stack

jq -r '"Termix " + .version' /opt/termix/package.json
systemctl is-enabled termix termix-firstboot nginx guacd
sudo stat -c 'mode=%a owner=%U:%G  %n' /var/lib/termix

The deployed Termix version, all four units enabled for the customer boot, the 0700 data directory owned by the termix service account, and a fully patched OS

Step 13 - Where your data lives

Everything Termix stores - the host inventory, saved credentials, user accounts, session history and the per VM encryption keys - lives under /var/lib/termix, owned by the dedicated termix service account with mode 0700. The application itself is under /opt/termix and holds no state.

sudo ls -la /var/lib/termix | head -8

Back up that directory as a unit. It contains both the encrypted database and the .env file holding the keys that decrypt it, so a backup without .env cannot be restored.

Step 14 - Put TLS in front of it

The image serves plain HTTP on port 80. Until you terminate TLS, your Termix password and everything you type into a terminal cross the network in clear text, so this is not optional for anything beyond a short lived test.

Point a DNS name at the VM's public IP, open 443/tcp in the NSG, then issue a certificate with certbot, which will edit the nginx site in place:

sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d termix.your-domain.com --redirect --agree-tos -m you@your-domain.com

Certbot installs a renewal timer automatically. After it completes, reach Termix at https://termix.your-domain.com/.

Security notes

  • No default credentials. The image ships with zero user accounts. You create the first administrator on first visit and it becomes the administrator. Until you do, that form is open to anyone who can reach port 80, which is why the NSG rule should be scoped to your own IP address before first boot.
  • Per VM encryption keys. The four secrets that sign sessions and encrypt the credential store are generated from the system random source on your own VM at first boot and written to /var/lib/termix/.env with mode 0600. Nothing is baked into the image, so no two customer VMs share key material.
  • Encrypted credential store. Saved SSH keys and passwords are encrypted at rest. Each user's data encryption key is unwrapped only when they sign in with their password, so stored credentials are not readable while that user is signed out.
  • Loopback only backend. All twelve Termix backend services and the guacamole proxy are bound to 127.0.0.1 and reached through nginx, so the only network reachable ports are 22 and 80.
  • Authenticated websockets. The terminal, container console, serial and tunnel websockets each verify your session inside the connection handler and close with code 1008 without one. The terminal and serial endpoints additionally require an unlocked data key, so a session alone is not enough to reach stored credentials.
  • Host key verification. Termix verifies the SSH host key of every server on first connection and asks you to confirm the fingerprint before opening a session.
  • Close sign ups from Admin once your administrator account exists.
  • Patching. The image ships fully patched and unattended-upgrades remains enabled, so security updates continue to apply on your VM.

Support

cloudimg provides 24/7 support for this image. Contact support@cloudimg.co.uk with the VM name, region and the output of systemctl status termix nginx guacd.