Using SSH Agent Forwarding Safely to Avoid Leaking Keys When Jumping Through Multiple Bastions

When you have to touch a host that lives behind several bastions, forwarding the agent is the quickest way to keep your private key off every hop. The catch? A compromised bastion can steal that key if you’re not careful. Below is a hands‑on walk‑through that shows how to tighten forwarding, what to watch out for, and how to double‑check that the key never ends up where it shouldn’t.

1. Understand the Threat Model

ThreatWhy it matters
Key exposureA breached bastion can use the forwarded key to reach downstream hosts.
MITMAn attacker could trick your local SSH client into forwarding the key to an arbitrary host.
Untrusted hostsSome internal services (monitoring agents, etc.) might run an SSH server that could request the key.

The aim is to confine the key to the exact hosts you trust and to stop accidental leaks.

2. Prep Your Local Machine

  1. Generate a dedicated key for bastion hops (if you don’t have one already):

    ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_bastion -C "bastion-access"
    
  2. Add it to your agent:

    eval "$(ssh-agent -s)"
    ssh-add ~/.ssh/id_ed25519_bastion
    
  3. Make sure it’s loaded:

    ssh-add -l
    

3. Configure the First Bastion (B1)

On B1 you want to allow forwarding only for the next hop (B2). Edit /etc/ssh/sshd_config:

# /etc/ssh/sshd_config on B1
AllowAgentForwarding yes
Match User alice
    AllowAgentForwarding no
    PermitOpen 192.168.10.0/24:22
  • AllowAgentForwarding yes turns it on globally.
  • The Match block turns it off for the user that will use the key, preventing accidental forwarding to other hosts.
  • PermitOpen limits outbound connections to the subnet that holds B2.

Restart:

sudo systemctl restart sshd

4. Restrict Forwarding on the Second Bastion (B2)

Repeat the pattern on B2 so it only forwards to the final target (T):

# /etc/ssh/sshd_config on B2
AllowAgentForwarding yes
Match User alice
    AllowAgentForwarding no
    PermitOpen 10.0.0.0/24:22

Now, connecting from your laptop to B1 will hand the key over to B2, and B2 will hand it over to T, nothing else.

5. Use the -J Option for Clean Syntax

Instead of nesting ssh -A -t commands, take advantage of OpenSSH’s jump host syntax (since 7.3):

ssh -A -J alice@b1.example.com:22 alice@b2.example.com:22 alice@10.0.0.42

The -A flag enables forwarding for the entire chain, but the per‑host restrictions in sshd_config keep the key from leaking beyond the intended hop.

6. Verify Forwarding Is Limited

On B1, run:

ssh -vvv alice@b2.example.com

Look for:

debug3: Requesting authentication agent forwarding
debug3: Authentication agent forwarding request sent

On B2, connect to T and confirm the agent is present:

ssh -vvv alice@10.0.0.42

You should see the same forwarding request, but no attempt to forward elsewhere.

7. Audit the Flow with ssh-keygen -lf

If you want to double‑check that the key isn’t being forwarded to an unintended host, inspect the fingerprints on each hop:

ssh alice@b1.example.com 'ssh-keygen -lf ~/.ssh/authorized_keys'

The fingerprint should match the one you generated locally. A different fingerprint means the key was probably copied or forwarded elsewhere.

8. Use ProxyJump with Per‑Host Forwarding

If you can’t touch sshd_config on the bastions, you can still limit forwarding by disabling it locally and enabling it only for the last hop:

ssh -o ForwardAgent=no -J alice@b1.example.com:22 alice@b2.example.com:22 \
    -o ForwardAgent=yes alice@10.0.0.42

You lose the single -A convenience, but you gain tighter control.

9. Harden the SSH Agent Itself

The agent can be a target. Use -t to set a timeout:

ssh-agent -t 1h

After an hour of inactivity the agent dies, narrowing the window an attacker has.

If your distro supports systemd user services, let systemd manage the agent:

systemctl --user enable ssh-agent.service
systemctl --user start ssh-agent.service

Systemd will kill the agent when you log out.

10. Troubleshooting Common Issues

SymptomLikely CauseFix
“Permission denied (publickey)” on B2Agent forwarding disabled on B1Verify AllowAgentForwarding yes and the Match block on B1
“Connection closed by remote host” after a few secondsAgent timeout expiredIncrease timeout or keep the agent alive (ssh-agent -t 2h)
Key appears on an unexpected hostMisconfigured PermitOpen or missing Match blockRe‑check sshd_config on each bastion

11. When to Avoid Agent Forwarding

  • Highly Sensitive Environments – If the downstream host holds critical data, copy the key to a dedicated jump machine instead of forwarding.
  • Untrusted Bastions – If you can’t guarantee the bastion’s integrity, disable forwarding entirely.
  • Compliance Requirements – Some regulations forbid key forwarding; use key distribution instead.

12. Alternative: SSH Certificates

OpenSSH certificates let you delegate trust without forwarding the private key. Create a CA key, sign the user’s key, and configure each bastion to accept the certificate. No forwarding needed, and you get a cleaner audit trail.

# On the CA machine
ssh-keygen -t ed25519 -f ~/.ssh/ca_key -C "ssh-ca"
ssh-keygen -s ~/.ssh/ca_key -I alice -V +52w ~/.ssh/id_ed25519_bastion.pub

# On each bastion
AuthorizedPrincipalsFile /etc/ssh/authorized_principals/%u

For more on certificates, see the OpenSSH docs: https://docs.kernel.org/ssh/ssh_keygen.html


See also