
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/configthat usesProxyJumpfor 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.
-
Create a VPS and assign a clear hostname, for example
bastion1.yourdomain.com. Update DNS later (A record). -
Create an admin user. Avoid password-based SSH for normal operations.
adduser ops usermod -aG sudo ops -
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 web1from your workstation and look for which hop fails. - Make sure
AllowTcpForwarding yesis 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.