ssh-agent and Agent Forwarding Explained

ssh-agent and Agent Forwarding Explained

ssh-agent holds your decrypted private keys in memory and signs authentication challenges on request. You unlock a key once; every subsequent connection uses the agent.

The key itself never leaves the agent. Clients send it data to sign, and it returns a signature.

Starting it

Most desktop sessions start an agent automatically. Check:

echo $SSH_AUTH_SOCK
# /run/user/1000/keyring/ssh

If that is empty:

eval "$(ssh-agent)"

The eval matters. ssh-agent prints shell commands that export SSH_AUTH_SOCK and SSH_AGENT_PID; running it without eval prints them and sets nothing, which is why the agent appears not to work.

ssh-add ~/.ssh/id_ed25519     # add a key, prompts for passphrase
ssh-add -l                    # list loaded keys
ssh-add -D                    # remove all keys
ssh-add -t 3600 ~/.ssh/id_ed25519   # forget after an hour

Better than adding keys manually, let SSH do it on first use:

# ~/.ssh/config
Host *
    AddKeysToAgent yes
    IdentitiesOnly yes

IdentitiesOnly yes is worth setting for a separate reason: without it, SSH offers every key in the agent to every server in turn. With several keys and a server whose MaxAuthTries is low, you get rejected before reaching the right one. Our SSH config builder covers per-host key selection.

Agent forwarding, and why to avoid it

ssh -A user@jumphost
# now git clone from jumphost works using your local keys

-A forwards the agent socket to the remote host. Convenient, and it has a real problem.

The socket on the remote host is usable by root, and by anyone who can read it. While your session is connected, an attacker on that machine can sign with your keys. They authenticate as you to every server those keys open: production, your Git host, other machines entirely.

They cannot steal the key file, which is the reassurance usually offered and is not much of one. Being able to use a key for the duration of your session is enough.

ls -l $SSH_AUTH_SOCK    # on the remote: this is the exposure

If you must forward, constrain it per host rather than globally, and add confirmation:

ssh-add -c ~/.ssh/id_ed25519

-c makes the agent prompt on your local machine for every signing request. An unexpected prompt while you are idle is an unambiguous signal that something is wrong.

Never set ForwardAgent yes for Host *.

ProxyJump, which is what you actually want

The common reason for forwarding is reaching a machine through a bastion. ProxyJump does that without exposing anything:

ssh -J bastion.example.com user@internal-host
# ~/.ssh/config
Host bastion
    HostName bastion.example.com
    User admin

Host internal-*
    ProxyJump bastion
    User deploy
ssh internal-db01    # transparently routed through bastion

The connection tunnels through the bastion, and authentication to the final host happens directly between your machine and it. The bastion forwards encrypted bytes and never sees your agent, your keys, or your session contents.

This covers the majority of cases where people reach for -A.

For Git on a remote host, the better answer is usually a deploy key on that host rather than forwarding your personal credentials to it.

systemd user service

If your desktop does not start an agent, or you want one with consistent settings:

# ~/.config/systemd/user/ssh-agent.service
[Unit]
Description=SSH key agent

[Service]
Type=simple
Environment=SSH_AUTH_SOCK=%t/ssh-agent.socket
ExecStart=/usr/bin/ssh-agent -D -a $SSH_AUTH_SOCK

[Install]
WantedBy=default.target
systemctl --user enable --now ssh-agent
echo 'export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/ssh-agent.socket"' >> ~/.bashrc

Our systemd service file guide covers the syntax.

Hardware keys

The strongest improvement available. A FIDO2 key stores the private key in hardware that cannot export it:

ssh-keygen -t ed25519-sk -C "yubikey"

The -sk suffix means security key. Authentication requires touching the device, so a compromised machine cannot authenticate without physical presence.

This also fixes agent forwarding: a forwarded agent backed by a hardware key still requires a touch on your desk for every use, so an attacker on the intermediate host gets nothing.

Our SSH keys guide covers key types generally, and SSH hardening covers the server side.

Frequently Asked Questions

What does ssh-agent actually do?

It holds your decrypted private keys in memory and performs signing operations on behalf of SSH clients. This means you type your key passphrase once per session instead of on every connection, and the decrypted key never touches disk or gets passed to the client.

Why does ssh-add say it cannot open a connection to my authentication agent?

The SSH_AUTH_SOCK environment variable is not set in your current shell, so ssh-add cannot find the agent. Either no agent is running, or one is running but your shell did not inherit the variable. Running eval $(ssh-agent) starts one and exports the variable into that shell.

Is SSH agent forwarding safe?

Not on a host you do not fully trust. Anyone with root on the intermediate machine can use the forwarded socket to authenticate as you to any server your keys open, for as long as your session is connected. They cannot steal the key itself, which is limited comfort.

What should I use instead of agent forwarding?

ProxyJump, written as -J or ProxyJump in your SSH config. It tunnels the connection through the intermediate host so authentication happens directly between your machine and the final destination, and the jump host never sees your agent socket at all.

How do I make ssh-agent remember my key across reboots?

You cannot, and that is the point: the agent holds decrypted keys in memory only. What you can do is have your desktop session start an agent automatically, which most do, or use AddKeysToAgent yes in your SSH config so keys are added on first use rather than manually.

Can I set a timeout on keys in ssh-agent?

Yes. ssh-add -t 3600 adds a key that is forgotten after an hour, and starting the agent with ssh-agent -t sets a default for all keys. Combined with ssh-add -c, which requires confirmation for each use, this significantly reduces the window in which a compromised session is useful.