Server Management

SSH Bastion / Jump Host

Set up an SSH bastion (jump host) to securely access private servers through a single public entry point

An SSH bastion (or jump host) is a single server exposed to the internet that acts as a gateway to access servers on a private network. Instead of exposing all servers publicly, you SSH into the bastion first, then jump to internal servers.

Internet → Bastion (public IP) → Private Server 1
                               → Private Server 2
                               → Private Server 3

Requirements

  • One server with a public IP (the bastion)
  • One or more servers reachable from the bastion (private network)
  • SSH key authentication (passwords strongly discouraged)

Configure the bastion server

On the bastion, harden SSH and allow only key auth:

sudo nano /etc/ssh/sshd_config
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AllowTcpForwarding yes
GatewayPorts no
X11Forwarding no
MaxAuthTries 3

Restart SSH:

sudo systemctl restart ssh

Connect through the bastion (one-liner)

ssh -J user@bastion-ip user@private-server-ip

The -J flag (ProxyJump) connects to bastion-ip first, then jumps to private-server-ip through it.

Example:

ssh -J admin@203.0.113.1 ubuntu@10.0.0.5

Add this to your local ~/.ssh/config to avoid typing the jump every time:

# Bastion
Host bastion
    HostName 203.0.113.1
    User admin
    IdentityFile ~/.ssh/bastion_key
    ServerAliveInterval 60

# Private servers (accessed via bastion)
Host web1
    HostName 10.0.0.5
    User ubuntu
    IdentityFile ~/.ssh/web_key
    ProxyJump bastion

Host db1
    HostName 10.0.0.10
    User ubuntu
    IdentityFile ~/.ssh/db_key
    ProxyJump bastion

# Wildcard: all 10.0.0.x via bastion
Host 10.0.0.*
    User ubuntu
    ProxyJump bastion

Now you can connect simply with:

ssh web1
ssh db1
ssh 10.0.0.20

Copy files through the bastion

SCP through a jump host:

scp -J admin@bastion-ip /local/file ubuntu@10.0.0.5:/remote/path

With ~/.ssh/config configured:

scp /local/file web1:/remote/path

Rsync through a bastion:

rsync -avz -e "ssh -J admin@bastion-ip" /local/dir ubuntu@10.0.0.5:/remote/dir

Forward a port through the bastion

Access a service on a private server (e.g., a web UI) on your local machine:

# Tunnel port 8080 on private server → localhost:8080
ssh -J admin@bastion-ip -L 8080:10.0.0.5:8080 ubuntu@10.0.0.5

Then open http://localhost:8080 in your browser.

With ~/.ssh/config:

ssh -L 8080:localhost:8080 web1

Agent forwarding (use your local key on private servers)

Instead of copying your private key to the bastion, use SSH agent forwarding so the bastion proxies auth challenges back to your local machine:

On your local machine:

# Add key to agent
ssh-add ~/.ssh/web_key

# Connect with agent forwarding
ssh -A admin@bastion-ip
# From bastion, you can now ssh to private servers using your local key
ssh ubuntu@10.0.0.5

In ~/.ssh/config:

Host bastion
    HostName 203.0.113.1
    User admin
    ForwardAgent yes

Agent forwarding gives any root user on the bastion the ability to use your keys. Only enable it on trusted bastions. Consider using ProxyJump instead, it doesn't forward your agent to the bastion.


Bastion hardening checklist

# Disable password auth
sudo sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config

# Allow only specific users to SSH
echo "AllowUsers admin" | sudo tee -a /etc/ssh/sshd_config

# Rate limit SSH connections
sudo ufw limit ssh

# Install fail2ban
sudo apt install fail2ban -y

# Restrict bastion to only jump (no shell)
# For users that should only jump and not use the bastion directly:
# Set their shell to /usr/sbin/nologin and use ForceCommand

Restrict a user to jump-only (no shell)

# Create a jump-only user
sudo useradd -m -s /usr/sbin/nologin jumpuser
sudo mkdir -p /home/jumpuser/.ssh
sudo cp /root/.ssh/authorized_keys /home/jumpuser/.ssh/
sudo chown -R jumpuser:jumpuser /home/jumpuser/.ssh

In /etc/ssh/sshd_config:

Match User jumpuser
    AllowTcpForwarding yes
    X11Forwarding no
    PermitTTY no
    ForceCommand /bin/false

This user can only be used as a ProxyJump target, no interactive shell.


Audit bastion access

Log all connections through the bastion:

# View all SSH logins
sudo journalctl -u ssh | grep "Accepted"

# Real-time monitoring
sudo tail -f /var/log/auth.log | grep sshd

On this page