Jump Servers (Bastion Host)

A jump server, or bastion host, is a hardened gateway for accessing internal systems. Rather than connecting directly, you first log into this secure host and then “jump” to other machines. This setup enhances security, centralizes access control, and allows detailed session logging.

Jump Servers (Bastion Host)
Photo by yannick Coffisee more @ Prints -/- yC -/- yc.wed

SSH jump servers, also called bastion hosts, are key elements in secure network setups. They serve as controlled gateways, enabling access to private servers that are not directly reachable from the internet. Using the -J flag in SSH makes connecting through these jump servers seamless, letting you hop from your local machine to a target server in a single command.

Understanding the Concept

A bastion host sits in a DMZ or public subnet, serving as the only entry point to your private infrastructure. Instead of exposing internal servers directly, you authenticate through the bastion, which then forwards your connection.

Basic Syntax

$ ssh -J user@bastion-host user@target-host

Topology

[Client] → [Bastion (public IP)] → [Internal Host (private IP)]

Real-World Examples

1. Simple Jump Through a Single Bastion

# Connect to internal server via bastion
ssh -J admin@bastion.example.com dev@10.0.1.23

# Using different ports
ssh -J admin@bastion.example.com:2222 dev@10.0.1.23:22

2. Multiple Jump Hosts (Chain)

# Chaining multiple jump servers
ssh -J user1@jump1.example.com,user2@jump2.example.com user@final-server

Here, SSH passes through multiple bastions in sequence before reaching the final target.

3. Using Private Keys

# Specify keys for both connections
ssh -J -i ~/.ssh/bastion_key user@bastion -i ~/.ssh/internal_key user@target
  • -i ~/.ssh/bastion_key → key for the bastion host
  • -i ~/.ssh/internal_key → key for the internal target server

This allows you to authenticate with different keys at each stage of the jump, keeping credentials separate and secure.

SSH Config File Setup (Pro. Way)

Create ~/.ssh/config for persistent configurations:

# Bastion Host Configuration
Host bastion
    HostName bastion.example.com
    User admin
    Port 22
    IdentityFile ~/.ssh/bastion_rsa
    ForwardAgent yes

# Internal Server via Bastion
Host internal-server
    HostName 10.0.1.23
    User developer
    Port 22
    ProxyJump bastion
    IdentityFile ~/.ssh/internal_rsa

# Multiple Internal Servers with Pattern Matching
Host 10.0.1.*
    User developer
    ProxyJump bastion
    IdentityFile ~/.ssh/internal_rsa

# AWS EC2 instances through bastion
Host ec2-*.compute.amazonaws.com
    User ec2-user
    ProxyJump bastion
    IdentityFile ~/.ssh/aws-key.pem

Then connect simply with:

ssh internal-server
ssh 10.0.1.45

Advanced Techniques

Port Forwarding Through Jump Server

# Local port forwarding through bastion
ssh -J bastion -L 8080:internal-server:80 user@internal-server

# Access internal database through tunnel
ssh -J bastion -L 5432:db.internal:5432 user@bastion

Access remote web app at:

http://localhost:8080


Copying Files via Jump Server

# SCP through bastion (modern approach)
scp -J admin@bastion file.txt dev@10.0.1.23:/path/

# Rsync through bastion
rsync -e "ssh -J admin@bastion" -avz file.txt dev@10.0.1.23:/path/

Using Agent Forwarding

# Enable agent forwarding through bastion
ssh -J bastion -A user@target

# In config file
Host target
    ProxyJump bastion
    ForwardAgent yes

⚠️ Use cautiously—exposes your agent to the bastion.


Security Best Practices

1. Use SSH Keys, Not Passwords

# Generate dedicated keys
ssh-keygen -t ed25519 -f ~/.ssh/bastion_key -C "bastion-access"
ssh-keygen -t ed25519 -f ~/.ssh/internal_key -C "internal-access"

2. Restrict Jump Server Access

To harden a bastion host, you can limit what users can do when connecting. In the ~/.ssh/authorized_keys file on the jump server, you can restrict access like this:

In bastion's ~/.ssh/authorized_keys:

command="internal-sftp",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-ed25519 AAA... bastion-key
  • command="internal-sftp" → forces only SFTP; no shell access.
  • no-port-forwarding → disables SSH tunneling.
  • no-X11-forwarding → disables GUI forwarding.
  • no-agent-forwarding → prevents key forwarding.
  • no-pty → blocks interactive shells.

This ensures the jump server is used only as intended, minimizing the risk of abuse.

3. Implement MFA

Combine with tools like google-authenticator or use certificates.

4. Audit and Logging

Enable detailed logging on the bastion:

# In /etc/ssh/sshd_config
LogLevel VERBOSE
# Monitor logs
tail -f /var/log/auth.log | grep sshd




BONUS

Troubleshooting Common Issues

Verbose Mode for Debugging

Sometimes your jump connection fails or behaves unexpectedly. SSH's verbose mode shows exactly what happens during the connection, including key negotiation, authentication, and ProxyJump routing.

ssh -vvv -J user@bastion_ip user@10.0.0.10
  • -v → verbose output
  • -vv → more detailed
  • -vvv → maximum debug info (step-by-step connection flow)


Advanced SSH: Bastions + ProxyJump + Config Includes

Create reusable configurations with include directives:
Why Use Include
Instead of putting all your SSH hosts in a single file, you can split them into reusable config files. This keeps your SSH setup organized, scalable, and easier to maintain.

1) Directory Structure

~/.ssh/
├── config                # main SSH config
├── config.d/
│   ├── bastions.conf     # bastion host definitions
│   └── internal.conf     # internal hosts definitions
└── keys/
    ├── prod-bastion-key
    ├── dev-bastion-key
    └── internal-key

2) Main SSH Config (~/.ssh/config)

# Load all config files from the config.d folder
Include config.d/*.conf

Keeps the main config minimal; all host definitions are in separate files.

3) Bastion Hosts Config (~/.ssh/config.d/bastions.conf)

# Production bastion
Host prod-bastion
    HostName 54.123.45.67
    User ops
    IdentityFile ~/.ssh/keys/prod-bastion-key

# Development bastion
Host dev-bastion
    HostName 10.5.0.1
    User devops
    IdentityFile ~/.ssh/keys/dev-bastion-key

4) Internal Hosts Config (config.d/internal.conf)

# Internal production server, accessed via prod-bastion
Host prod-app-01
    HostName 10.10.0.10
    User admin
    IdentityFile ~/.ssh/keys/internal-key
    ProxyJump prod-bastion

# Internal development server, accessed via dev-bastion
Host dev-app-01
    HostName 10.5.0.20
    User dev
    IdentityFile ~/.ssh/keys/internal-key
    ProxyJump dev-bastion

5) How to Connect

Now connecting is simple:

# Connect to bastion directly
ssh prod-bastion
ssh dev-bastion

# Connect to internal hosts via bastions automatically
ssh prod-app-01
ssh dev-app-01

No need to type -J every time—SSH reads the ProxyJump from your config.

6) Why This Setup Works

  • Modular: Separate files for bastions and internal hosts
  • Scalable: Easily add new servers by creating a new .conf in config.d/
  • Clean: Main ~/.ssh/config stays short and readable
  • Automated jumps: ProxyJump handles routing automatically

Performance Optimization

You can improve SSH performance and stability, especially over slower networks or multi-hop connections, with a few key config options.

# Enable compression for slower connections
Host bastion
    Compression yes
    CompressionLevel 6

# Keep connections alive
Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3

Using the -J flag provides a simple, secure way to access private hosts through a bastion. For repeated connections, configure ~/.ssh/config with key-based authentication, optional agent forwarding, and ProxyJump settings. Always log and monitor bastion access. This approach scales with your infrastructure while maintaining strong security.

This wraps up our tutorial. Thanks for following along.

Keep Us Caffeinated  ⦿ ⦿
Icon Join our 33K+ readers Spotify Logo