LFCS 114 🐧 SSH Agent and Config File
The previous chapter deployed the public key to the server’s authorized_keys. This chapter covers two client-side mechanisms that make key-based authentication practical at scale: the SSH agent, which caches decrypted private keys in memory so the passphrase is typed once per session, and the SSH config file, which stores per-host connection settings so the command line is not a series of flags. The LFCS exam tests both. The agent is the answer to the question “how do I avoid typing my passphrase every time?” The config file is the answer to “how do I avoid typing the hostname, port, user, and key path every time?”
Key point: The SSH agent holds decrypted keys in memory. ssh-add loads a key into the agent. The config file at ~/.ssh/config defines per-host settings and is read before the command line. A host alias defined in the config can be used as ssh <alias>, and the corresponding User, HostName, Port, and IdentityFile are applied automatically.
Why the agent and config file exist
The passphrase problem. A private key protected by a passphrase is useless to an attacker who steals the key file. But typing the passphrase for every ssh, scp, git fetch, and rsync invocation defeats the purpose. The SSH agent solves this: the key is decrypted once, loaded into the agent’s memory, and the agent handles the authentication. The private key on disk remains encrypted. The agent provides the decrypted key to the SSH client on demand .
The multi-key problem. A developer may have a personal key, a work key, and a key for a specific bastion host. The server rejects the wrong key and accepts the right one, but the client offers keys in order. The agent can hold multiple keys, and the config file can specify which key to use for which host. The IdentityFile directive in the config file tells the client which key to offer first .
The long command problem. Without the config file, a connection to a non-standard port on a jump host with a specific key requires a long command line: ssh -p 2222 -i ~/.ssh/work_key -J bastion.example.com user@internal.example.com. The same connection in the config file is a few lines, and the command becomes ssh internal. The config file is read before the command line, so its settings apply unless overridden by flags .
The agent forwarding problem. A bastion host is the only entry point to an internal network. A developer logs into the bastion and from there connects to internal servers. Without agent forwarding, the developer must copy the private key to the bastion—a security violation. With agent forwarding, the developer’s local agent is forwarded to the bastion, and the bastion can authenticate to internal servers using the developer’s keys without ever storing them. The ForwardAgent yes directive in the config enables this for a specific host .
The trade-off. The agent holds decrypted keys in memory. A process with sufficient privileges on the client machine can extract them. The config file is a plaintext file; it contains hostnames, usernames, and key paths, but not private keys. Neither mechanism is a security risk on a properly configured workstation. Both are essential for a usable SSH workflow.
a. The SSH agent and ssh-add
The ssh-agent is a process that holds decrypted keys. It is usually started automatically by the desktop session or the shell profile. The SSH_AUTH_SOCK environment variable points to the agent’s socket; the SSH client uses this variable to find the agent.
# Start the agent (if not already running)
eval "$(ssh-agent -s)"
# The output sets SSH_AUTH_SOCK and SSH_AGENT_PID.
# Add the default key (prompts for passphrase once)
ssh-add
# Add a specific key
ssh-add ~/.ssh/id_ed25519_work
# List loaded keys
ssh-add -l
# Remove a key
ssh-add -d ~/.ssh/id_ed25519_work
# Remove all keys
ssh-add -D
The ssh-add command without arguments loads the default keys (~/.ssh/id_rsa, ~/.ssh/id_ed25519, etc.). The -l flag lists the fingerprints of the loaded keys. The -d flag removes a specific key. The -D flag removes all keys .
The ssh-add -t <seconds> flag sets a lifetime for the loaded key. After the lifetime expires, the key is removed from the agent. This is a security measure for keys that should not remain decrypted indefinitely. The -t 3600 sets a one-hour lifetime .
On macOS, the keychain can store the passphrase, so the agent is repopulated automatically after a reboot. On Linux, the GNOME Keyring or KDE Wallet can serve the same function. The exam does not test the desktop integration, but it tests the ssh-add and ssh-add -l commands.
b. The SSH config file: ~/.ssh/config
The ~/.ssh/config file defines per-host settings. The file is a sequence of Host blocks. Each block starts with a Host line that names one or more patterns, followed by directives that apply when the pattern matches.
# ~/.ssh/config
Host work
HostName git.work.example.com
User alice
Port 2222
IdentityFile ~/.ssh/id_ed25519_work
Host bastion
HostName bastion.example.com
User admin
IdentityFile ~/.ssh/id_ed25519_work
ForwardAgent yes
Host internal
HostName 10.0.0.50
User app
ProxyJump bastion
The Host work block defines an alias. The command ssh work connects to git.work.example.com on port 2222 as user alice, using the key ~/.ssh/id_ed25519_work. The HostName directive is the actual address. The Host name is the alias that appears in the command .
The ProxyJump directive is the mechanism for connecting through a bastion. The ssh internal command connects to 10.0.0.50 by first establishing a connection to bastion, then tunneling through it. This is the modern replacement for the older ProxyCommand with nc .
The Host * block matches every host and is used for global defaults. It must appear at the end of the file, because the first matching value for each directive wins.
# Global defaults
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
AddKeysToAgent yes
IdentitiesOnly yes
ServerAliveInterval 60 sends a keepalive packet every 60 seconds. ServerAliveCountMax 3 disconnects after three unanswered keepalives. AddKeysToAgent yes automatically loads the key into the agent when it is used. IdentitiesOnly yes tells the client to offer only the keys specified in the config, not every key in the agent. This is the fix for the “too many authentication failures” error, which occurs when the server rejects the client after too many offered keys .
c. Command-line overrides and precedence
The config file is read before the command line. A flag passed to ssh overrides the config file. The precedence order is: command-line flags, then the user’s config file, then the system-wide config file (/etc/ssh/ssh_config). Within the config file, the first matching value for each directive wins.
# Override the port for one connection
ssh -p 2200 work
# Override the user
ssh -l bob work
# Override the key
ssh -i ~/.ssh/id_rsa_backup work
The ssh -G <host> command dumps the effective configuration for a host, including the values from the config file and the defaults. This is the diagnostic tool when the config file does not seem to be applying.
# Show effective config for host "work"
ssh -G work
The output includes every directive that applies, not just the ones in the config file. The grep filter narrows it to the directives that matter.
ssh -G work | grep -E 'hostname|user|port|identityfile'
Complete Example Session
# ============================================
# PART 1: START THE AGENT
# ============================================
eval "$(ssh-agent -s)"
# Agent pid 12345
# ============================================
# PART 2: LOAD A KEY
# ============================================
ssh-add ~/.ssh/id_ed25519_work
# Enter passphrase for /home/user/.ssh/id_ed25519_work: (type passphrase)
# Identity added: /home/user/.ssh/id_ed25519_work
# ============================================
# PART 3: LIST LOADED KEYS
# ============================================
ssh-add -l
# 256 SHA256:abcdef... user@example.com (ED25519)
# ============================================
# PART 4: CREATE THE CONFIG FILE
# ============================================
mkdir -p ~/.ssh
cat > ~/.ssh/config <<'EOF'
Host work
HostName git.work.example.com
User alice
Port 2222
IdentityFile ~/.ssh/id_ed25519_work
Host bastion
HostName bastion.example.com
User admin
IdentityFile ~/.ssh/id_ed25519_work
ForwardAgent yes
Host internal
HostName 10.0.0.50
User app
ProxyJump bastion
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
IdentitiesOnly yes
EOF
chmod 600 ~/.ssh/config
# ============================================
# PART 5: TEST THE ALIAS
# ============================================
ssh work
# Connects to git.work.example.com on port 2222 as alice.
# ============================================
# PART 6: TEST THE BASTION ALIAS
# ============================================
ssh bastion
# Connects to bastion.example.com as admin.
# ============================================
# PART 7: TEST THE PROXY JUMP
# ============================================
ssh internal
# Connects to 10.0.0.50 through bastion.
# ============================================
# PART 8: SHOW EFFECTIVE CONFIG
# ============================================
ssh -G internal | grep -E 'hostname|user|proxyjump'
# hostname 10.0.0.50
# user app
# proxyjump bastion
# ============================================
# PART 9: REMOVE A KEY
# ============================================
ssh-add -d ~/.ssh/id_ed25519_work
# Identity removed
The nine parts trace the agent and config workflow: agent start, key load, key list, config creation, alias test, bastion test, proxy jump test, effective config, and key removal.
Quick Reference
Agent Commands
| Command | Purpose |
|---|---|
eval "$(ssh-agent -s)" | Start the agent |
ssh-add | Load default keys |
ssh-add <key> | Load specific key |
ssh-add -l | List loaded keys |
ssh-add -d <key> | Remove specific key |
ssh-add -D | Remove all keys |
ssh-add -t 3600 <key> | Load with one-hour lifetime |
Config Directives
| Directive | Purpose |
|---|---|
Host <alias> | Define a host block |
HostName <address> | Actual address |
User <name> | Username |
Port <port> | Port number |
IdentityFile <path> | Private key path |
ProxyJump <host> | Jump through a bastion |
ForwardAgent yes | Forward the agent to the host |
IdentitiesOnly yes | Offer only the specified keys |
ServerAliveInterval <s> | Keepalive interval |
ServerAliveCountMax <n> | Keepalive retry count |
Precedence
| Priority | Source |
|---|---|
| 1 (highest) | Command-line flags |
| 2 | User config (~/.ssh/config) |
| 3 | System config (/etc/ssh/ssh_config) |
Best Practices
✅ Do This:
ssh-add ~/.ssh/id_ed25519_work
<!-- Load keys into the agent --> # ✅
Host work
HostName git.work.example.com
User alice
IdentityFile ~/.ssh/id_ed25519_work
<!-- Use aliases for frequently used hosts --> # ✅
Host *
IdentitiesOnly yes
<!-- Prevent too many authentication failures --> # ✅
❌ Don’t Do This:
# Type the passphrase for every command
<!-- Load the key into the agent instead --> # ❌
# Forward the agent to every host
<!-- Only forward to bastion hosts --> # ❌
# Leave the config file at 644
<!-- The config file must be 600 --> # ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Passphrase prompts every time | Key not loaded in agent | ssh-add ~/.ssh/id_ed25519 |
| Too many authentication failures | Too many keys offered | IdentitiesOnly yes in config |
| Config not applied | File permissions wrong | chmod 600 ~/.ssh/config |
| ProxyJump fails | Bastion unreachable | Verify bastion connection first |
| Agent lost after reboot | Session-bound | Start agent in shell profile or use keychain |
Real-World Examples
1. Start Agent
eval "$(ssh-agent -s)"
2. Load Key
ssh-add ~/.ssh/id_ed25519
3. List Keys
ssh-add -l
4. Remove Key
ssh-add -d ~/.ssh/id_ed25519
5. Create Config
Host work
HostName git.work.example.com
User alice
IdentityFile ~/.ssh/id_ed25519_work
6. Bastion with ProxyJump
Host internal
HostName 10.0.0.50
ProxyJump bastion
7. Forward Agent
Host bastion
ForwardAgent yes
8. Global Defaults
Host *
IdentitiesOnly yes
9. Override Port
ssh -p 2200 work
10. Show Effective Config
ssh -G work | grep -E 'hostname|user|port'
Visual
Agent Workflow
┌──────────────────────────────────────────────┐
│ Private key (encrypted on disk) │
│ ~/.ssh/id_ed25519 │
└──────────────────┬───────────────────────────┘
│
│ ssh-add
▼
┌──────────────────────────────────────────────┐
│ Agent memory │
│ Decrypted key │
│ Serves authentication without passphrase │
└──────────────────┬───────────────────────────┘
│
│ ssh work
▼
┌──────────────────────────────────────────────┐
│ SSH client uses agent's socket │
│ Signs authentication challenge │
└──────────────────────────────────────────────┘
Config File Structure
┌──────────────────────────────────────────────┐
│ ~/.ssh/config │
│ │
│ Host work │
│ HostName git.work.example.com │
│ User alice │
│ Port 2222 │
│ IdentityFile ~/.ssh/id_ed25519_work │
│ │
│ Host internal │
│ HostName 10.0.0.50 │
│ ProxyJump bastion │
│ │
│ Host * │
│ IdentitiesOnly yes │
└──────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Agent start | eval "$(ssh-agent -s)" |
| Load key | ssh-add <key> |
| List keys | ssh-add -l |
| Remove key | ssh-add -d <key> |
| Config file | ~/.ssh/config |
| Config permission | 600 |
| Bastion jump | ProxyJump <host> |
| Key selection | IdentityFile <path> |
| Too many failures fix | IdentitiesOnly yes |
Key takeaways:
- The agent caches decrypted keys. The passphrase is typed once per session. The private key on disk remains encrypted. The agent provides the decrypted key to the SSH client on demand .
ssh-addloads keys. The-lflag lists them, the-dflag removes a specific key, and the-Dflag removes all of them .- The config file defines per-host settings. A
Hostblock withHostName,User,Port, andIdentityFileturns a long command into a short alias . ProxyJumpis the bastion mechanism. It connects through an intermediate host without copying private keys to the bastion .IdentitiesOnly yesprevents authentication failures. Without it, the client offers every key in the agent, and the server may reject the connection after too many attempts .
Remember: The agent is the memory of the SSH client. The config file is the shortcut. The passphrase is typed once, the key is loaded, and the agent handles the rest. The host alias is defined once, and the config file applies the settings for every command. The two mechanisms complement each other: the agent handles the credential, and the config file handles the destination.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!