Set up SSH bastion host with jump server configuration for secure network access

Intermediate 25 min Apr 18, 2026 162 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Configure an SSH bastion host to secure access to private networks, implementing jump server functionality with key-based authentication and access controls for enhanced security.

Prerequisites

  • Root or sudo access to the bastion host server
  • SSH client installed on user machines
  • Basic understanding of SSH key management

What this solves

An SSH bastion host (or jump server) acts as a secure gateway between public networks and your private infrastructure. Instead of exposing multiple servers directly to the internet, you route all SSH connections through a single hardened bastion host, reducing attack surface and centralizing access control.

Step-by-step installation

Update system packages and install OpenSSH server

Start by updating your system and installing the SSH server if not already present.

sudo apt update && sudo apt upgrade -y
sudo apt install -y openssh-server fail2ban ufw
sudo dnf update -y
sudo dnf install -y openssh-server fail2ban firewalld

Create dedicated bastion user accounts

Create separate user accounts for each person who needs bastion access. Never use shared accounts for security auditing.

sudo useradd -m -s /bin/bash bastion-admin
sudo useradd -m -s /bin/bash bastion-dev
sudo mkdir -p /home/bastion-admin/.ssh /home/bastion-dev/.ssh
sudo chmod 700 /home/bastion-admin/.ssh /home/bastion-dev/.ssh
sudo chown bastion-admin:bastion-admin /home/bastion-admin/.ssh
sudo chown bastion-dev:bastion-dev /home/bastion-dev/.ssh

Configure SSH server hardening

Harden the SSH configuration by disabling password authentication, changing the default port, and restricting access methods.

# Change default SSH port
Port 2222

Protocol and security settings

Protocol 2 PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes ChallengeResponseAuthentication no UsePAM yes

Connection settings

ClientAliveInterval 300 ClientAliveCountMax 2 MaxAuthTries 3 MaxStartups 3:30:10

Allow specific users only

AllowUsers bastion-admin bastion-dev

Disable unnecessary features

X11Forwarding no AllowTcpForwarding yes GatewayPorts no PermitTunnel no

Logging

SyslogFacility AUTH LogLevel VERBOSE

Set up SSH key authentication

Add SSH public keys for each bastion user. Generate keys on client machines first, then add public keys to the bastion host.

# On client machine (generate if needed)
ssh-keygen -t ed25519 -C "user@example.com"

Copy public key content, then on bastion host:

sudo tee /home/bastion-admin/.ssh/authorized_keys << 'EOF' ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGQn8QVTzKbcXh5iMqBqB2tUxI5g7VzJ0yA5V2nE5P4h admin@example.com EOF sudo chmod 600 /home/bastion-admin/.ssh/authorized_keys sudo chown bastion-admin:bastion-admin /home/bastion-admin/.ssh/authorized_keys

Configure firewall rules

Set up firewall rules to allow SSH on the custom port while blocking unnecessary access.

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 2222/tcp comment 'SSH bastion'
sudo ufw enable
sudo ufw status
sudo systemctl enable --now firewalld
sudo firewall-cmd --permanent --remove-service=ssh
sudo firewall-cmd --permanent --add-port=2222/tcp
sudo firewall-cmd --reload
sudo firewall-cmd --list-all

Configure Fail2ban for intrusion prevention

Set up Fail2ban to automatically block IP addresses that attempt multiple failed SSH connections.

[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3
ignoreip = 127.0.0.1/8 203.0.113.0/24

[sshd]
enabled = true
port = 2222
logpath = /var/log/auth.log
banaction = iptables-multiport
sudo systemctl enable --now fail2ban
sudo systemctl status fail2ban

Restart SSH service with new configuration

Apply the SSH configuration changes by restarting the service.

Warning: Test the new SSH configuration before closing your current session. Open a new terminal and test connection on port 2222 first.
sudo sshd -t
sudo systemctl restart sshd
sudo systemctl enable sshd
sudo systemctl status sshd

Set up jump server configuration for users

Configure SSH client settings on user machines to use the bastion host for accessing private servers.

# Bastion host configuration
Host bastion
    HostName 203.0.113.10
    Port 2222
    User bastion-admin
    IdentityFile ~/.ssh/id_ed25519
    ServerAliveInterval 60
    ServerAliveCountMax 3

Private server through bastion

Host private-server HostName 10.0.1.100 User ubuntu ProxyJump bastion IdentityFile ~/.ssh/id_ed25519

Wildcard for private network

Host 10.0.1.* User ubuntu ProxyJump bastion IdentityFile ~/.ssh/id_ed25519

Configure SSH agent forwarding

Enable SSH agent forwarding to use local SSH keys when jumping to private servers, avoiding the need to store keys on the bastion host.

# Add to existing configuration
AllowAgentForwarding yes
# Update bastion host config
Host bastion
    HostName 203.0.113.10
    Port 2222
    User bastion-admin
    IdentityFile ~/.ssh/id_ed25519
    ForwardAgent yes
    ServerAliveInterval 60

Set up logging and monitoring

Configure comprehensive logging for security auditing and monitoring of bastion host access.

# Bastion host logging
auth,authpriv.*    /var/log/bastion-auth.log
:programname, isequal, "sshd"    /var/log/bastion-ssh.log
& stop
sudo systemctl restart rsyslog
sudo touch /var/log/bastion-auth.log /var/log/bastion-ssh.log
sudo chmod 640 /var/log/bastion-*.log

Create user access control script

Implement a script to manage user access and provide session logging for compliance.

#!/bin/bash

Bastion host session logger

USER=$(whoami) CLIENT_IP=${SSH_CLIENT%% *} TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S') SESSION_ID="${USER}-${RANDOM}" echo "[$TIMESTAMP] User $USER connected from $CLIENT_IP (Session: $SESSION_ID)" >> /var/log/bastion-sessions.log

Display access banner

cat << EOF =============================================== AUTHORIZED ACCESS ONLY Bastion Host - example.com Session ID: $SESSION_ID All activities are logged and monitored =============================================== EOF

Start shell with logging

script -a -f -q /var/log/bastion-sessions/$SESSION_ID.log echo "[$TIMESTAMP] User $USER disconnected (Session: $SESSION_ID)" >> /var/log/bastion-sessions.log
sudo chmod +x /usr/local/bin/bastion-session
sudo mkdir -p /var/log/bastion-sessions
sudo touch /var/log/bastion-sessions.log

Configure user shell to use session script

Set the bastion session script as the default shell for bastion users to ensure all sessions are logged.

sudo chsh -s /usr/local/bin/bastion-session bastion-admin
sudo chsh -s /usr/local/bin/bastion-session bastion-dev

Verify your setup

Test your bastion host configuration from a client machine.

# Test direct bastion connection
ssh -p 2222 bastion-admin@203.0.113.10

Test jump connection to private server

ssh private-server

Test with verbose output for debugging

ssh -v private-server

Check active connections on bastion

sudo ss -tuln | grep :2222 sudo who

Review logs

sudo tail -f /var/log/bastion-auth.log sudo tail -f /var/log/bastion-sessions.log

Common issues

SymptomCauseFix
Connection refused on port 2222Firewall blocking custom SSH portCheck firewall rules: sudo ufw status or sudo firewall-cmd --list-all
Permission denied (publickey)SSH key not properly configuredVerify key permissions: chmod 600 ~/.ssh/authorized_keys and ownership
ProxyJump connection failsSSH agent not running or forwarding disabledStart SSH agent: eval $(ssh-agent) and add key: ssh-add ~/.ssh/id_ed25519
Too many authentication failuresMultiple SSH keys being triedSpecify exact key in SSH config: IdentitiesOnly yes
Fail2ban not blocking attacksWrong log path or port configurationCheck Fail2ban status: sudo fail2ban-client status sshd

Next steps

Running this in production?

Want this handled for you? Setting this up once is straightforward. Keeping it patched, monitored, backed up and performant across environments is the harder part. See how we run infrastructure like this for European teams.

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle infrastructure security hardening for businesses that depend on uptime. From initial setup to ongoing operations.