Back to tutorials
Tutorial

Tutorial: Set Up a VPS Jump Box (Bastion Host) for Safer SSH Access in 2026

Tutorial to set up a VPS jump box (bastion host) for safer SSH access: keys, firewall rules, ProxyJump, and hardening steps.

By Anurag Singh
Updated on Aug 09, 2026
Category: Tutorial
Share article
Tutorial: Set Up a VPS Jump Box (Bastion Host) for Safer SSH Access in 2026

A public SSH port on every server is convenient. It’s also why your auth logs never stop filling with scans and password-spray noise. This VPS jump box tutorial shows how to set up a small bastion host as a single, well-defended entry point for admin access. Your web, database, and mail servers can stop exposing SSH to the internet.

The pattern is simple. SSH into one hardened box. Then hop to internal servers using keys. You’ll also tighten firewall rules and set SSH defaults that match how teams manage fleets in 2026.

What you’ll build (and why it helps)

A jump box (bastion host) is a minimal server. Its main job is to accept SSH from trusted locations and route you to other servers.

In practice, it lets you manage multiple VPS and dedicated machines without leaving port 22 open on each one.

  • One public SSH surface area: the bastion has a public IP; target servers don’t need public SSH.
  • Cleaner firewall policy: targets allow SSH only from the bastion’s IP.
  • Better audit trail: admin access funnels through one place, which simplifies logging and access control.

If you want HostMyCode to handle OS patching and baseline hardening for the bastion and app servers, start with managed VPS hosting. If you prefer full control, a standard HostMyCode VPS works well for this layout.

Prerequisites

  • One small VPS to use as the bastion (Ubuntu 24.04 LTS is a practical default in 2026).
  • One or more target servers (VPS or dedicated) you want to protect (Ubuntu/Debian/AlmaLinux/Rocky all work).
  • SSH key pair on your admin machine.
  • Root or sudo access on all servers.

Network layout and naming (pick a clean convention)

Decide how target servers will be reachable from the bastion. You have two common options:

  1. Private networking (best): targets have private IPs; only the bastion needs a public IP.
  2. Public IP + firewall restriction (acceptable): targets keep public IPs, but SSH only allows the bastion’s public IP.

Pick hostnames that stay clear once you have more than a couple machines. Example:

  • bastion01 → public IP (and optionally private IP)
  • web01, mail01, db01 → private IPs preferred

If you need to migrate DNS during a hosting change, read this DNS migration tutorial to avoid downtime and mail breakage.

Step 1: Provision the bastion VPS and update it

Create a small VPS (1 vCPU / 1–2 GB RAM is often enough for a bastion). Patch it immediately:

sudo apt update
sudo apt -y full-upgrade
sudo reboot

After reboot, confirm you’re current:

uname -a
ssh -V

In 2026, OpenSSH versions vary by distro and update cadence. What matters is your maintenance model. Stay on a vendor-maintained OpenSSH release. Keep security updates flowing.

Step 2: Create an admin user and lock down root SSH

On the bastion, create a non-root admin user and grant sudo:

sudo adduser admin
sudo usermod -aG sudo admin

Copy your SSH key to the new user (from your laptop):

ssh-copy-id admin@BASTION_PUBLIC_IP

Now lock down SSH on the bastion. Edit /etc/ssh/sshd_config:

sudo nano /etc/ssh/sshd_config

Recommended baseline settings for a bastion:

PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no
UsePAM yes
PubkeyAuthentication yes
X11Forwarding no
AllowTcpForwarding yes
GatewayPorts no
ClientAliveInterval 300
ClientAliveCountMax 2
AllowUsers admin

Validate the config and reload SSH:

sudo sshd -t
sudo systemctl reload ssh

Keep your current SSH session open while you test a new login in another terminal.

Step 3: Add a strict firewall policy on the bastion

On Ubuntu, UFW is a good fit for a bastion. The rules stay readable. Changes are easy to review later.

Start with deny-by-default inbound, and allow SSH only:

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw enable
sudo ufw status verbose

If you have a static office IP, tighten it further. Allow SSH only from that address:

sudo ufw delete allow 22/tcp
sudo ufw allow from YOUR_OFFICE_IP/32 to any port 22 proto tcp

If you’re often on changing networks, a VPN or an extra access gate usually beats constantly editing allowlists. For a broader checklist, see SSH hardening steps for VPS and dedicated servers.

Step 4: Harden the bastion for “boring reliability”

A bastion should be dull. Fewer packages and fewer daemons mean fewer things to patch. You also get fewer surprises.

  • Remove anything you don’t need (for example, FTP servers, web servers, unused database clients).
  • Enable unattended security updates (Ubuntu):
sudo apt -y install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades

Then verify what’s listening on the network:

sudo ss -tulpn

Ideally you see mostly sshd—and not much else.

Step 5: Prepare each target server to accept SSH only from the bastion

On each target server, apply the same core SSH hardening. Disable root SSH. Disable password auth.

Then add firewall rules so SSH is accepted only from the bastion’s IP.

On a target server (UFW example):

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow from BASTION_IP to any port 22 proto tcp
sudo ufw enable
sudo ufw status verbose

If the target server runs mail or DNS, open only the ports that service needs. Don’t paste “web ports” onto everything by habit.

Before enabling the firewall, make sure you have console access (VNC/KVM/provider console). Alternatively, keep an existing root session open so you can revert if needed.

Step 6: Use SSH ProxyJump from your laptop (the cleanest workflow)

ProxyJump lets you reach a target server through the bastion with one command. You don’t need to SSH into the bastion first. You also don’t need a second SSH hop.

On your laptop, edit ~/.ssh/config:

Host bastion01
  HostName BASTION_PUBLIC_IP
  User admin
  IdentitiesOnly yes
  IdentityFile ~/.ssh/id_ed25519

Host web01
  HostName 10.0.0.11
  User admin
  ProxyJump bastion01
  IdentitiesOnly yes
  IdentityFile ~/.ssh/id_ed25519

Host mail01
  HostName 10.0.0.12
  User admin
  ProxyJump bastion01
  IdentitiesOnly yes
  IdentityFile ~/.ssh/id_ed25519

Now connect directly:

ssh web01
ssh mail01

If targets don’t have private IPs, set HostName to the public IP. Still lock SSH to the bastion’s IP in the firewall. Exposure drops because only the bastion can reach port 22.

Step 7: Make agent forwarding safer (or avoid it)

Agent forwarding (-A) is convenient. It also widens the blast radius if the bastion is compromised.

A safer default looks like this:

  • Keep your private keys on your laptop.
  • Use ProxyJump instead of agent forwarding.
  • If you must use agent forwarding, enable it only for specific hosts, not globally.

Example for a single host:

Host web01
  ForwardAgent no

Step 8: Add a second gate: port knocking or a VPN (optional, but practical)

If the bastion is your only public SSH endpoint, adding one more gate is often worth it. Port knocking can work well when you can’t rely on static IPs.

HostMyCode has a dedicated walk-through here: port knocking to hide SSH on a VPS.

If you have multiple admins, a VPN is often easier than maintaining allowlists. Keep membership tight. Make offboarding a checklist item, not a “later” task.

Step 9: Logging and basic alerting for the bastion

The bastion is where you should notice trouble first. At minimum, set expectations for:

  • auth logs retained and reviewed
  • resource alerts (CPU spikes can point to brute force attempts or tunneling abuse)
  • SSH login notifications for unexpected IPs

For a practical baseline, use this server monitoring tutorial and treat the bastion as a priority node.

Quick SSH log checks:

sudo journalctl -u ssh --since "24 hours ago" --no-pager
sudo grep -i "failed" /var/log/auth.log | tail -n 50

Step 10: Common problems (and fast fixes)

Problem: “Connection timed out” when SSHing to a target through the bastion

  • Check the target firewall allows SSH from the bastion IP (correct IP, correct interface).
  • From the bastion, test reachability to the target:
ssh admin@10.0.0.11
nc -vz 10.0.0.11 22

Problem: ProxyJump works, but you land as the wrong user

Set User per-host in ~/.ssh/config. Don’t assume the same username exists on every machine.

Problem: Key is ignored, and SSH prompts for a password

  • On the target, check /etc/ssh/sshd_config includes PubkeyAuthentication yes.
  • Verify permissions:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

Problem: You locked yourself out after enabling the firewall

This is why you keep an existing session open. Verify rules before you enable them.

If you do get locked out, use the provider console to disable UFW temporarily:

sudo ufw disable

Then fix the rules and re-enable it.

Operational checklist: your “done” definition

  • Bastion: password logins disabled, root SSH disabled, UFW deny-by-default, SSH restricted to trusted IPs or protected by an extra gate.
  • Targets: SSH only allowed from bastion (and from provider console networks if required).
  • Keys: unique keys per admin, revoked keys removed promptly.
  • Monitoring: bastion login attempts and resource usage alerting enabled.
  • Backups: you can rebuild the bastion quickly (config documented, keys managed, recovery plan tested).

On backups: a jump box is often easier to rebuild than restore. Your broader server estate usually isn’t.

If you haven’t set retention and run restore tests, start with this 3-2-1 VPS backup strategy.

Where HostMyCode fits

A jump box only pays off if it stays predictable. That means stable routing, consistent access rules, and timely security updates.

It’s also the “small server that matters.” It’s the one teams tend to neglect.

If you’re doing this for client hosting or reseller operations, put the bastion on its own small VPS. Keep web/mail nodes separate.

That separation limits blast radius.

For higher-traffic fleets, pair bastion access with a dedicated server layer. Use it for workloads where isolation matters.

If you want a clean starting point for a bastion + private server layout, deploy a small HostMyCode VPS as your jump box and lock SSH down to it across your fleet. Prefer fewer moving parts? Use managed VPS hosting so patching and baseline hardening don’t get skipped.

FAQ

Do I need a VPS jump box if I only manage one server?

Not always. For a single VPS, strong SSH hardening plus a strict firewall is usually enough. A jump box starts paying for itself once you have multiple servers or multiple admins.

Should the bastion run any websites or control panels?

No. Keep it single-purpose. Mixing a bastion with a public web stack increases attack surface and makes patching priorities messier.

Can I use the bastion to reach cPanel/WHM securely?

Yes. You can tunnel admin ports through SSH without opening new firewall ports. If you need that workflow, use SSH local forwarding and keep WHM access private.

Do targets need private IPs for this to work?

Private IPs are ideal, but not required. You can still restrict SSH on targets to the bastion’s public IP. You’ll reduce exposure even if targets keep public addresses.

Summary

This VPS jump box tutorial gave you a repeatable setup. You now have one hardened public entry point, locked-down target servers, and a comfortable workflow using ProxyJump.

It’s intentionally plain. It works. Expect quieter auth logs, fewer open SSH endpoints, and a cleaner story for admin access.

If you’re rolling this out across multiple client sites or servers, start with a dedicated bastion on a HostMyCode VPS, then expand to dedicated servers for high-traffic workloads where isolation matters.

Tutorial: Set Up a VPS Jump Box (Bastion Host) for Safer SSH Access in 2026 | HostMyCode