Security AWS

JumpServer Privileged Access Bastion on AWS User Guide

| Product: JumpServer

Overview

JumpServer is an open source privileged access management (PAM) platform - a bastion host that sits between your engineers and your infrastructure. Rather than distributing SSH keys and database passwords, you vault those credentials inside JumpServer and grant people access to assets instead of to secrets. Every session they open is brokered by the gateway, recorded, and retrievable afterwards as a replay.

It brokers SSH, SFTP, RDP, VNC and database sessions, from a browser or from a standard SSH client, with role based access control, an asset and account inventory, and a searchable session audit trail.

This cloudimg AMI delivers JumpServer Community Edition v4.10.17 fully installed and wired as an eight container appliance. The web console answers on port 80 and the SSH and SFTP gateway on port 2222 within minutes of launch.

What is in the image

Component Role Host port
jms_web nginx front door serving the web console and REST API 80
jms_core Django core: authentication, RBAC, asset and account inventory, audit trail none
jms_celery asynchronous task worker (same image as core) none
jms_koko SSH and SFTP character gateway 2222
jms_lion browser based RDP and VNC gateway none
jms_chen browser based database client none
jms_postgresql PostgreSQL 16.10 datastore (the vault and the audit trail) none
jms_redis Valkey 8.1 cache and task broker none

Only ports 22, 80 and 2222 are open. The datastore and the cache publish no host port at all - they are reachable only on the private container bridge network.

Community Edition, and only Community Edition

JumpServer is open core: its Enterprise features ship as a separate proprietary component called XPack, pulled from a private vendor registry. This image contains Community Edition only. The build asserts that the shipped core image contains no XPack directory, re-asserts it against the running container, and confirms the live application settings report XPack disabled. Nothing in this guide describes an Enterprise feature. JumpServer Community Edition is licensed GPLv3; the corresponding source for every component is available from the upstream project at the pinned v4.10.17 tag.

One deliberate substitution

Upstream's stack uses Redis 7.4, which is source available under RSALv2 and SSPLv1 rather than an OSI approved open source licence. This image substitutes Valkey, the BSD-3-Clause Linux Foundation fork, as a protocol compatible drop in. The container keeps the service hostname redis so JumpServer's own wiring is untouched, but the image is valkey/valkey. Every component of the shipped appliance is therefore under an OSI approved licence.

Launching the instance

Launch the AMI from the AWS Marketplace listing. Recommended settings:

Setting Value
Instance type m5.large runs the full stack. Upstream's documented floor for a standalone deployment is 4 vCPU and 8 GB of memory, so choose m5.xlarge or larger for production concurrency.
Root volume 40 GiB gp3 (the default in the listing)
Data volume 100 GiB gp3, attached automatically, mounted at /var/lib/jumpserver
Security group inbound TCP 22 (SSH), 80 (web console and REST API), 2222 (SSH and SFTP gateway)
Key pair your own EC2 key pair - it is what you use for the operating system login

Restrict the security group to your own address ranges wherever you can. Port 80 serves the console; port 2222 is the bastion gateway your engineers connect through.

First boot takes a few minutes: a one-shot service generates every secret, initialises an empty database, brings the eight containers up and waits for the gateway to report healthy before it finishes.

Connecting to your instance

SSH to the operating system with your own EC2 key pair, as the login user for your AMI variant:

OS variant SSH login user Command
Ubuntu 24.04 ubuntu ssh -i your-key.pem ubuntu@<public-ip>

Note the distinction, because both are SSH and it matters:

Port What it is Who logs in
22 the operating system of the appliance itself you, with your EC2 key pair, as ubuntu
2222 the JumpServer bastion gateway your engineers, with their JumpServer accounts, to reach your assets

Retrieving your credentials

Every secret in this appliance is generated on your instance at first boot and exists nowhere else. Read them over SSH:

Step Command
Read the credentials file sudo cat /root/jumpserver-credentials.txt

The file is mode 0600, owned by root, and contains the console URL, the admin username, the per-instance administrator password and a per-instance REST API token.

You can confirm which keys are present without printing any value:

sudo grep -oE '^[A-Za-z_.]+=' /root/jumpserver-credentials.txt | sort -u

That administrator password is deliberately single use. JumpServer is armed to require you to choose a new password the first time you sign in interactively. The credentials file is not updated when you change it, so store the new password somewhere safe. The API token is a JumpServer private token and is not affected by the interactive password change, so automation keeps working.

Verifying the appliance from the command line

Every command in this section is safe to run on a live instance and prints no secret.

Services

systemctl is-active docker.service jumpserver-firstboot.service jumpserver.service

The eight containers

sudo docker ps --format '{{.Names}}\t{{.Image}}\t{{.Status}}' | sort

Note that jms_redis runs the valkey/valkey image - the container name and hostname are kept for wiring compatibility, but the software is Valkey, not Redis.

Component health through the front door

Each registered component reports its own health endpoint through the nginx front door. All four must answer 200:

for path in /api/health/ /koko/health/ /lion/health/ /chen/healthy; do
    printf '%s -> %s\n' "$path" "$(curl -sS -o /dev/null -w '%{http_code}' --max-time 20 "http://127.0.0.1$path")"
done

What actually listens on the host

ss -ltnH | awk '{print $4}' | sort -u

You should see 22, 80 and 2222 (plus the local systemd-resolved stub on 127.0.0.53). You should not see 5432 or 6379 in any address family. This check is deliberately written to enumerate every listener rather than to grep for one spelling of an address, because a dual stack service can bind [::ffff:127.0.0.1] and slip past a naive 0.0.0.0 match.

The datastore and cache publish no host port

for c in jms_postgresql jms_redis; do
    printf '%s ports: %s\n' "$c" "$(sudo docker inspect -f '{{json .NetworkSettings.Ports}}' "$c")"
done

A null value against each port means the container port is not published to the host at all. That is strictly stronger than binding to loopback: there is no host socket to reach.

The dedicated data volume

The whole stateful tier lives on its own EBS volume mounted at /var/lib/jumpserver, referenced in /etc/fstab by filesystem UUID so it reproduces on every instance launched from this AMI:

findmnt -no SOURCE,TARGET,FSTYPE /var/lib/jumpserver
df -h /var/lib/jumpserver /var/lib/containerd /

Both Docker stores are on that volume. This matters more than it looks: Docker 29 keeps image layers in the containerd content store, so relocating only Docker's data-root would leave several gigabytes of image layers on the OS root disk. /var/lib/containerd is bind mounted onto the data volume with its own /etc/fstab entry:

grep -E 'jumpserver|containerd' /etc/fstab

Every secret is per-instance

The image records a one-way SHA-256 digest of the secrets that were baked into it at build time. First boot replaces those secrets, so the digest of the live configuration must differ from the recorded image digest. Neither command prints a secret - both print a digest:

echo "image digest : $(sudo cat /var/lib/cloudimg/jumpserver-image-secrets.sha256)"
echo "live digest  : $(sudo grep -E '^(SECRET_KEY|BOOTSTRAP_TOKEN)=' /opt/jumpserver/config.txt | sha256sum | awk '{print $1}')"

If those two strings match, the appliance is still running the image's throwaway secrets and first boot has not completed. They must be different.

The vendor default credentials are rejected

JumpServer's own database migration seeds the admin account with the published default password ChangeMe. First boot replaces it before the appliance is reachable. Prove it against the live login endpoint:

for pw in ChangeMe admin; do
    code=$(curl -sS -o /tmp/jms-default-check.json -w '%{http_code}' --max-time 20 \
        -X POST http://127.0.0.1/api/v1/authentication/auth/ \
        -H 'Content-Type: application/json' \
        -d "{\"username\":\"admin\",\"password\":\"$pw\"}")
    printf 'admin/%s -> HTTP %s  token_issued=%s\n' "$pw" "$code" "$(grep -c '"token"' /tmp/jms-default-check.json)"
done
rm -f /tmp/jms-default-check.json

Both attempts must return HTTP 400 with token_issued=0. JumpServer temporarily locks an account after five failed sign-ins, so do not repeat this check in a loop.

Unauthenticated API access is refused

curl -sS -o /dev/null -w 'unauthenticated /api/v1/users/users/ -> HTTP %{http_code}\n' \
    --max-time 20 'http://127.0.0.1/api/v1/users/users/?limit=1'

Expect 401.

Signing in to the web console

Open http://<public-ip>/ in a browser and sign in as admin with the password from your credentials file.

The JumpServer sign-in page

JumpServer immediately requires you to choose your own password. This is the single-use behaviour described above, and it is armed deliberately at first boot.

The forced password reset presented at the first interactive sign-in

After setting your password you are asked to acknowledge the account security notice once. Tick the agreement and submit, and the console opens on the dashboard: live session and user counts, failed sessions today, and the user and asset activity trend.

The JumpServer console dashboard

Adding your first asset

An asset is something you want people to reach - a Linux host, a Windows host, a network device, a database, a cloud account or a web application. An account is a credential on that asset, held in JumpServer's vault and encrypted with the master key generated on your instance.

  1. Go to Console → Assets and click Create.
  2. Choose the asset type (Host for a Linux or Windows server), give it a name and its address, and pick the matching platform.
  3. Under Accounts, add the login that JumpServer should use on that asset - a username with either a password or an SSH private key. That secret is encrypted into the vault; nobody who is granted access to the asset ever sees it.
  4. Save. JumpServer can test the account and report connectivity against the real host.

The JumpServer asset inventory

Granting access

Access is granted by authorization, not by handing out the credential.

  1. Go to Console → Authorizations and click Create.
  2. Select the users or user groups, the assets or nodes, and which accounts they may use.
  3. Choose the protocols and the actions they are allowed - connect, upload, download.
  4. Save. The authorization takes effect within a few seconds; the gateway caches each user's permitted asset list briefly.

Asset authorizations granting users access to assets and accounts

Opening a recorded session

Your engineers reach their assets in either of two ways, and both are recorded.

From a browser. They sign in to the console, open Workbench, and click the asset. SSH and SFTP sessions open in the browser terminal; RDP and VNC sessions open through the lion gateway; database sessions open through the chen client. Nothing needs installing on their machine.

From a standard SSH client, through the gateway on port 2222. The login string names the JumpServer user, the account on the asset, the asset's address, and the JumpServer host:

Form Value
Login string <jumpserver-user>@<account>@<asset-address>@<public-ip>
Command ssh -p 2222 '<jumpserver-user>@<account>@<asset-address>@<public-ip>'
Interactive menu ssh -p 2222 '<jumpserver-user>@<public-ip>' then choose from the asset list

The asset is matched by its address, not by its display name; the display-name form returns match asset failed: No found asset.

Every session that completes appears under Console → Audit → Sessions with its user, asset, account, protocol and duration, and the terminal replay is retrievable from there. Session recordings are stored on the dedicated data volume, so they grow independently of the OS disk.

Operating the appliance

Task Command
Restart the whole stack sudo systemctl restart jumpserver.service
Stop the stack sudo systemctl stop jumpserver.service
Follow the core application log sudo docker logs -f jms_core
Follow the gateway log sudo docker logs -f jms_koko
Inspect the compose configuration sudo docker compose -f /opt/jumpserver/docker-compose.yml --env-file /opt/jumpserver/config.txt ps

The appliance configuration lives in /opt/jumpserver/config.txt (mode 0600, root only). It holds the master key, the component bootstrap token and the datastore passwords for your instance. Do not copy it off the instance, and do not share it.

Registered components - the SSH gateway, the RDP and VNC gateway and the database client - are given a reduced copy of that configuration at /opt/jumpserver/config_safe.txt which deliberately excludes the datastore host, port and passwords. They authenticate to core with the bootstrap token and have no business holding database credentials. You can confirm the exclusion holds without printing anything sensitive:

for k in DB_HOST DB_PORT DB_PASSWORD REDIS_PASSWORD; do
    printf '%s present in config_safe.txt: %s\n' "$k" \
        "$(sudo grep -c "^${k}=" /opt/jumpserver/config_safe.txt)"
done

Every count must be 0.

Backup

Everything worth backing up - the vault, the asset and account inventory, the users and authorizations, the audit trail and the session recordings - lives under /var/lib/jumpserver on the dedicated EBS volume. Snapshot that volume with EBS snapshots or AWS Backup on whatever schedule your retention policy requires.

Back up /opt/jumpserver/config.txt too, separately and securely. It holds the master key that decrypts the vault, so a database snapshot without it is not restorable.

Adding TLS and your own domain

The image serves plain HTTP on port 80 so it works the moment it launches. For anything beyond an initial trial you should terminate TLS in front of it:

  • Put an Application Load Balancer with an AWS Certificate Manager certificate in front of the instance, forwarding to port 80, and restrict the instance security group to the load balancer.
  • Or terminate TLS on the instance with your own certificate and a reverse proxy.

Point your own DNS name at whichever you choose. Contact cloudimg support if you would like us to walk through either option with you.

Support

cloudimg provides 24/7 technical support for this image by email and live chat.

  • Email: support@cloudimg.co.uk
  • We help with deployment, connecting your first assets and accounts, access control design, TLS termination with your own certificate and domain, session recording retention, datastore backup, instance sizing and upgrade guidance.

JumpServer Community Edition is licensed GPLv3. The corresponding source for JumpServer and each of its bundled components is available from the upstream project at the pinned v4.10.17 release tag.