Ss
Networking Azure

SSHPiper on Ubuntu 24.04 on Azure User Guide

| Product: SSHPiper on Ubuntu 24.04 LTS on Azure

Overview

This guide covers the deployment and configuration of SSHPiper on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. SSHPiper is the missing reverse proxy for sshd: a single static Go binary (sshpiperd) that presents one SSH entry point and routes each incoming connection to an upstream SSH host based on the login username, using a pluggable router. Acting as a bastion, it centralises access to a fleet of backend servers behind one gateway, with per user mapping and no agent or change required on the upstream hosts.

The image installs SSHPiper pinned to a specific release and verified by SHA-256 at build time, baked reproducibly into the image (the exact version and checksum are recorded in /opt/sshpiper/VERSION). sshpiperd runs under systemd as the unprivileged sshpiper system user (a hardened unit: NoNewPrivileges, ProtectSystem=strict, PrivateTmp). The system OpenSSH server stays on port 22 for normal administrative access; SSHPiper is the network facing gateway on port 2222.

Secure by default: the image ships with zero keys and zero credentials. On the very first boot this image generates, uniquely for this VM, the SSHPiper host key, an upstream key pair and a demo client key pair, and it stands up a working demo pipe so you can see the reverse proxy routing immediately. The demo pipe routes the downstream user demo (authenticated with the per VM demo client key) to the upstream account demobackend on the local SSH server, using a per VM key SSHPiper holds. The gateway details and the path to the demo client key are written to /root/sshpiper-credentials.txt (mode 0600, root only). No shared secret and no default login ever ships inside the image.

What is included:

  • SSHPiper (sshpiperd), pinned and SHA-256 verified, run under systemd as the unprivileged sshpiper system user

  • The SSH reverse proxy gateway on port 2222, routing each connection to an upstream host by login username via the yaml router

  • The system OpenSSH server on port 22 for administration, unchanged

  • A working demo pipe generated on first boot (demo -> demobackend@127.0.0.1:22) that proves the proxy routes and remaps the username, ready to be replaced with your own pipes

  • Every key generated uniquely per VM on first boot — the SSHPiper host key, the upstream key pair and the demo client key — with nothing secret baked into the image

  • The per VM gateway details and demo client key path in /root/sshpiper-credentials.txt (0600) — no shared secret, no default login

  • Ubuntu 24.04 LTS base with latest security patches applied at build time

  • Azure Linux Agent for seamless cloud integration and SSH key injection

  • 24/7 cloudimg support with guaranteed 24 hour response SLA

Prerequisites

  • Active Azure subscription, SSH public key, VNet + subnet in target region

  • Subscription to the SSHPiper listing on Azure Marketplace

  • One or more upstream SSH servers you want to route connections to (the image ships with a local demo upstream so you can try it immediately)

Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) is ample for a gateway proxying many concurrent SSH sessions. Larger fleets can use Standard_D2s_v5 or larger. SSHPiper relays SSH sessions only; it holds no data plane state.

Step 1: Deploy from the Azure Portal

Search SSHPiper in Marketplace, select the cloudimg publisher, click Create. NSG rules: TCP 22 (administration) and TCP 2222 (the SSH gateway) from the networks your users connect from.

Step 2: Deploy from the Azure CLI

RG="sshpiper-prod"; LOCATION="eastus"; VM_NAME="sshpiper-01"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/sshpiper-ubuntu-24-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az vm create \
  --resource-group "$RG" --name "$VM_NAME" \
  --image "$GALLERY_IMAGE_ID" \
  --size Standard_B2s \
  --admin-username azureuser \
  --ssh-key-values "$SSH_KEY" \
  --public-ip-sku Standard
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 22 --priority 1001
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 2222 --priority 1002

Step 3: Confirm the gateway is running

SSH in on port 22 as azureuser for administration. The SSHPiper gateway runs as the sshpiper service and listens on port 2222; the system SSH server continues to listen on port 22.

sudo systemctl status sshpiper --no-pager | head -14
ss -tln | grep -E ':2222|:22 '

sshpiper.service is active (running), the gateway is listening on :2222, and the system SSH server is on :22.

sshpiper.service active (running) with the pinned SSHPiper version; ss shows the SSHPiper gateway listening on port 2222 and the system OpenSSH server on port 22

Step 4: Retrieve the per instance gateway details

The gateway details, the demo pipe mapping and the path to the per VM demo client key are generated on the first boot and stored in a root only file. Read it over SSH:

sudo cat /root/sshpiper-credentials.txt

The file records the gateway host and port, the demo downstream username (demo), the upstream account it maps to (demobackend) and the path to the per VM demo client private key (/root/sshpiper-demo-key). The active routing rule lives in the yaml router config:

sudo cat /etc/sshpiper/sshpiperd.yaml

The per instance sshpiper-credentials.txt showing the gateway host and port, the demo to demobackend mapping and the demo client key path, alongside the yaml router config that defines the pipe

Step 5: Try the demo pipe (a real proxied SSH hop)

The demo pipe proves the reverse proxy end to end. Connecting to the gateway on port 2222 as the downstream user demo, authenticated with the per VM demo client key, routes the session to the upstream account demobackend on the local SSH server. whoami on the far side therefore returns demobackend, not demo — SSHPiper remapped the username and connected to the upstream on your behalf:

sudo ssh -p 2222 -i /root/sshpiper-demo-key \
  -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
  demo@127.0.0.1 whoami

An unmapped username has no pipe and is rejected by the gateway:

sudo ssh -p 2222 -i /root/sshpiper-demo-key \
  -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
  -o ConnectTimeout=10 nosuchpipe@127.0.0.1 whoami 2>&1 | tail -1 || true

The first command prints demobackend; the second is refused because no pipe matches that username.

A real proxied SSH hop through the gateway: ssh as the demo user on port 2222 returns demobackend from whoami, proving SSHPiper routed the connection to the upstream account and remapped the username; an unmapped username is rejected

Connect from your workstation

To reach the gateway from your own machine, copy the per VM demo client key off the VM first, then connect to the gateway host on port 2222 (the commands below run on your workstation, not on the VM):

# from your workstation (replace <public-ip> with your VM public IP)
scp azureuser@<public-ip>:/root/sshpiper-demo-key ~/sshpiper-demo-key   # copy the key (run the cp as root on the VM first if needed)
chmod 600 ~/sshpiper-demo-key
ssh -p 2222 -i ~/sshpiper-demo-key demo@<public-ip> whoami

Step 6: Add your own pipe

SSHPiper routes by the downstream login username using the yaml router at /etc/sshpiper/sshpiperd.yaml. Each pipe has a from block (which downstream username to match and which public keys to accept) and a to block (the upstream host, the username to log in as, and the private key SSHPiper should use). To route a new username alice to your own upstream, add a pipe like this:

version: "1.0"
pipes:
  - from:
      - username: "alice"
        authorized_keys:
          - /var/lib/sshpiper/alice/authorized_keys   # alice's public key(s)
    to:
      host: 10.0.0.20:22            # your upstream host
      username: appuser             # the account to log in as upstream
      ignore_hostkey: true          # or set known_hosts for strict checking
      private_key: /var/lib/sshpiper/alice/upstream_key   # key SSHPiper uses upstream

After editing the router config, apply it by restarting the gateway:

sudo systemctl restart sshpiper
sudo systemctl is-active sshpiper

You can also match usernames by regular expression, accept passwords, or pin upstream host keys with known_hosts for strict verification. See the SSHPiper project documentation for the full router reference.

The exact pinned SSHPiper release and its SHA-256 are recorded in /opt/sshpiper/VERSION, so the image is reproducible and auditable:

cat /opt/sshpiper/VERSION

The /opt/sshpiper/VERSION provenance file showing the pinned release tag and verified SHA-256 checksum, alongside an example of adding your own pipe to the yaml router and restarting the gateway

Step 7: Server components

Component Version / Detail
Gateway SSHPiper sshpiperd (pinned + SHA-256 verified, see /opt/sshpiper/VERSION)
Gateway port 2222 (SSH reverse proxy, routes by username)
Router yaml router plugin at /etc/sshpiper/sshpiperd.yaml (0600)
Admin SSH system OpenSSH server on port 22 (unchanged)
Service account sshpiper (unprivileged system user, hardened unit)
Per VM keys host key + upstream key + demo client key, all generated on first boot
Demo pipe demo -> demobackend@127.0.0.1:22 (replace with your own pipes)
Credentials per VM gateway details + demo key path, /root/sshpiper-credentials.txt (0600)
Operating system Ubuntu 24.04 LTS (patched at build)
License MIT (SSHPiper, Copyright (c) 2014 Boshi Lian)

Step 8: Managing the SSHPiper service

Check the gateway is running and enabled at boot:

sudo systemctl is-active sshpiper
sudo systemctl is-enabled sshpiper

Use sudo systemctl restart sshpiper to apply router config changes, sudo systemctl status sshpiper for the full unit state, and sudo journalctl -u sshpiper -f to follow the live logs (each rejected connection to an unmapped username is logged there — that is normal for a public gateway).

The router config and per VM keys live under /etc/sshpiper and /var/lib/sshpiper and persist across service restarts and VM reboots.

Step 9: Security recommendations

  • Restrict the NSG. Allow TCP 2222 (and 22 for administration) only from the networks your users connect from. The gateway does not need to be reachable from the whole internet unless your users are.

  • Replace the demo pipe before production. The demo pipe exists to prove the proxy works. Remove it from /etc/sshpiper/sshpiperd.yaml and add your own pipes mapping real users to your upstream hosts.

  • Pin upstream host keys. Set known_hosts on each to block (instead of ignore_hostkey: true) so SSHPiper verifies the upstream server identity.

  • Prefer key based downstream auth. Give each pipe its own authorized_keys so only the intended user's key is accepted, and rotate keys when people leave.

  • Keep the OS patched. Unattended security upgrades remain enabled on the running VM.

  • Back up /etc/sshpiper and /var/lib/sshpiper to preserve your router config and keys.