Back to tutorials
Tutorial

SSH Jump Host Setup Tutorial (2026): Secure Admin Access to VPS & Dedicated Servers Without Exposing SSH

SSH jump host setup tutorial for 2026: lock down admin access to VPS and dedicated servers using ProxyJump, MFA, and firewall rules.

By Anurag Singh
Updated on Jul 30, 2026
Category: Tutorial
Share article
SSH Jump Host Setup Tutorial (2026): Secure Admin Access to VPS & Dedicated Servers Without Exposing SSH

Leaving port 22 open to the internet comes with a daily bill. You’ll see constant scans, brute-force attempts, bloated auth logs, and more room for mistakes. This SSH jump host setup tutorial shows how to put one hardened “bastion” in front of your VPS and dedicated servers. Your production hosts then accept SSH only from that single, controlled source.

You’ll deploy a jump host on Ubuntu 24.04 LTS and harden its SSH settings. Then you’ll connect through it with ProxyJump. Finally, you’ll lock down every downstream server so SSH is reachable only from the bastion. This fits freelancers, small hosting resellers, and teams managing more than a couple of machines.

What you’ll build (and why it helps)

  • One public jump host with strict SSH settings, key-only auth, and optional MFA.
  • Private admin path to all other servers using ssh -J / SSH config.
  • Firewall rules that block public SSH on your web/mail/database hosts.
  • Auditable access: one choke point for logging, rate limiting, and credential policy.

If you’re spinning up new infrastructure, this pattern pairs well with a HostMyCode VPS as the bastion. Then run production workloads on separate VPS or dedicated servers.

If you’d rather not own the security maintenance, managed VPS hosting is a practical option. It’s especially useful for teams that want consistent guardrails.

Prerequisites and quick planning checklist

Decide these up front. Most “almost secure” SSH setups fail because the plan stays fuzzy.

  • Bastion OS: Ubuntu 24.04 LTS recommended. (Debian 12 works similarly.)
  • Downstream servers: any Linux with OpenSSH (Ubuntu/Debian/AlmaLinux/Rocky).
  • Authentication: SSH keys (required). MFA via PAM + TOTP (optional but useful).
  • Network trust model: “Only bastion can SSH to servers.” No exceptions.
  • Admin users: individual user accounts, not shared root keys.

Tip: If you’re tightening firewall rules next, keep this open: UFW firewall setup tutorial for hosting VPS. The same rule applies here: don’t “secure” yourself into a lockout.

Step 1 — Provision the jump host and create an admin user

Create a small VPS for the bastion. SSH is lightweight, so 1 vCPU and 1 GB RAM is usually enough. If your provider offers a static IPv4, use it.

On Ubuntu 24.04, log in once using the provider console or initial credentials. Then create a non-root admin user:

adduser admin
usermod -aG sudo admin

Copy your SSH public key into the new account:

mkdir -p /home/admin/.ssh
chmod 700 /home/admin/.ssh
nano /home/admin/.ssh/authorized_keys
chmod 600 /home/admin/.ssh/authorized_keys
chown -R admin:admin /home/admin/.ssh

From here on, SSH in as admin with keys. Use sudo for privileged work.

Step 2 — Harden SSH on the bastion (minimum secure baseline)

OpenSSH defaults are reasonable. A bastion should be more opinionated.

Start by editing:

sudo nano /etc/ssh/sshd_config

Use these settings as a baseline (adjust usernames as needed):

# Disable password auth
PasswordAuthentication no
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no

# Don’t allow direct root SSH
PermitRootLogin no

# Reduce attack surface
X11Forwarding no
AllowTcpForwarding yes
PermitTunnel no

# Keep sessions predictable
ClientAliveInterval 300
ClientAliveCountMax 2

# Only allow specific users
AllowUsers admin

Validate the config and reload SSH:

sudo sshd -t
sudo systemctl reload ssh

Safety habit: Keep your current SSH session open while you test a fresh login. If the new session fails, you can still undo the change.

Step 3 — Put a firewall in front of the bastion

The bastion should accept inbound SSH and little else. UFW is a clean way to enforce that:

sudo apt update
sudo apt install -y ufw

sudo ufw default deny incoming
sudo ufw default allow outgoing

# Allow SSH (port 22 by default)
sudo ufw allow 22/tcp

sudo ufw enable
sudo ufw status verbose

If you have a stable office IP or VPN egress IP, restrict SSH to that source:

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

This one change often drops SSH background noise to almost nothing.

If your admins travel and can’t keep a fixed IP, keep SSH open. Then lean on keys, rate limiting, and MFA.

Step 4 — (Optional) Add MFA to bastion SSH using TOTP

MFA is easiest to justify on the bastion. You get a second factor without touching every production server.

If a key is copied or leaked, the second factor can still stop logins.

Install the Google Authenticator PAM module:

sudo apt install -y libpam-google-authenticator

As the admin user (not root), run:

google-authenticator

For most setups, these answers are sensible defaults:

  • Time-based tokens: Yes
  • Disallow multiple uses: Yes
  • Increase window: Yes (helps with clock drift)
  • Rate limit: Yes

Enable PAM for SSH. Edit:

sudo nano /etc/pam.d/sshd

Add (near the top):

auth required pam_google_authenticator.so

Then update SSHD to allow challenge prompts. Edit:

sudo nano /etc/ssh/sshd_config

Set:

KbdInteractiveAuthentication yes

Reload SSH:

sudo sshd -t
sudo systemctl reload ssh

Pitfall: MFA plus AllowTcpForwarding can complicate some automation patterns. It’s excellent for humans.

For CI or scripted access, use separate users/keys and consider Match User rules.

Step 5 — Generate a dedicated “bastion-to-servers” SSH key

Avoid reusing your laptop key everywhere. Keep two clear layers:

  • Your laptop key → logs into bastion
  • Bastion key → logs into downstream servers

On the bastion, generate a keypair:

sudo -u admin ssh-keygen -t ed25519 -a 64 -f /home/admin/.ssh/bastion_to_servers -C "bastion-to-servers"

Print the public key so you can paste it into each downstream server:

sudo -u admin cat /home/admin/.ssh/bastion_to_servers.pub

Step 6 — Create an admin user on each downstream server

Do this on every VPS or dedicated server you want behind the bastion.

adduser admin
usermod -aG sudo admin
mkdir -p /home/admin/.ssh
chmod 700 /home/admin/.ssh
nano /home/admin/.ssh/authorized_keys
chmod 600 /home/admin/.ssh/authorized_keys
chown -R admin:admin /home/admin/.ssh

Paste the bastion public key (bastion_to_servers.pub) into authorized_keys on each server.

If you’re moving an existing server into this model, do it in two passes. Add the key first and verify bastion access. Then remove public SSH access.

Step 7 — Restrict SSH on downstream servers to the bastion only

This is where the design pays off. After this step, public SSH access to production hosts stops being a thing.

Option A (recommended): firewall restriction. On each downstream server using UFW:

# Allow SSH only from bastion public IP (replace with your bastion IP)
sudo ufw allow from 198.51.100.25 to any port 22 proto tcp

# Remove broader SSH rules (examples; check your rules list)
sudo ufw status numbered
sudo ufw delete <rule-number>

Option B: SSHD restriction (useful as a second layer). In /etc/ssh/sshd_config on the downstream server:

AllowUsers admin
PasswordAuthentication no
PermitRootLogin no

Reload SSH on downstream:

sudo sshd -t
sudo systemctl reload ssh

Quick diagnostic: From your laptop, try ssh admin@downstream. It should fail. From the bastion, it should work.

Step 8 — Configure ProxyJump on your laptop (clean daily workflow)

Once ProxyJump is in place, you connect to servers as if they were direct hosts. Put the routing logic in your SSH config:

nano ~/.ssh/config

Example configuration:

Host bastion
  HostName 198.51.100.25
  User admin
  IdentityFile ~/.ssh/id_ed25519

Host web-1
  HostName 10.0.0.11
  User admin
  ProxyJump bastion
  IdentityFile ~/.ssh/id_ed25519

Host mail-1
  HostName 10.0.0.21
  User admin
  ProxyJump bastion
  IdentityFile ~/.ssh/id_ed25519

Note: For downstream servers, HostName can be a private IP (best, if you have private networking). It can also be a public IP (still fine if port 22 only allows the bastion).

Now you can connect like this:

ssh web-1

Or do it one-off without SSH config:

ssh -J admin@198.51.100.25 admin@10.0.0.11

Step 9 — Add guardrails: per-user keys, forced commands, and tighter agent use

Shared admin keys make audits and offboarding painful. Give each person their own key. That lets you revoke access cleanly without disrupting everyone else.

On downstream servers, you can constrain specific keys in authorized_keys. Example: block interactive shells and allow only a single forwarded port for an automation key:

no-pty,permitopen="127.0.0.1:2087" ssh-ed25519 AAAAC3NzaC1... team-automation

For human admins, keep things straightforward. Avoid SSH agent forwarding unless you have a clear reason.

In your local SSH config:

Host *
  ForwardAgent no

If you truly need agent forwarding, enable it on one host at a time rather than everywhere.

Step 10 — Centralize logs and detect abuse early

A bastion only helps if you watch it. Treat it like a checkpoint.

Review failures, watch for unusual login times, and investigate patterns before they turn into incidents.

On Ubuntu, basic checks:

# Recent SSH auth events
sudo journalctl -u ssh --since "24 hours ago" | tail -n 200

# Failed logins summary (quick-and-dirty)
sudo journalctl -u ssh --since "24 hours ago" | grep -i "failed" | tail

If you want lightweight daily visibility plus basic blocking, pair this approach with: log monitoring setup guide with journalctl, Logwatch, and Fail2Ban.

Step 11 — Make it hosting-friendly: cPanel/WHM and web workflows through the bastion

Hosting work still involves browser panels (WHM, cPanel, webmail, internal dashboards). You can keep those services off the public internet.

Reach them through SSH port forwarding instead.

Example: access WHM on a cPanel server without exposing port 2087 publicly:

ssh -J bastion admin@cpanel-1 -L 2087:127.0.0.1:2087

Then open https://localhost:2087 in your browser.

For a broader walkthrough, use: SSH port forwarding tutorial for cPanel, webmail, and databases. It’s the same technique applied to the ports you use every day.

Step 12 — Test your setup (a short, realistic acceptance checklist)

  • From laptop: ssh bastion works with key auth (and MFA if enabled).
  • From laptop: direct SSH to downstream server fails.
  • From laptop: ssh web-1 works via ProxyJump.
  • On downstream servers: sshd -t passes and service reloads cleanly.
  • Firewall rules on downstream show only bastion IP allowed to port 22.
  • Logs on bastion show successful login entries you can correlate to users.

Troubleshooting: common breakpoints and fast fixes

ProxyJump fails with “Permission denied (publickey)”

  • Confirm the downstream server has the bastion public key in /home/admin/.ssh/authorized_keys.
  • Confirm permissions: chmod 700 ~/.ssh, chmod 600 authorized_keys.
  • From the bastion, test directly: ssh -i ~/.ssh/bastion_to_servers admin@10.0.0.11.

You locked yourself out after firewall changes

  • Use the provider console (out-of-band) to re-open SSH temporarily.
  • On UFW, you can disable quickly: sudo ufw disable.
  • Re-add a safe rule for your current IP, verify login, then tighten again.

MFA prompts break automation

  • Create a separate user for automation and exempt it using a Match User block in /etc/ssh/sshd_config.
  • Or keep MFA only for human users on the bastion, and do not use the bastion for automated processes.

You need SSL panels but want fewer open ports

Use SSH tunneling for panels, and keep HTTP/HTTPS public only where required. For SSL automation and common pitfalls, see: SSL certificate deployment tutorial for Let’s Encrypt on a VPS.

Summary: a safer admin perimeter you can live with

A jump host isn’t flashy infrastructure. It is a boundary you can explain and enforce.

You end up with one public SSH endpoint with strict auth. Your production servers accept SSH only from that endpoint.

If you manage client sites, mail servers, or multiple WordPress installs, you’ll see quieter logs. You’ll also see fewer “where did that login come from?” surprises.

If you want a clean place to run the bastion while keeping the rest of your fleet isolated, start with a HostMyCode VPS for the jump host. Then scale workloads on separate instances.

If you prefer not to handle patching, baseline hardening, and routine checks, managed VPS hosting keeps the operational burden lower.

A jump host works best when your IPs and networking are stable. Run the bastion on a HostMyCode VPS, then place web and control-panel servers behind it with tight firewall rules. If you’d rather focus on sites and clients than upkeep, managed VPS hosting can handle routine hardening and updates for you.

FAQ

Do I still need Fail2Ban if I use a jump host?

On downstream servers, often no—because SSH isn’t reachable publicly. On the bastion, yes. It still helps throttle abuse patterns (even with key-only auth) and keeps logs quieter.

Should the bastion be a VPS or a dedicated server?

A VPS is enough for most setups. Choose a dedicated server only if you have strict compliance requirements, heavy auditing, or you’re centralizing access for many admins across large fleets.

Can I use this setup with cPanel/WHM servers?

Yes. Keep WHM/cPanel ports closed to the public and use SSH port forwarding through the bastion for admin sessions. Your customer-facing sites stay on 80/443 as usual.

What’s the simplest way to roll this out without downtime?

Add bastion keys first, validate ProxyJump access, then tighten firewalls. Do not change SSH ports or disable passwords until you’ve confirmed key access works end-to-end.

What if my admins don’t have static IPs?

Keep bastion SSH open to the internet but require keys and MFA, and consider rate limiting. The big win remains: downstream servers accept SSH only from the bastion.