Back to tutorials
Tutorial

SSH Jump Host Setup Guide Tutorial (2026): Bastion Access for VPS & Dedicated Servers Without Exposing Port 22

SSH jump host setup guide tutorial for 2026: build a bastion, restrict SSH, add 2FA, and stop brute-force scans.

By Anurag Singh
Updated on Oct 01, 2026
Category: Tutorial
Share article
SSH Jump Host Setup Guide Tutorial (2026): Bastion Access for VPS & Dedicated Servers Without Exposing Port 22

Leaving SSH exposed to the public internet has a permanent cost. You get constant scans, noisy auth logs, and the occasional “why is sshd eating CPU?” surprise. This SSH jump host setup guide tutorial shows how to put a bastion (jump host) in front of your VPS and dedicated servers. Port 22 stays private, access stays traceable, and you can tighten firewall rules without locking yourself out.

The model is simple. You SSH into one hardened entry point. Then you hop to web servers, database nodes, and control panel boxes over a private network.

For hosting teams, this also gives you one place to enforce keys, MFA, and auditing.

What you’ll build (and what you need)

  • Jump host (bastion): a small Linux VPS with a public IP, hardened SSH, and strict allow-lists.
  • Target servers: your web hosting VPS, cPanel/DirectAdmin server, mail server, or dedicated server with SSH only reachable from the jump host.
  • SSH client config: a clean ~/.ssh/config that uses ProxyJump for one-command access.

Assumptions for commands below:

  • Jump host OS: Ubuntu 24.04 LTS or Debian 12 (both common choices in 2026).
  • Target OS: Ubuntu/Debian/AlmaLinux/Rocky (steps note distro differences where it matters).
  • You can use a private network (preferred). If you can’t, you can still restrict targets to the jump host’s public IP.

If you need servers sized for this pattern, start with a small bastion VPS. Put workloads on separate machines.

HostMyCode’s HostMyCode VPS plans work well for jump hosts and admin nodes. Production workloads often need more CPU/RAM headroom.

Step 1: Provision a dedicated jump host VPS

Don’t run your bastion on the same server as your web stack. A separate host limits blast radius. It also makes access boundaries obvious.

  1. Create a VPS and assign a clear hostname, for example bastion1.yourdomain.com. Update DNS later (A record).

  2. Create an admin user. Avoid password-based SSH for normal operations.

    adduser ops
    usermod -aG sudo ops
  3. Install baseline tooling:

    sudo apt update
    sudo apt install -y openssh-server ufw fail2ban

Tip: If your “new server hardening” checklist is dusty, refresh it before you go further.

HostMyCode has a practical walkthrough here: server hardening tutorial (2026).

SSH jump host setup guide tutorial: Harden sshd on the bastion

Keep bastion SSH intentionally strict. Aim for boring access. Use keys only, block root login, keep an allow-list, and disable extras you don’t need.

Edit /etc/ssh/sshd_config (Ubuntu/Debian). On AlmaLinux/Rocky, the path is the same.

sudo nano /etc/ssh/sshd_config

Apply these baseline settings (adjust usernames to match your environment):

# Auth
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey

# Limit who can log in
AllowUsers ops

# Reduce exposure
X11Forwarding no
AllowTcpForwarding yes
PermitTunnel no
ClientAliveInterval 300
ClientAliveCountMax 2
MaxAuthTries 3

# Logging
LogLevel VERBOSE

Reload SSH safely:

sudo sshd -t && sudo systemctl reload ssh

Leave your current SSH session open. Test a second login in another window. This habit prevents most self-inflicted lockouts.

Step 3: Add a second factor (practical options)

MFA fits best on a bastion because it’s the choke point.

On Linux, a common choice is PAM-based TOTP. Another strong option is hardware-backed SSH keys (FIDO2). Pick one approach and keep it maintainable.

Option A: TOTP (PAM Google Authenticator compatible)

On Ubuntu/Debian:

sudo apt install -y libpam-google-authenticator

For user ops:

sudo -u ops google-authenticator

Edit /etc/pam.d/sshd and add:

auth required pam_google_authenticator.so nullok

Then in /etc/ssh/sshd_config set:

KbdInteractiveAuthentication yes
AuthenticationMethods publickey,keyboard-interactive

Reload SSH:

sudo sshd -t && sudo systemctl reload ssh

Operational note: keep a break-glass path. Use provider console access, or a second admin key stored offline. Bastion MFA works right up until the only enrolled device disappears.

Option B: Hardware keys (FIDO2 / security keys)

If your team already uses security keys, generate resident or non-resident FIDO2 keys. Place the public key in ~/.ssh/authorized_keys.

This avoids PAM dependencies. It also stays simple for small teams.

Step 4: Firewall the bastion (UFW example)

The bastion should do one job: accept SSH from a small set of source IPs. Start by denying all inbound traffic by default.

sudo ufw default deny incoming
sudo ufw default allow outgoing

Allow SSH only from your office IP(s) or VPN egress IP(s):

sudo ufw allow from 203.0.113.10 to any port 22 proto tcp
sudo ufw allow from 198.51.100.0/24 to any port 22 proto tcp

Enable UFW:

sudo ufw enable
sudo ufw status verbose

If you also have a provider-level firewall, apply the same rule set there.

Dropping junk traffic before it hits the OS keeps logs cleaner. It also reduces load.

Related: for a careful UFW + safe SSH rule order, use this guide: VPS firewall setup guide tutorial (2026).

Step 5: Restrict target servers to the bastion only

This step is the whole point. Your target servers should not accept SSH from the internet.

Limit SSH access to:

  • the bastion’s private IP (best), or
  • the bastion’s public IP (fine if private networking isn’t available).

Ubuntu/Debian target (UFW)

On the target server, allow SSH only from the bastion. Then remove any broad “open SSH” rules.

# Allow bastion to SSH in
sudo ufw allow from 10.0.0.10 to any port 22 proto tcp

# (Optional) if you must use public IP
# sudo ufw allow from 203.0.113.200 to any port 22 proto tcp

# Ensure SSH isn't open to the world elsewhere
sudo ufw delete allow 22/tcp 2>/dev/null || true

sudo ufw enable
sudo ufw status verbose

AlmaLinux/Rocky target (firewalld)

sudo firewall-cmd --permanent --remove-service=ssh
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.10/32" service name="ssh" accept'
sudo firewall-cmd --reload
sudo firewall-cmd --list-all

Don’t treat “changing the SSH port” as a replacement for this. A non-standard port cuts down on drive-by scans. It does not create an access boundary.

Step 6: Configure SSH ProxyJump on your laptop (clean and repeatable)

On your workstation, create or edit ~/.ssh/config:

mkdir -p ~/.ssh
chmod 700 ~/.ssh
nano ~/.ssh/config

Example configuration:

Host bastion
  HostName bastion1.yourdomain.com
  User ops
  IdentityFile ~/.ssh/id_ed25519
  IdentitiesOnly yes
  ServerAliveInterval 60

Host web1
  HostName 10.0.1.21
  User ops
  ProxyJump bastion
  IdentityFile ~/.ssh/id_ed25519
  IdentitiesOnly yes

Host cpanel1
  HostName 10.0.1.30
  User root
  ProxyJump bastion
  IdentityFile ~/.ssh/id_ed25519

Now connect like this:

ssh web1
ssh cpanel1

SSH will hop through the bastion automatically. You don’t need manual ssh -J flags. You also avoid juggling terminals.

Step 7: Forward agent or not? Choose deliberately

Agent forwarding (ForwardAgent) feels convenient. It also widens the blast radius if the bastion is ever compromised.

In most setups, it’s safer to place a dedicated “internal access” key on the bastion. Scope that key tightly.

If you must use agent forwarding, enable it only where you need it:

Host bastion
  ForwardAgent no

Host web1
  ForwardAgent yes

For most hosting teams, a clean model looks like this:

  • Workstation key authenticates to bastion.
  • Bastion holds a separate key used only to reach targets.
  • Targets accept SSH only from bastion’s IP.

Step 8: Add session logging (basic audit trail)

On a bastion, logging is part of the value. At a minimum, aim for:

  • SSH auth logs (already)
  • command history that survives disconnects (useful, not perfect)
  • optional: full session recording for regulated environments

A quick improvement is what you already set: LogLevel VERBOSE.

If you can, ship logs off the bastion. Even simple rsyslog to another internal VM is better than losing evidence to a disk failure.

On Ubuntu/Debian, SSH logs typically land in /var/log/auth.log. On Alma/Rocky they’re usually in /var/log/secure.

If you want daily summaries without standing up a SIEM, set up Logwatch: Logwatch setup tutorial (2026).

Step 9: Add Fail2Ban on the bastion (quiet the noise)

Fail2Ban doesn’t replace allow-listing. It still helps when something “trusted” goes bad.

Common examples include a compromised office network or a sloppy VPN exit configuration.

Create a local jail:

sudo nano /etc/fail2ban/jail.d/sshd.local
[sshd]
enabled = true
maxretry = 4
findtime = 10m
bantime = 1h
backend = systemd

Restart and check status:

sudo systemctl restart fail2ban
sudo fail2ban-client status sshd

Step 10: Operational checklist for hosting teams (what breaks in real life)

  • Console access: confirm you have emergency console access for bastion and targets before you tighten firewalls.
  • Key lifecycle: rotate admin keys on a schedule and on staff changes. Use this process: SSH key rotation tutorial (2026).
  • Separate roles: don’t use personal keys for automation. Create dedicated keys with restricted permissions.
  • Don’t forget control panels: if you run WHM/cPanel or DirectAdmin, keep SSH private and administer panels via HTTPS with strong auth from known IPs.
  • Document internal IPs: write down which private subnet the bastion can reach, and which servers are “SSH via bastion only”.

Troubleshooting: common jump host failures and fast fixes

You can SSH to bastion, but not to the target

  • Confirm the target firewall allows SSH from bastion’s IP (private or public).
  • From bastion, test directly: ssh ops@10.0.1.21.
  • Check routing: does bastion actually have a route to the target’s private subnet?

ProxyJump fails with “Connection closed”

  • Run: ssh -vvv web1 from your workstation and look for which hop fails.
  • Make sure AllowTcpForwarding yes is set on the bastion.
  • If you use a custom SSH port on bastion, specify it in the bastion host block with Port 2222.

MFA locked you out

  • Use your hosting provider console to login and temporarily relax AuthenticationMethods.
  • Keep a second admin account with a separately stored key (break-glass), then monitor its use.

Summary: a safer admin perimeter for VPS and dedicated servers

A jump host gives you one hardened door instead of ten. Once it’s in place, daily work gets simpler.

You get one SSH entry point, consistent access rules, and target servers that stay out of reach of internet scanners.

If you want an easy way to run this pattern, place your bastion and targets on HostMyCode VPS plans. Or choose managed VPS hosting if you’d rather offload baseline hardening and upkeep.

Need a clean SSH perimeter for client hosting or internal ops? HostMyCode makes it straightforward to spin up a dedicated bastion on a HostMyCode VPS, then lock down production nodes behind it. If you’d rather reduce ongoing admin work, managed VPS hosting helps you keep firewall rules, updates, and access controls consistent as your server fleet grows.

FAQ

Do I still need to change my SSH port if I use a jump host?

Not usually. Restricting target SSH to the bastion’s IP provides real protection. A port change is optional noise reduction, not a security boundary.

Should the bastion be in the same location as my servers?

Yes when possible. Keeping the bastion close to your target servers reduces latency and simplifies private networking. If you host globally, you can run one bastion per region.

Can I use the bastion for SFTP too?

You can, but it mixes roles. For hosting operations, keep file transfer endpoints separate unless you have a clear policy and strict user isolation.

What’s the minimum size for a jump host?

For a small team, 1 vCPU and 1 GB RAM is often enough. If you add session recording or heavy log shipping, give it more disk and memory.

How do I avoid a single point of failure?

Run two bastions and allow both IPs on target firewalls. Keep their configs identical and test failover monthly.

SSH Jump Host Setup Guide Tutorial (2026): Bastion Access for VPS & Dedicated Servers Without Exposing Port 22 | HostMyCode