Skip to main content

Configuring SSH for Secure Production Server Access

Master SSH key pair generation, ~/.ssh/config setup, bastion host access, port forwarding, and production sshd hardening for secure Linux server management.

30 Terms

Overview and What You Will Learn

SSH is the primary tool for accessing every Linux server in production. Every Razorpay engineer, every Swiggy DevOps team member, every Zerodha infrastructure operator uses SSH daily. But most engineers only know the basics — ssh user@host — and miss the features that make SSH faster, safer, and far more powerful.

By the end of this lab you will:

  • Generate and deploy Ed25519 SSH key pairs correctly
  • Configure ~/.ssh/config for one-command server access
  • Set up ProxyJump for accessing private servers through a bastion host
  • Use SSH port forwarding to access internal services securely
  • Harden sshd_config to production security standards
  • Troubleshoot SSH connection failures systematically

Why This Matters in Production

A production environment at a company like Hotstar has hundreds of servers — none reachable directly from the internet. Engineers connect through a bastion host (jump server) that is the single entry point. Without proper SSH config, every connection requires a complex command with multiple flags. With proper SSH config, it is ssh payment-prod-db-1 and you are connected.

More critically: misconfigured SSH is one of the most common attack vectors against production servers. Enabling password authentication, allowing root login, and using weak key algorithms are mistakes that put entire infrastructure at risk.

Core Principles

SSH directory structure and required permissions:

◈ DIAGRAM
+------------------------------------------+
| ~/.ssh/ chmod 700 |
| |
| config chmod 600 |
| id_ed25519 chmod 600 |
| id_ed25519.pub chmod 644 |
| authorized_keys chmod 600 |
| known_hosts chmod 600 |
+------------------------------------------+
SSH enforces these permissions. Wrong permissions
mean SSH silently refuses to use the key or config.

SSH authentication flow:

◈ DIAGRAM
+------------------+ +------------------+
| Client | | Server (sshd) |
| | | |
| 1. Connect | -------> | Listen :22 |
| | | |
| 2. Key exchange | <------> | Exchange keys |
| Encrypted | | Session key set |
| tunnel open | | |
| | | |
| 3. Send username | -------> | Check auth methods|
| | | |
| 4. Key auth: | -------> | Check |
| Sign challenge| | authorized_keys |
| with priv key | | Verify signature |
| | | |
| 5. Shell access | <------> | Spawn bash |
+------------------+ +------------------+

Detailed Step-by-Step Practical Lab

Milestone 1 — Generate an Ed25519 key pair

Ed25519 is the preferred key algorithm in 2026. It is faster, more secure, and produces shorter keys than RSA-4096 while providing equivalent or better security.

Bash
## Generate Ed25519 key pair
ssh-keygen -t ed25519 -C "rahul@devops.in"
## Generating public/private ed25519 key pair.
## Enter file in which to save the key (~/.ssh/id_ed25519): [press Enter]
## Enter passphrase (empty for no passphrase): [type a strong passphrase]
## Enter same passphrase again:
## Your identification has been saved in /home/rahul/.ssh/id_ed25519
## Your public key has been saved in /home/rahul/.ssh/id_ed25519.pub
## Verify the files were created with correct permissions
ls -la ~/.ssh/
## -rw------- id_ed25519 (600 -- private key)
## -rw-r--r-- id_ed25519.pub (644 -- public key)
## View the public key (safe to share)
cat ~/.ssh/id_ed25519.pub
## ssh-ed25519 AAAAC3NzaC1lZDI1NTE5... rahul@devops.in
## Copy public key to a server
ssh-copy-id -i ~/.ssh/id_ed25519.pub rahul@10.0.1.50
## Number of key(s) added: 1
## Now try logging into the machine: ssh rahul@10.0.1.50
## Test key-based login
ssh rahul@10.0.1.50
## Should connect without password (passphrase if set)
Common Mistake

Generating a key without a passphrase for "convenience." If your laptop is stolen or compromised, an unprotected private key gives instant access to every server where your public key is installed. Always use a strong passphrase and ssh-agent to avoid typing it repeatedly.

Milestone 2 — Configure ~/.ssh/config for fast access

The SSH config file lets you define aliases, set defaults, and configure complex connection options so they become a single command.

Bash
## Create or edit the config file
mkdir -p ~/.ssh && chmod 700 ~/.ssh
nano ~/.ssh/config
chmod 600 ~/.ssh/config
TEXT
## ~/.ssh/config -- complete production example
## Global defaults applied to all connections
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
AddKeysToAgent yes
IdentityFile ~/.ssh/id_ed25519
## Bastion / jump server
Host bastion
HostName 52.66.100.200
User rahul
Port 22
IdentityFile ~/.ssh/id_ed25519
## Production servers (accessed via bastion)
Host payment-prod-*
User rahul
ProxyJump bastion
IdentityFile ~/.ssh/id_ed25519
Host payment-prod-api-1
HostName 10.0.1.50
Host payment-prod-db-1
HostName 10.0.2.100
## Staging environment
Host staging-*
User deploy
ProxyJump bastion
IdentityFile ~/.ssh/id_ed25519_staging
Host staging-api
HostName 10.0.10.50
Bash
## Now connect with just the alias
ssh payment-prod-api-1
## Automatically: user=rahul, jumps via bastion, uses correct key
## No more typing:
## ssh -i ~/.ssh/id_ed25519 -J rahul@52.66.100.200 rahul@10.0.1.50

Milestone 3 — Set up ProxyJump for bastion host access

Most production environments have no direct internet access to application servers. A bastion (jump) host is the single entry point.

Bash
## One-time: copy your key to the bastion
ssh-copy-id -i ~/.ssh/id_ed25519.pub rahul@52.66.100.200
## Then copy your key to the internal server via the bastion
ssh-copy-id -i ~/.ssh/id_ed25519.pub -o ProxyJump=rahul@52.66.100.200 rahul@10.0.1.50
## Test the jump connection
ssh -J rahul@52.66.100.200 rahul@10.0.1.50
## -J flag: jump through the specified host
## With config file set up, just use the alias
ssh payment-prod-api-1
## ProxyJump chains (multiple hops)
ssh -J bastion1,bastion2 rahul@internal-server
## Agent forwarding (use your local keys on the jump server)
## WARNING: only use on trusted hosts
ssh -A rahul@52.66.100.200

Milestone 4 — Use SSH port forwarding

SSH port forwarding creates encrypted tunnels to access internal services without exposing them directly.

Bash
## Local forwarding: access remote PostgreSQL on localhost:5433
## Connects to 10.0.2.100:5432 through the SSH server at 10.0.1.50
ssh -L 5433:10.0.2.100:5432 rahul@10.0.1.50
## In another terminal, connect to the database as if it were local
psql -h localhost -p 5433 -U postgres mydb
## Local forward: access internal Grafana dashboard
ssh -L 3000:localhost:3000 rahul@10.0.1.50
## Then open http://localhost:3000 in your browser
## Run in background (-f) and stay alive (-N no command)
ssh -fN -L 5433:10.0.2.100:5432 rahul@10.0.1.50
## Remote forwarding: expose local port on remote server
## Useful for webhooks during development
ssh -R 8080:localhost:3000 rahul@10.0.1.50
## Anyone hitting 10.0.1.50:8080 reaches your localhost:3000
## Dynamic SOCKS proxy: route all traffic through server
ssh -D 1080 rahul@10.0.1.50
## Configure browser to use SOCKS5 proxy at localhost:1080

Milestone 5 — Manage ssh-agent for passphrase convenience

Bash
## Start ssh-agent (usually started automatically by desktop environment)
eval $(ssh-agent -s)
## Agent pid 12345
## Add your private key (prompts for passphrase once)
ssh-add ~/.ssh/id_ed25519
## List loaded keys
ssh-add -l
## 256 SHA256:abc123... rahul@devops.in (ED25519)
## Add key that expires after 8 hours (re-auth after workday)
ssh-add -t 8h ~/.ssh/id_ed25519
## Remove all keys from agent
ssh-add -D
## On macOS: integrate with Keychain (passphrase remembered across reboots)
## In ~/.ssh/config:
## Host *
## UseKeychain yes
## AddKeysToAgent yes

Milestone 6 — Harden sshd_config for production

Bash
## Edit the SSH server configuration
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup
sudo nano /etc/ssh/sshd_config
TEXT
## /etc/ssh/sshd_config -- production hardening settings
## Change default port to reduce automated scan noise
## (Security through obscurity -- not a substitute for real security)
Port 22
## Disable root login completely
PermitRootLogin no
## Disable password authentication -- keys only
PasswordAuthentication no
ChallengeResponseAuthentication no
## Allow only specific users
AllowUsers rahul deploy jenkins
## Limit authentication attempts
MaxAuthTries 3
LoginGraceTime 30
## Disconnect idle sessions after 10 minutes
ClientAliveInterval 300
ClientAliveCountMax 2
## Use modern key exchange algorithms only
KexAlgorithms curve25519-sha256,diffie-hellman-group14-sha256
## Disable X11 forwarding (not needed on servers)
X11Forwarding no
## Disable TCP forwarding if not needed (restrict to specific hosts if needed)
## AllowTcpForwarding no
## Log level for audit
LogLevel VERBOSE
Bash
## Validate config before applying
sudo sshd -t
## No output = valid config
## Apply changes
sudo systemctl reload sshd
## Verify from ANOTHER terminal that you can still connect
## before closing current session
ssh rahul@10.0.1.50

Production Best Practices and Common Pitfalls

Scenario Wrong Correct
New server setup Leave PasswordAuthentication yes Disable password auth immediately
Team access Share one SSH key Each engineer has their own key
Debugging connection Guess at the problem ssh -vvv user@host for full debug
Accessing internal DB Open DB port to internet Use SSH port forwarding
Config file change Apply without testing Test with sshd -t, keep second session open

Quick Reference and Troubleshooting Commands

Task Command
Generate key ssh-keygen -t ed25519 -C "email"
Copy key to server ssh-copy-id -i ~/.ssh/id_ed25519.pub user@host
Connect via bastion ssh -J bastion user@internal-host
Local port forward ssh -L localport:remotehost:remoteport user@host
Debug connection ssh -vvv user@host
Test sshd config sudo sshd -t
Load key in agent ssh-add ~/.ssh/id_ed25519
List loaded keys ssh-add -l
Tip

The ServerAliveInterval 60 setting in ~/.ssh/config sends a keepalive packet every 60 seconds. Without it, SSH connections through NAT or firewalls with idle timeouts drop silently — and you discover the connection is dead only when you try to type something.

Common Mistake

Running ssh -A (agent forwarding) habitually on all connections. Agent forwarding exposes your ssh-agent socket on the remote server. Any user with root on that server can use your agent to impersonate you elsewhere. Always use ProxyJump instead — it achieves the same result without exposing your agent.

Common Mistake

Running ssh -A (agent forwarding) on every connection out of habit. Agent forwarding exposes your local ssh-agent socket on the remote server. Root on that server can silently use your keys to access other servers as you. Use ProxyJump in ~/.ssh/config instead — same result, no agent exposure.

Remember

Always keep a second SSH terminal open when testing new sshd_config changes. If you lock yourself out by misconfiguring authentication, the existing session keeps you connected so you can fix the error without needing console access.

Security

Never use ssh -A (agent forwarding) to untrusted servers. Agent forwarding allows the remote server to use your ssh-agent — meaning anyone with root on that server can use your keys to connect to other servers as you. Use ProxyJump instead, which does not expose your agent to intermediate hosts.

Resources

Explore More in Linux Networking and Security

All 4 Topics

Frequently Asked Questions

Is Configuring SSH for Secure Production Server Access free to learn on DevOps Network?

Yes - this topic, like everything on DevOps Network, is 100% free with no paywall or sign-up gate.

What does the Configuring SSH for Secure Production Server Access topic cover?

Master SSH key pair generation, ~/.ssh/config setup, bastion host access, port forwarding, and production sshd hardening for secure Linux server management.