Add an SSH key permanently to ssh-agent on Linux, macOS and Windows - why ssh-add is forgotten after a reboot and the four fixes that stick (tested on OpenSSH 9.6 and 9.5)

TL;DR

  • ssh-add is not permanent because the agent is not permanent. The key lives in the memory of one ssh-agent process; a new login, a reboot, or a terminal without SSH_AUTH_SOCK set does not see it.
  • Could not open a connection to your authentication agent (Linux, macOS) and Error connecting to agent: No such file or directory (Windows) both mean no agent is reachable, not that the key is wrong.
  • Linux, simplest: AddKeysToAgent yes and IdentityFile in ~/.ssh/config. The key is added to the agent the first time you use it, passphrase typed once per login session.
  • Linux, every terminal and every login: eval $(keychain --eval --quiet --agents ssh ~/.ssh/id_ed25519) in ~/.bashrc, or a systemd user service so one agent survives logout.
  • macOS: UseKeychain yes in the config and ssh-add --apple-use-keychain ~/.ssh/id_ed25519; the passphrase goes into the login keychain.
  • Windows: the ssh-agent service is Disabled by default; enable it once from an elevated PowerShell, then ssh-add persists across reboots.
  • Linux output below is from OpenSSH 9.6p1 on Ubuntu 24.04 (2026-10-16); Windows output is from OpenSSH for Windows 9.5p1 on my own laptop (2026-10-17).
  • Not your exact case? The closest post on this site is git@github.com.

The 2023 version of this post listed the commands per operating system. The question people actually arrive with is why the key they added yesterday is gone today, so this rewrite starts from that, reproduces the failures, and shows which fix survives what.

Table of Content

  1. Why ssh-add is forgotten - what survives what
  2. Generate a key, and the first error
  3. Linux fix 1 - AddKeysToAgent in the SSH config file
  4. Linux fix 2 - keychain, one agent for every shell
  5. Linux fix 3 - a systemd user service
  6. macOS - UseKeychain and apple-use-keychain
  7. Windows - enable the ssh-agent service
  8. Verify the key and the GitHub connection
  9. What broke when I ran this
  10. Conclusion

1. Why ssh-add is forgotten - what survives what

ssh-agent is a process that holds decrypted private keys in memory and answers signing requests over a Unix socket. ssh finds that socket through the SSH_AUTH_SOCK environment variable. ssh-add talks to the same socket. Everything about "permanent" follows from that:

MethodNew terminal in the same loginNew login or rebootPassphrase typedNeeds
eval $(ssh-agent) then ssh-addonly shells that inherit SSH_AUTH_SOCKlostevery new agentnothing
AddKeysToAgent yes in ~/.ssh/configyes, if the desktop session started an agentkey re-added on first useonce per loginan agent from the session
keychain in ~/.bashrcyes, all shellskey re-added at first shellonce per bootthe keychain package
systemd user serviceyesagent survives logout, not rebootonce per bootsystemctl --user
macOS keychain (UseKeychain yes)yeskey re-added, passphrase from keychainnever againmacOS
Windows ssh-agent serviceyeskeys survive rebootonce, everadmin rights once

"Permanent" should still have a ceiling on a laptop that leaves the building. The agent, the key and the config file each accept a lifetime:

WhereSettingEffect
per keyssh-add -t 3600 ~/.ssh/id_ed25519this key is forgotten after 3600 seconds (1 hour)
per agentssh-agent -t 8hevery key added gets an 8 hours default lifetime
per host in the configAddKeysToAgent 30mkeys added on first use expire after 30 minutes

I use ssh-add -t 28800, 8 hours, a working day. The ssh-agent manual and the ssh-add manual document the lifetime flags, and the time format accepts s, m, h, d and w suffixes.

Where the key lives with each method, and what kills it


2. Generate a key, and the first error

On a clean Ubuntu 24.04 machine on 2026-10-16 (the script kept the key under /tmp/sshkeys instead of ~/.ssh, everything else is as you would type it):

 1$ ssh -V
 2OpenSSH_9.6p1 Ubuntu-3ubuntu13.19, OpenSSL 3.0.13 30 Jan 2024
 3
 4$ ssh-keygen -t ed25519 -C "rahul@laptop" -f /tmp/sshkeys/id_ed25519
 5Generating public/private ed25519 key pair.
 6Your identification has been saved in /tmp/sshkeys/id_ed25519
 7Your public key has been saved in /tmp/sshkeys/id_ed25519.pub
 8The key fingerprint is:
 9SHA256:teOkQ02qAWFWB+GHMJB3OpU+Js/Trf47CRnKxYlNNQw rahul@laptop
10
11$ ssh-add -l
12Could not open a connection to your authentication agent.
13
14$ ssh-add /tmp/sshkeys/id_ed25519
15Could not open a connection to your authentication agent.

Both ssh-add calls exit with code 2. There is no agent on this session, so there is nothing to add the key to. The 2023 fix, which still works, is to start one in the current shell:

 1$ eval "$(ssh-agent -s)"
 2Agent pid 2172
 3
 4$ ssh-add /tmp/sshkeys/id_ed25519
 5Identity added: /tmp/sshkeys/id_ed25519 (rahul@laptop)
 6
 7$ ssh-add -l
 8256 SHA256:teOkQ02qAWFWB+GHMJB3OpU+Js/Trf47CRnKxYlNNQw rahul@laptop (ED25519)
 9
10$ ssh runner@localhost 'echo logged in as $(whoami) via agent'
11logged in as runner via agent

And here is the same key when the agent is not reachable, with BatchMode=yes so ssh cannot prompt for the passphrase, which is what every script and CI job experiences:

1$ env -u SSH_AUTH_SOCK ssh -o BatchMode=yes -i /tmp/sshkeys/id_ed25519 runner@localhost echo hi
2runner@localhost: Permission denied (publickey,password).

Same key, same server, Permission denied, because the passphrase could not be supplied. That is the error that sends people to GitHub permission denied (publickey), and the agent is the cure. The eval approach dies with the shell: the agent it started is tied to that terminal, and a login shell on the next day starts with no SSH_AUTH_SOCK at all. That is the whole problem.


3. Linux fix 1 - AddKeysToAgent in the SSH config file

The fix that needs no extra package. Tell ssh which key to use and to add it to the agent on first use:

1# ~/.ssh/config
2Host github.com
3  HostName github.com
4  User git
5  IdentityFile ~/.ssh/id_ed25519
6  AddKeysToAgent yes

I tested it against localhost with an empty agent:

 1$ ssh-add -l
 2The agent has no identities.
 3
 4$ ssh localhost echo first login
 5first login              # passphrase prompted here, once
 6
 7$ ssh-add -l
 8256 SHA256:teOkQ02qAWFWB+GHMJB3OpU+Js/Trf47CRnKxYlNNQw rahul@laptop (ED25519)
 9
10$ ssh localhost echo second login, no passphrase prompt
11second login, no passphrase prompt

The first ssh added the key as a side effect; the second did not ask. AddKeysToAgent is documented in ssh_config(5) and takes yes, no, ask, confirm, or a lifetime such as 8h. The precondition is that an agent exists in the session, which on Ubuntu and Fedora desktops is true (GNOME starts one at login and exports SSH_AUTH_SOCK), and on a headless server over SSH usually is not. For servers, use fix 2 or 3.


4. Linux fix 2 - keychain, one agent for every shell

keychain is a small script that starts one ssh-agent per host, writes its socket and PID to ~/.keychain/<hostname>-sh, and makes every new shell reuse it. Install it (apt-get install keychain) and put one line in ~/.bashrc or ~/.zshrc:

1eval $(keychain --eval --quiet --agents ssh ~/.ssh/id_ed25519)

What a second shell sees:

 1$ bash -c 'eval $(keychain --eval --quiet --agents ssh /tmp/sshkeys/id_ed25519); ssh-add -l'
 2256 SHA256:teOkQ02qAWFWB+GHMJB3OpU+Js/Trf47CRnKxYlNNQw rahul@laptop (ED25519)
 3
 4$ bash -c 'eval $(keychain --eval --quiet --agents ssh); ssh-add -l; echo SOCK=$SSH_AUTH_SOCK'
 5256 SHA256:teOkQ02qAWFWB+GHMJB3OpU+Js/Trf47CRnKxYlNNQw rahul@laptop (ED25519)
 6SOCK=/tmp/ssh-mKSXhzVnHOF2/agent.2171
 7
 8$ cat ~/.keychain/runnervm8k3b6-sh
 9SSH_AUTH_SOCK=/tmp/ssh-mKSXhzVnHOF2/agent.2171; export SSH_AUTH_SOCK;
10SSH_AGENT_PID=2172; export SSH_AGENT_PID;

The second shell did not start a new agent; it read the file and attached to PID 2172. The passphrase is asked once after a reboot, in the first shell that runs keychain, and never again until the next reboot. This is what I use on servers where I log in over SSH, and it is the fix that also makes Ansible with SSH keys stop prompting in the middle of a playbook.


5. Linux fix 3 - a systemd user service

If you would rather not depend on a shell rc file (cron jobs, IDE terminals, tmux), run the agent as a user service:

 1# ~/.config/systemd/user/ssh-agent.service
 2[Unit]
 3Description=SSH key agent
 4
 5[Service]
 6Type=simple
 7Environment=SSH_AUTH_SOCK=%t/ssh-agent.socket
 8ExecStart=/usr/bin/ssh-agent -D -a $SSH_AUTH_SOCK
 9
10[Install]
11WantedBy=default.target
 1$ systemctl --user daemon-reload
 2$ systemctl --user enable --now ssh-agent
 3Created symlink /home/runner/.config/systemd/user/default.target.wants/ssh-agent.service → /home/runner/.config/systemd/user/ssh-agent.service.
 4
 5$ systemctl --user is-active ssh-agent
 6active
 7
 8$ export SSH_AUTH_SOCK=$XDG_RUNTIME_DIR/ssh-agent.socket
 9$ ssh-add -l
10The agent has no identities.
11$ ssh-add /tmp/sshkeys/id_ed25519
12Identity added: /tmp/sshkeys/id_ed25519 (rahul@laptop)

Put the export SSH_AUTH_SOCK=$XDG_RUNTIME_DIR/ssh-agent.socket line in ~/.bashrc (or ~/.pam_environment) so every process finds it. The socket path is fixed, so it is the same in every terminal. Run loginctl enable-linger $USER if you want the agent to keep running after you log out; %t and the unit format are in systemd.unit(5).


6. macOS - UseKeychain and apple-use-keychain

macOS ships an agent that starts at login, and its ssh-add can store the passphrase in the login keychain. I did not re-run this section in October 2026; it is GitHub's current documented procedure for adding a key to the ssh-agent on macOS, which I use on my own MacBook.

1# ~/.ssh/config
2Host github.com
3  AddKeysToAgent yes
4  UseKeychain yes
5  IdentityFile ~/.ssh/id_ed25519
1ssh-add --apple-use-keychain ~/.ssh/id_ed25519

After this, a reboot starts the agent, the first ssh loads the key, and the passphrase comes from the keychain instead of you. Two details from the same page: if the key has no passphrase, leave out the UseKeychain line, and the old ssh-add -K from my 2023 post is the deprecated spelling of --apple-use-keychain (it still works on Monterey and later but prints a warning).

Terminal lives in Applications, Utilities on macOS


7. Windows - enable the ssh-agent service

On Windows the agent is a service, and on a fresh install it is disabled. This is my own laptop on 2026-10-17:

 1PS> ssh -V
 2OpenSSH_for_Windows_9.5p1, LibreSSL 3.8.2
 3
 4PS> Get-Service ssh-agent | Format-List Name,Status,StartType
 5Name      : ssh-agent
 6Status    : Stopped
 7StartType : Disabled
 8
 9PS> ssh-add -l
10Error connecting to agent: No such file or directory

No such file or directory is the Windows wording of "no agent". The fix is three commands in an elevated PowerShell, from Microsoft's OpenSSH key management page:

1Get-Service ssh-agent | Set-Service -StartupType Automatic
2Start-Service ssh-agent
3ssh-add $env:USERPROFILE\.ssh\id_ed25519

Once the service is Automatic, keys added with ssh-add are stored encrypted under the service's account and come back after a reboot; this is the one platform where ssh-add really is permanent. I could not run the Set-Service step on this laptop because the account has no administrator rights, which is also the most common reason the fix "does not work" on a corporate machine: the first command fails with Access is denied and the status stays Disabled.

One trap for Git users: Git for Windows bundles its own OpenSSH. If git uses that one and ssh-add the Windows one, they talk to different agents. Point Git at the Windows binary with:

1git config --global core.sshCommand "C:/Windows/System32/OpenSSH/ssh.exe"

8. Verify the key and the GitHub connection

Whatever the platform, the two checks are the same:

1$ ssh-add -l -E sha256
2256 SHA256:teOkQ02qAWFWB+GHMJB3OpU+Js/Trf47CRnKxYlNNQw rahul@laptop (ED25519)
3
4$ ssh -T git@github.com
5git@github.com: Permission denied (publickey).

That second result is from my Windows laptop on 2026-10-17, where no key is registered with GitHub yet; a working setup answers Hi rahulwagh! You've successfully authenticated, but GitHub does not provide shell access. If ssh-add -l shows the key and GitHub still says Permission denied (publickey), the key is loaded but not registered; paste ~/.ssh/id_ed25519.pub into Settings, SSH keys. If ssh -T hangs or fails with Could not resolve hostname, it is network, not keys:

ssh could not resolve hostname github.com: a DNS or proxy problem, not a key problem

Port 22 blocked by a corporate firewall: Bad file number

The Bad file number one is port 22 blocked by a firewall; GitHub also listens on port 443 at ssh.github.com, which you can select with a Host github.com / HostName ssh.github.com / Port 443 block in the same config file. If you generate keys for servers with Terraform instead of by hand, generating SSH keys with Terraform shows the tls_private_key route, and they load into the agent the same way.


9. What broke when I ran this

I ran the Linux part as a script on a GitHub Actions runner (Ubuntu 24.04, OpenSSH 9.6p1) on 2026-10-16, and the Windows part on my laptop on 2026-10-17.

1. A child shell did not reproduce the problem. I started a login shell with bash -lc 'ssh-add -l' expecting "no agent", and it listed the key. A child process inherits SSH_AUTH_SOCK from its parent, so any shell opened from the shell that ran eval ssh-agent works. The failure only appears in a terminal that did not descend from that shell: a new SSH login, a new desktop session, or a cron job. The env -u SSH_AUTH_SOCK test in section 2 is the honest reproduction.

2. ssh-keygen without a terminal cannot ask for a passphrase. In the script I had to pass -N to set one. Interactive use prompts twice, as the 2023 post described, and that part is unchanged.

3. pgrep -c ssh-agent printed 1 after keychain, as it should. I expected to see two agents, the one from eval and keychain's. keychain found the running one through the environment and adopted it instead of starting another; it only starts a new agent when it cannot find one. That is exactly why it is better than putting eval $(ssh-agent) in .bashrc, which leaves one orphaned agent per terminal ever opened.

4. No admin rights on Windows means no permanent agent. Set-Service needs elevation and this laptop does not have it. The workaround that does not need admin is to start a per-session agent from the Git Bash ~/.bashrc with keychain-style logic, which is back to the Linux approach and survives nothing more than the session.


10. Conclusion

To summarise:

  1. The key is forgotten because the agent process is gone or unreachable, not because ssh-add failed.
  2. Could not open a connection to your authentication agent and Error connecting to agent both mean "no agent".
  3. Linux: AddKeysToAgent yes if your desktop starts an agent; keychain in .bashrc or a systemd user unit on servers.
  4. macOS: UseKeychain yes plus ssh-add --apple-use-keychain.
  5. Windows: enable the ssh-agent service once as administrator, then ssh-add is permanent.
  6. ssh-add -t 28800 caps the exposure at 8 hours on a laptop.

Did this save you a debugging session? Stay in the loop -