Deployment guide

Jumpserver

Overview

JumpServer is an open source privileged access management (PAM) bastion: a single audited entry point in front of your infrastructure. Operators sign in to JumpServer instead of to your servers. JumpServer holds the real credentials in an encrypted vault, injects them at connection time, and records every session it brokers so that it can be replayed. It fronts SSH and SFTP, RDP and VNC, databases and Kubernetes, so one gateway covers your estate.

This SecureAnyCloud image runs JumpServer Community Edition, which is free, open source and licensed under GPL-3.0. It runs the officially supported way, as the upstream containers orchestrated by Docker Compose under systemd. All images are pinned by digest and captured into the VM, so your instance starts without pulling anything. The enterprise XPack components are not present and not enabled.

Because JumpServer guards your credentials, nothing ships with a known secret. On first boot, before the port is reachable, your VM generates a unique SECRET_KEY (the master key that encrypts every vaulted credential), a component BOOTSTRAP_TOKEN, a database password, a cache password, an administrator password and an API token.

JumpServer is a trademark of its respective owner. This image is produced by SecureAnyCloud and is not affiliated with, endorsed by, or sponsored by JumpServer. It ships the free and open source GPL-3.0 Community Edition software, unmodified.

What you get:

  • JumpServer Community Edition v4.10.17 with core, celery, koko, lion, chen and web, each pinned by image digest
  • koko, the SSH and SFTP character gateway, published on port 2222
  • lion for browser-based RDP and VNC, and chen for the browser-based database client
  • A bundled PostgreSQL 16 database, pinned by digest, reachable only inside a private Docker network and never published to a host port
  • Valkey 8.1 (BSD-3-Clause) as the cache, in place of the source-available Redis used upstream, likewise never published to a host port
  • A dedicated 100 GiB data disk mounted at /var/lib/jumpserver, holding the database, the vault contents and every session recording
  • jumpserver.service and jumpserver-firstboot.service as systemd units, enabled and active on boot
  • A unique SECRET_KEY, BOOTSTRAP_TOKEN, database password, cache password, admin password and API token, generated per VM on first boot and never baked into the image
  • A clean, empty database on first boot: no default login, no shipped secret, no prior data
  • 24/7 SecureAnyCloud support

Prerequisites

You need an active Azure subscription, an SSH key pair, and a VNet with a subnet in the target region.

JumpServer runs a multi-container stack, so Standard_B4ms (4 vCPU / 16 GiB RAM) is the recommended starting size. The upstream minimum is 4 cores and 8 GiB.

Configure the network security group (NSG) to allow inbound 22/tcp from your management network for host administration, 80/tcp for the web console and 2222/tcp for the SSH bastion gateway. The console binds to the address the VM resolves on first boot. For production, put it behind your own domain and a real certificate (see Step 11).

Step 1 - Deploy from the Azure Marketplace

Sign in to the Azure Portal, choose Create a resource, search the Marketplace for JumpServer by SecureAnyCloud, and select Create.

On the Basics tab, pick your subscription, resource group, region and size (Standard_B4ms or larger). Under Administrator account, choose SSH public key and paste your key. Under Inbound port rules, allow SSH (22) and HTTP (80).

Select Review + create, then Create. When the VM exists, add an inbound rule for 2222/tcp so operators can reach the SSH gateway.

Step 2 - Deploy from the Azure CLI

# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:jumpserver-ubuntu-24-04:default:latest
az vm create \
  --resource-group <your-rg> \
  --name jumpserver \
  --image sac1647283583153:jumpserver-ubuntu-24-04:default:latest \
  --size Standard_B4ms \
  --admin-username azureuser \
  --ssh-key-values ~/.ssh/id_ed25519.pub \
  --vnet-name <your-vnet> --subnet <your-subnet> \
  --public-ip-sku Standard
az vm open-port --resource-group <your-rg> --name jumpserver --port 80 --priority 1010
az vm open-port --resource-group <your-rg> --name jumpserver --port 2222 --priority 1020

Step 3 - Connect to your VM

Connect to the VM over SSH as azureuser:

ssh azureuser@<vm-public-ip>

Step 4 - Confirm the services are running

First boot generates every secret and brings the stack up. jumpserver-firstboot.service runs once, then jumpserver.service keeps the Compose project running. Check both services and the containers:

sudo systemctl is-active jumpserver.service docker.service
sudo docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}'

Expected output:

active active NAMES IMAGE STATUS jms_web jumpserver/web:v4.10.17-ce Up 2 minutes (healthy) jms_lion jumpserver/lion:v4.10.17-ce Up 2 minutes (healthy) jms_celery jumpserver/core:v4.10.17-ce Up 2 minutes jms_koko jumpserver/koko:v4.10.17-ce Up 2 minutes (healthy) jms_chen jumpserver/chen:v4.10.17-ce Up 2 minutes (healthy) jms_core jumpserver/core:v4.10.17-ce Up 2 minutes (healthy) jms_postgresql postgres:16.10-bookworm Up 3 minutes (healthy) jms_redis valkey/valkey:8.1-bookworm Up 3 minutes (healthy)

The core API and the SSH gateway each expose a health endpoint. Query both:

curl -s -o /dev/null -w 'core: HTTP %{http_code}\n' http://127.0.0.1/api/health/
curl -s -o /dev/null -w 'koko: HTTP %{http_code}\n' http://127.0.0.1/koko/health/

Expected output:

core: HTTP 200 koko: HTTP 200

Watch koko in particular. It is the component that brokers SSH sessions, so a 200 from koko means the bastion can do its job.

Step 5 - Secure by default: no login ships with the image

The API refuses unauthenticated calls. The datastores are not reachable from the host at all: PostgreSQL and Valkey publish no host port and live only on the private container network. Verify both:

curl -s -o /dev/null -w 'no token: HTTP %{http_code}\n' http://127.0.0.1/api/v1/users/users/
sudo ss -ltn | awk 'NR==1 || /:80 |:2222 |:5432 |:6379 /'
sudo docker ps --format '{{.Names}} {{.Ports}}' | grep -E 'postgresql|redis'

Expected output: only 80 (console) and 2222 (SSH gateway) are bound on a host interface, and neither datastore maps a port.

no token: HTTP 401 State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 4096 0.0.0.0:2222 0.0.0.0:* LISTEN 0 4096 0.0.0.0:80 0.0.0.0:* jms_postgresql 5432/tcp jms_redis 6379/tcp

The upstream default administrator password does not work either, because first boot replaces it before the console is reachable:

curl -s -o /dev/null -w "default password: HTTP %{http_code}\n" \
  -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"ChangeMe"}' \
  http://127.0.0.1/api/v1/authentication/auth/

Expected output is 400: the credential is rejected and no token is issued.

default password: HTTP 400

Step 6 - Your per-VM credentials

First boot writes the generated credentials to a root-only file. List the file and view it with the secrets masked:

sudo ls -l /root/jumpserver-credentials.txt
sudo sed -E 's/(pass|PASSWORD|TOKEN)=.*/\1=********/' /root/jumpserver-credentials.txt | head -14

Expected output, with the secrets masked. The real file holds the values:

-rw------- 1 root root 1587 Jul 19 06:11 /root/jumpserver-credentials.txt
# JumpServer — generated on first boot by jumpserver-firstboot.service
# These credentials are unique to this VM. Store them somewhere safe. jumpserver.url=http://10.0.0.22/ jumpserver.admin.user=admin jumpserver.admin.pass=********
# Canonical keys consumed by the Marketplace smoke verifier + guide substitution: JUMPSERVER_ADMIN_PASSWORD=<set-at-first-boot> JUMPSERVER_API_TOKEN=<set-at-first-boot>

The API token is a JumpServer private token for the admin user. It keeps working after you change the console password, so your automation is not interrupted. Test it:

TOKEN=$(sudo grep '^JUMPSERVER_API_TOKEN=' /root/jumpserver-credentials.txt | cut -d= -f2-)
curl -s -o /dev/null -w 'with token: HTTP %{http_code}\n' \
  -H "Authorization: Token ${TOKEN}" http://127.0.0.1/api/v1/users/users/

Expected output:

with token: HTTP 200

Step 7 - Sign in to the console

Browse to http://<vm-public-ip>/ and sign in as admin with the password from the credentials file.

JumpServer requires you to choose a new password at your first interactive sign-in. Set the new password and store it safely, because the credentials file is not updated when you change it.

After you sign in, the Console dashboard shows live session counts, user and asset totals, and recent activity.

Step 8 - Add an asset and vault its credential

An asset is a machine you want to reach. An account is the credential on that machine. JumpServer stores it encrypted under the SECRET_KEY of this VM, so your operators never need to hold it.

In the Console, go to Assets, choose Create and add a host. Give it a name, its address and the Linux platform. Then add an account to it, which is a username plus either a password or an SSH private key. JumpServer vaults the secret. Finally, under Authorizations, grant a user or group permission to that asset with that account.

You can do the same over the API, using the token from Step 6:

TOKEN=$(sudo grep '^JUMPSERVER_API_TOKEN=' /root/jumpserver-credentials.txt | cut -d= -f2-)
curl -s -H "Authorization: Token ${TOKEN}" \
  'http://127.0.0.1/api/v1/assets/hosts/?limit=5' \
  | head -c 400; echo

This returns the JSON list of hosts JumpServer currently manages.

Step 9 - Connect through the bastion

Operators do not SSH to the target. They SSH to JumpServer on port 2222, and JumpServer opens the session for them using the vaulted credential. Running ssh -p 2222 <jumpserver-user>@<vm-ip> opens an interactive menu of everything that user is permitted to reach.

To go straight to one target, use the direct connect form of koko. It takes the JumpServer user, the account, the asset address and then the JumpServer host:

ssh -p 2222 '<jumpserver-user>@<account>@<asset-address>@<vm-ip>'

The gateway brokers the session. The shell that answers is the target's, reached with a credential the operator never saw.

Step 10 - Every session is recorded

The audit trail is the point of a bastion. Go to the Audits workspace, then Asset sessions, and open Historical sessions. Each brokered session is listed with the JumpServer user, the target, the account used, the protocol and the start time. Select Playback to replay exactly what happened.

Recordings, the vault and the database all live on the dedicated data disk. Confirm the mount:

mountpoint -q /var/lib/jumpserver && echo "/var/lib/jumpserver is a dedicated volume" df -h /var/lib/jumpserver | tail -1

Expected output:

/var/lib/jumpserver is a dedicated volume /dev/sda 98G 8.5G 85G 10% /var/lib/jumpserver

Because this data sits on its own disk, you can grow or snapshot your audit history independently of the OS disk.

Step 11 - Production: your own domain with TLS

The image serves the console over HTTP on port 80, so it works as soon as the VM boots. For production, terminate TLS in front of it. Point a DNS name at the VM, put a reverse proxy or an Azure Application Gateway with a certificate for that name in front, and restrict 80/tcp to that proxy.

Keep 2222/tcp reachable for the SSH gateway, and restrict 22/tcp to your management network. Operators should use the bastion, not the host.

Back up /var/lib/jumpserver (database, vault and recordings), and keep /root/jumpserver-credentials.txt somewhere safe. The SECRET_KEY in /opt/jumpserver/config.txt decrypts every vaulted credential. Without it, a restored database is unreadable.

Support

SecureAnyCloud provides 24/7 support for this image. Contact us at sales@secureanycloud.com or visit secureanycloud.com.

Deploy on Azure

Launch JumpServer on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure