SSH for Developers: Config, Keys, Tunneling, and Security
SSH for Developers: Config, Keys, Tunneling, and Security
SSH is one of those tools that most developers use daily but few configure well. A properly set up SSH config saves you from typing long commands, makes multi-server workflows painless, and improves your security posture. Here's everything you need to know.
SSH Config File Mastery
The SSH config file (~/.ssh/config) lets you define named hosts with specific connection settings. Instead of typing ssh -i ~/.ssh/work_key -p 2222 [email protected], you type ssh staging.
Basic Structure
# ~/.ssh/config
# Personal GitHub
Host github.com
User git
IdentityFile ~/.ssh/id_ed25519_personal
# Work GitHub (different account)
Host github-work
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_work
# Production server
Host prod
HostName 203.0.113.50
User deploy
Port 2222
IdentityFile ~/.ssh/id_ed25519_work
# Staging server (behind VPN)
Host staging
HostName 10.0.1.50
User deploy
ProxyJump bastion
# Bastion/jump host
Host bastion
HostName bastion.example.com
User admin
IdentityFile ~/.ssh/id_ed25519_work
Wildcard Patterns
Apply settings to multiple hosts at once:
# All work servers
Host *.example.com
User deploy
IdentityFile ~/.ssh/id_ed25519_work
ServerAliveInterval 60
ServerAliveCountMax 3
# Default settings for all connections
Host *
AddKeysToAgent yes
IdentitiesOnly yes
HashKnownHosts yes
ServerAliveInterval 60 sends a keepalive packet every 60 seconds, preventing connections from dropping during idle periods. IdentitiesOnly yes prevents SSH from trying every key in your agent, which can cause authentication failures when you have many keys.
Useful Options
| Option | What it does |
|---|---|
IdentitiesOnly yes |
Only try keys specified by IdentityFile |
ServerAliveInterval 60 |
Send keepalive every 60 seconds |
Compression yes |
Compress data (useful for slow connections) |
ControlMaster auto |
Reuse connections (see multiplexing below) |
ForwardAgent yes |
Forward SSH agent (use cautiously) |
Connection Multiplexing
Multiplexing reuses a single TCP connection for multiple SSH sessions to the same host. This eliminates reconnection overhead.
Host *
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600
mkdir -p ~/.ssh/sockets
After the first connection to a host, subsequent ssh, scp, and rsync commands connect instantly. ControlPersist 600 keeps the connection alive for 10 minutes after the last session closes.
Key Management
Generate ed25519 Keys
Always use ed25519. RSA keys still work but ed25519 is faster, shorter, and more secure.
# Generate a key with a descriptive comment
ssh-keygen -t ed25519 -C "hailey@work-laptop-2026"
# Generate a key with a custom filename
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_work -C "work"
Always use a passphrase. A key without a passphrase is equivalent to writing your password in a file on disk.
ssh-agent
The SSH agent holds your decrypted private keys in memory so you only enter your passphrase once per session.
# Start the agent (usually automatic on modern systems)
eval "$(ssh-agent -s)"
# Add a key
ssh-add ~/.ssh/id_ed25519
# Add with a time limit (key is forgotten after 4 hours)
ssh-add -t 4h ~/.ssh/id_ed25519
# List loaded keys
ssh-add -l
On macOS, add this to your SSH config to use the Keychain:
Host *
UseKeychain yes
AddKeysToAgent yes
On Linux with GNOME or KDE, the desktop environment typically runs an SSH agent automatically. systemd users can also enable ssh-agent.service.
1Password SSH Agent
1Password can act as your SSH agent, storing keys in its vault. This is excellent for teams because keys are backed up, synced across devices, and protected by 1Password's security model.
# ~/.ssh/config
Host *
IdentityAgent "~/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"
On Linux:
Host *
IdentityAgent ~/.1password/agent.sock
When you SSH somewhere, 1Password prompts for biometric authentication. The private key never leaves 1Password's secure storage. This is the most convenient and secure approach for individual developers.
Jump Hosts and Tunneling
Jump Hosts (ProxyJump)
A jump host (bastion) is a server that sits between you and your target. Use ProxyJump instead of the older ProxyCommand syntax.
Host internal-db
HostName 10.0.2.100
User admin
ProxyJump bastion.example.com
# Command line equivalent
ssh -J bastion.example.com [email protected]
# Multiple jumps
ssh -J bastion1,bastion2 admin@target
ProxyJump creates an encrypted tunnel through the bastion. The bastion never sees the traffic to the final destination in plaintext.
SSH Tunneling (SOCKS Proxy)
Create a SOCKS proxy to route all traffic through a remote server:
ssh -D 1080 -N remote-server
Configure your browser or application to use localhost:1080 as a SOCKS5 proxy. Useful for accessing internal web applications without a VPN.
Port Forwarding for Local Development
Port forwarding is one of SSH's most underused features for development. It lets you securely access remote services as if they were running locally.
Local Port Forwarding
Access a remote service through a local port:
# Forward local port 5432 to a remote PostgreSQL server
ssh -L 5432:localhost:5432 db-server
# Forward to a service on the remote network (not the SSH server itself)
ssh -L 5432:internal-db.example.com:5432 bastion
# Run in background
ssh -fNL 5432:localhost:5432 db-server
Now psql -h localhost -p 5432 connects to the remote database through an encrypted tunnel.
Remote Port Forwarding
Expose a local service to a remote server:
# Make local port 3000 available on the remote server's port 8080
ssh -R 8080:localhost:3000 remote-server
Useful for sharing a local development server with a colleague or testing webhooks.
Config File Forwarding
Host dev-db
HostName db-server.example.com
User admin
LocalForward 5432 localhost:5432
LocalForward 6379 localhost:6379
Now ssh dev-db automatically sets up port forwarding for both PostgreSQL and Redis.
Secure Practices
Do
- Use ed25519 keys with strong passphrases
- Use
IdentitiesOnly yesto prevent key leakage across hosts - Disable password authentication on servers (
PasswordAuthentication noinsshd_config) - Use
ProxyJumpinstead of SSH agent forwarding when possible - Set
HashKnownHosts yesto obscure which servers you connect to - Rotate keys when leaving a team or organization
- Use
ForwardAgentsparingly and only to trusted hosts
Don't
- Don't use RSA keys shorter than 4096 bits (or just use ed25519)
- Don't forward your SSH agent to untrusted servers -- a compromised server can use your agent to authenticate as you
- Don't disable host key checking (
StrictHostKeyChecking no) in production - Don't store private keys without passphrases unless they're in a hardware security module or 1Password
- Don't use the same key for everything -- separate keys for personal, work, and CI
Hardening sshd_config
If you manage servers, these settings matter:
# /etc/ssh/sshd_config
PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
MaxAuthTries 3
AllowUsers deploy admin
Quick Reference
# Test connection
ssh -T [email protected]
# Copy public key to server
ssh-copy-id user@server
# Copy file
scp file.txt server:/path/
# Sync directory
rsync -avz ./local/ server:/remote/
# Check SSH config syntax
ssh -G hostname
# Verbose connection debugging
ssh -vvv hostname
The -G flag is invaluable for debugging config issues. It prints the resolved configuration for a host without connecting, showing you exactly which settings will be applied.
Recommendations
- Set up
~/.ssh/configas early as possible. Every server you connect to more than once deserves a named host entry. - Use ed25519 keys exclusively. Have separate keys for separate concerns (personal, work, CI).
- Enable connection multiplexing (
ControlMaster auto) for faster reconnections. - Use
ProxyJumpfor bastion access instead of agent forwarding. - Consider 1Password SSH agent for key management -- it's the best balance of security and convenience.
- Use local port forwarding regularly. It's more secure than exposing database ports to the network.