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-addis not permanent because the agent is not permanent. The key lives in the memory of onessh-agentprocess; a new login, a reboot, or a terminal withoutSSH_AUTH_SOCKset does not see it.Could not open a connection to your authentication agent(Linux, macOS) andError connecting to agent: No such file or directory(Windows) both mean no agent is reachable, not that the key is wrong.- Linux, simplest:
AddKeysToAgent yesandIdentityFilein~/.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 yesin the config andssh-add --apple-use-keychain ~/.ssh/id_ed25519; the passphrase goes into the login keychain. - Windows: the
ssh-agentservice isDisabledby default; enable it once from an elevated PowerShell, thenssh-addpersists 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
- Why ssh-add is forgotten - what survives what
- Generate a key, and the first error
- Linux fix 1 - AddKeysToAgent in the SSH config file
- Linux fix 2 - keychain, one agent for every shell
- Linux fix 3 - a systemd user service
- macOS - UseKeychain and apple-use-keychain
- Windows - enable the ssh-agent service
- Verify the key and the GitHub connection
- What broke when I ran this
- 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:
| Method | New terminal in the same login | New login or reboot | Passphrase typed | Needs |
|---|---|---|---|---|
eval $(ssh-agent) then ssh-add | only shells that inherit SSH_AUTH_SOCK | lost | every new agent | nothing |
AddKeysToAgent yes in ~/.ssh/config | yes, if the desktop session started an agent | key re-added on first use | once per login | an agent from the session |
keychain in ~/.bashrc | yes, all shells | key re-added at first shell | once per boot | the keychain package |
| systemd user service | yes | agent survives logout, not reboot | once per boot | systemctl --user |
macOS keychain (UseKeychain yes) | yes | key re-added, passphrase from keychain | never again | macOS |
Windows ssh-agent service | yes | keys survive reboot | once, ever | admin 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:
| Where | Setting | Effect |
|---|---|---|
| per key | ssh-add -t 3600 ~/.ssh/id_ed25519 | this key is forgotten after 3600 seconds (1 hour) |
| per agent | ssh-agent -t 8h | every key added gets an 8 hours default lifetime |
| per host in the config | AddKeysToAgent 30m | keys 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.
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).

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:


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:
- The key is forgotten because the agent process is gone or unreachable, not because
ssh-addfailed. Could not open a connection to your authentication agentandError connecting to agentboth mean "no agent".- Linux:
AddKeysToAgent yesif your desktop starts an agent;keychainin.bashrcor a systemd user unit on servers. - macOS:
UseKeychain yesplusssh-add --apple-use-keychain. - Windows: enable the
ssh-agentservice once as administrator, thenssh-addis permanent. ssh-add -t 28800caps the exposure at 8 hours on a laptop.
Did this save you a debugging session? Stay in the loop -
- Subscribe on YouTube - every post here has a screen-recorded companion video.
- Code on GitHub - the repositories, Terraform and scripts behind these posts.