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
| Threat | Why it matters |
|---|---|
| Key exposure | A breached bastion can use the forwarded key to reach downstream hosts. |
| MITM | An attacker could trick your local SSH client into forwarding the key to an arbitrary host. |
| Untrusted hosts | Some 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
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"Add it to your agent:
eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_ed25519_bastionMake 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 yesturns it on globally.- The
Matchblock turns it off for the user that will use the key, preventing accidental forwarding to other hosts. PermitOpenlimits 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
| Symptom | Likely Cause | Fix |
|---|---|---|
| “Permission denied (publickey)” on B2 | Agent forwarding disabled on B1 | Verify AllowAgentForwarding yes and the Match block on B1 |
| “Connection closed by remote host” after a few seconds | Agent timeout expired | Increase timeout or keep the agent alive (ssh-agent -t 2h) |
| Key appears on an unexpected host | Misconfigured PermitOpen or missing Match block | Re‑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
- Quickly Pinpointing Unauthorised sudo Attempts with Journalctl Filters
- Why my SSH jump host was silently dropping connections after an OpenSSH 9.5 update and how I fixed it
- When systemd‑resolved silently drops a local /etc/hosts entry and the service you can’t reach
- When resolvectl Caches Fail: Quick Steps to Clear Stale DNS Entries Without Restarting NetworkManager
- When ss Shows No Open TCP Ports Even Though Services Are Running