Use an SSH bastion host for private VPS access
Reach private servers through one hardened jump host using ProxyJump, agent-safe key handling, restricted firewalls, and auditable SSH configuration.

A bastion host gives administrators one public SSH entry point while application and database servers accept management traffic only from a private network. It reduces exposure and makes access policy easier to audit.
The bastion is not a place to run applications. Keep it small, patched, and boring.
Establish a recovery path first
Do this from an existing root session and keep that session open until the replacement login has been tested. Confirm that the provider console or rescue mode works before changing SSH. A bastion without out-of-band access can turn one typo into a lockout.
Create a named administrator account before disabling root login:
sudo adduser bastionadmin
sudo usermod -aG sudo bastionadmin
From your workstation, copy a key to the new account:
ssh-copy-id -i ~/.ssh/getvps_admin.pub bastionadmin@203.0.113.10
If ssh-copy-id is unavailable, create /home/bastionadmin/.ssh/authorized_keys from the provider console and fix ownership and permissions:
sudo install -d -m 700 -o bastionadmin -g bastionadmin /home/bastionadmin/.ssh
sudo install -m 600 -o bastionadmin -g bastionadmin /tmp/bastionadmin-authorized_keys /home/bastionadmin/.ssh/authorized_keys
Open a second terminal and verify both key login and sudo before hardening SSH:
ssh -o IdentitiesOnly=yes -i ~/.ssh/getvps_admin bastionadmin@203.0.113.10
sudo -v
sudo id
Do not continue until that works in a fresh session.
Network policy
Assume:
bastion public: 203.0.113.10
bastion private: 10.20.0.10
app private: 10.20.0.21
db private: 10.20.0.31
Allow public port 22 on the bastion only from known administration networks where practical. On the app and database hosts, allow SSH only from 10.20.0.10.
Harden the bastion
Use key-only authentication in /etc/ssh/sshd_config.d/99-bastion.conf:
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AllowUsers bastionadmin
X11Forwarding no
AllowTcpForwarding yes
GatewayPorts no
PermitTunnel no
Reload SSH only after checking configuration:
sudo sshd -t
sudo systemctl reload ssh
AllowTcpForwarding yes is required for the jump connection. GatewayPorts no prevents forwarded ports from listening broadly by default.
Keep the original root session open, start a new bastionadmin session, and verify it again after the reload. Only then close root. If the new session fails, use the still-open root or provider console to restore the previous file and reload SSH. Never combine account creation, firewall changes, and SSH hardening into one unverified step.
Configure your workstation
In ~/.ssh/config:
Host vps-bastion
HostName 203.0.113.10
User bastionadmin
IdentityFile ~/.ssh/getvps_admin
IdentitiesOnly yes
Host app-private
HostName 10.20.0.21
User appadmin
IdentityFile ~/.ssh/getvps_admin
IdentitiesOnly yes
ProxyJump vps-bastion
Now ssh app-private creates a TCP tunnel through the bastion. Your private key remains on your workstation; it does not need to be copied to the jump host.
Avoid routine agent forwarding. A compromised bastion can ask a forwarded agent to sign authentication requests while the session is open. ProxyJump works without it.
Separate people and automation
Give every administrator an individual account or key. Do not share one private key across a team. For deployment automation, create a separate restricted user and key with only the permissions the pipeline needs.
Record SSH logins centrally or ship journald records off-host. At minimum, monitor failed login volume and successful sessions from unexpected addresses.
Plan for bastion failure
Keep provider-console access documented and tested. For important environments, provision a second bastion in another failure domain and list both in SSH configuration.
A bastion reduces attack surface, but it also becomes a dependency. Patch it promptly, back up its configuration rather than its host, and make replacement a documented procedure.
Restrict the private hosts too
On each private server, first create and verify its replacement administrator account in the same way. Then, from an existing root session on that private host, stage the bastion-only rule:
sudo ufw allow from 10.20.0.10 to any port 22 proto tcp
Test a new ProxyJump session and sudo -v before removing the old broad SSH rule. If your private network uses IPv6, add and test the equivalent rule for the bastion's IPv6 address or management prefix. Keep a provider-console or existing root recovery path until every host has been verified.
Audit and rotate access
List keys in each administrator's authorized_keys, record their owner and purpose, and remove keys with no owner. Rotate a key by adding the replacement, testing it through the bastion, and removing the old key only after confirmation. Use sshd -T to inspect effective configuration.
The bastion should not hold application secrets, source checkouts, or long-lived agent sockets. If it is compromised, revoke administrator access, rebuild it from a known image, and review every private host for unexpected logins.
Continue reading