SSH access to servers directly from the internet is a common practice that makes every host a target. For a team of 20 engineers managing 50 servers, that's 50 open ports — a huge attack surface. A bastion host (jump server) reduces this to a single public IP. We set up such a node with IP whitelisting, mandatory MFA, and full logging of all sessions. The result: transparent access for developers and security at enterprise standards.
Why bastion host and not VPN or direct SSH?
Direct SSH to every server multiplies the attack surface: each host is a separate target. For 50 servers, that's 50 IP addresses to protect. VPN (OpenVPN, WireGuard) provides a single entry but adds complexity in managing certificates and clients. Bastion is the sweet spot: one public IP, minimal moving parts, maximum transparency for developers. ProxyJump in ~/.ssh/config makes working with the bastion seamless.
How does ProxyJump work?
The modern way to work with a bastion is ProxyJump in ~/.ssh/config. No agent forwarding (vulnerable), no copying keys to the bastion:
# ~/.ssh/config
Host bastion.corp.example.com
User ec2-user
IdentityFile ~/.ssh/corp-key.pem
ServerAliveInterval 30
Host 10.0.*.*
ProxyJump bastion.corp.example.com
User ubuntu
IdentityFile ~/.ssh/corp-key.pem
Host prod-db-01
Hostname 10.0.2.50
ProxyJump bastion.corp.example.com
User ubuntuAfter this, ssh prod-db-01 connects transparently through the bastion. The user's key never leaves their machine — the SSH agent forwards only the specific signing, not the key itself.
What settings secure the bastion?
The bastion is a target. It must be locked down. Recommended sshd_config settings:
| Parameter | Value | Purpose |
|---|---|---|
Port |
22 (or non-standard) | Stealth — non-standard port reduces bot noise |
PermitRootLogin |
no | Disallow direct root access |
PasswordAuthentication |
no | Keys only |
PubkeyAuthentication |
yes | Mandatory |
AllowGroups |
ssh-users | Group restriction |
LogLevel |
VERBOSE | Detailed logging |
MaxAuthTries |
3 | Limit login attempts |
ClientAliveInterval |
300 | Check activity |
ClientAliveCountMax |
2 | Disconnect idle sessions |
Firewall: incoming SSH only from office or VPN IP ranges. In AWS, this is a Security Group allowing port 22 only from corporate IP addresses. Egress — only to internal production subnets.
# AWS Security Group via awscli
aws ec2 authorize-security-group-ingress \
--group-id sg-bastion-id \
--protocol tcp --port 22 \
--cidr 203.0.113.0/24 # corporate IP
# Deny all other inbound
aws ec2 revoke-security-group-ingress \
--group-id sg-bastion-id \
--protocol tcp --port 22 \
--cidr 0.0.0.0/0 How to add MFA on the bastion?
MFA via the PAM module google-authenticator:
apt install libpam-google-authenticator
# /etc/pam.d/sshd — add at the top
auth required pam_google_authenticator.so nullok
# /etc/ssh/sshd_config
ChallengeResponseAuthentication yes
AuthenticationMethods publickey,keyboard-interactiveThe user goes through two factors: public key + TOTP. nullok allows users without configured TOTP to log in temporarily — removed after full rollout.
Comparison of SSH access methods
| Method | Security | Complexity | User Convenience | Auditing |
|---|---|---|---|---|
| Direct SSH to each server | Low (many entry points) | Low | High | No |
| OpenVPN + SSH | High (tunnel encryption) | Medium | Medium (VPN client needed) | No |
| Agent forwarding | Medium (key on bastion) | Low | High | No |
| Bastion + ProxyJump | High (key never leaves client) | Low | High (transparent) | Yes (sshd logs) |
What's included in the bastion setup work
- Analysis of current access scheme and architecture design for your infrastructure (AWS/GCP/Yandex Cloud).
- Hardened sshd_config setup with CIS recommendations.
- MFA integration (Google Authenticator, Duo Security).
- Centralized logging (Loki, ELK) with retention policies.
- Ansible/Terraform templates for automated deployment.
- Documentation (runbook, access diagram).
- Team training (how to add new users, how to work with ProxyJump).
Common bastion setup mistakes
- Using agent forwarding instead of ProxyJump — the key ends up on the bastion. - Weak password or unclosed password-based access. - Lack of logging — impossible to investigate incidents. - Ignoring MFA for administrators.Timelines and implementation stages
| Stage | Duration | Result |
|---|---|---|
| Analysis and design | 1 day | Access diagram, IP range selection |
| Bastion deployment (AWS/GCP) | 1 day | Working bastion with IP whitelist and hardened sshd |
| MFA and ProxyJump setup | 1–2 days | Users connect with two-factor authentication |
| Centralized logging | 1 day | Session logs sent to central system |
| Automation (Ansible/Terraform) | 1–2 days | Repeatable deployment |
A basic bastion on AWS/GCP with IP whitelisting, hardened sshd, and ProxyJump configs for the team is set up in 1 day. With MFA, centralized logging, and ansible automation — 2–3 days.
Why contact us?
We've implemented bastion hosts for 20+ clients — from startups to large e-commerce projects. We use only proven patterns: ProxyJump instead of agent forwarding, Google Authenticator PAM for MFA, RSYSLOG for centralized log collection. We guarantee that users' keys never appear on the bastion and all sessions are recorded. Get a consultation on your project — we'll assess your current access scheme and propose an optimized solution.







