ssh
SSH
Generate a New SSH Key#
View SSH Key#
macOS / Linux#
Windows (PowerShell)#
Configure GitHub to Use SSH over the HTTPS Port#
Test SSH Connection#
Configure Proxy#
SSH Agent Key Management#
Start ssh-agent Service on Windows#
On Windows, you need to start the ssh-agent service before using it:
Add Key to SSH Agent#
macOS / Linux:
Windows (PowerShell):
List Added Keys#
Remove Key from SSH Agent#
Remove a specific key:
macOS / Linux:
Windows (PowerShell):
Remove all keys:
Delegating Keys to 1Password#
The private key never touches disk. Both authentication and commit signing go through 1Password's agent, and each use requires biometric approval.
Only public keys stay on disk — ssh uses them to locate the matching private key in the agent.
ssh Configuration#
The wildcard block belongs at the end of the file. Most ssh_config options take the
first matching value, so a specific Host must appear earlier to override the wildcard.
The wildcard block needs IdentityAgent and nothing else. Which keys the agent offers,
and in what order, is decided by agent.toml — that is the file the official
docs use to control ordering, so you don't run into the six-key authentication limit most
servers impose.
Three things that are easy to get wrong:
IdentityFilepoints at the public key (.pub), not the private key. ssh uses it to find the matching private key inside the agent; no private key file is needed locally.- Keep the constraint inside the specific
Host, not the wildcard block.IdentityFileis cumulative, so putting it underHost *means the host that needs a dedicated key gets handed the general one first — which then forces a secondHost * !hostnameblock just to exclude it. Declare it on that one host instead;IdentitiesOnly yesthen applies only there, and every other host keeps using the agent. - The wrong key doesn't necessarily fail, and that's the hard one to catch. If your
default key is also a valid account on that same server, the server accepts it happily —
the connection works, commands run, only the identity is wrong. The only way to spot it
is to check the fingerprint on the
Server accepts keyline ofssh -vagainstssh-add -l.
Key Allowlist#
~/.config/1Password/ssh/agent.toml:
Without this file the agent only offers SSH keys from your default vault (Personal / Private / Employee) and none at all from custom vaults. So the moment you sort keys into per-project vaults, this file stops being optional hardening and becomes required.
Order matters. The agent offers keys in the order they appear in this file, which is how you avoid hitting the six-attempt limit once you have several keys.
It matches on the item title, exactly. Rename the item in 1Password without updating
this file and the agent immediately reports The agent has no identities — SSH, git and
signing all break at once. The error disguises itself as:
That reads like a corrupted public key file. What actually happened is that the agent offered nothing, so ssh fell back to reading the public key as if it were private. Without knowing that connection, this sends you down the wrong path.
Commit Signing#
allowedSignersFile is the commonly missed one. Without it, local
git log --show-signature fails to find a trusted signer and only the remote platform can
verify anything. One signer per line:
The public key also has to be added to GitHub a second time, with key type Signing Key. Authentication and signing are separate purposes — with only the authentication key registered, local signing succeeds while the web UI still says Unverified.
Naming Keys#
The comment at the end of a public key plays no part in authentication; it exists purely for identification. Keep it consistent in three places, using the item title rather than an email address:
Do not blank the comment. With several entries in authorized_keys, telling which key
is yours and which one to ask about then requires comparing fingerprints line by line with
ssh-keygen -lf.
Browser autofill inserts the public key without a comment (the site's name field gets the
item title instead). If the target platform writes the key verbatim into authorized_keys,
what lands there is bare base64 with no comment — use ssh-copy-id for those, since it
carries the comment from the local .pub.
Agent Forwarding#
Root on the remote host can read the agent socket under /tmp and borrow your key to reach
any machine that trusts you. Whether to enable it depends on how well you know who owns
that machine, and whether you keep long-lived connections open.
Forwarding is per-session, so long-lived sessions (tmux, multiplexer panes) cannot use it. Such a session inherits the socket path from the connection that created it, and that path dies with the connection. Pinning it behind a symlink does not help — the socket itself is gone. Therefore:
- Do not set
commit.gpgSign = trueon remote hosts, orgit commitfails outright in any session without an agent - Automation should use a dedicated deploy key rather than a forwarded personal key
Keeping Identity off Servers#
If you never commit on a server, strip the git identity:
The side effect is a welcome one: with no user.email, git commit refuses outright with
*** Please tell me who you are. — turning "don't commit on servers" from a habit into
something the tooling enforces. Options that carry no personal data, such as insteadOf,
are worth keeping for clone/pull.
When a one-off commit really is necessary, pass the identity inline instead:
Verification#
verification.reason is worth reading: unsigned means no signature at all,
unknown_key means the platform does not recognise the key (not added, or added as an
Authentication key), and bad_email means the signing key's address does not match the
commit author.
Always run ssh-keygen -Y sign before deleting a local private key. ssh-add -l only
proves the agent knows about the key; a successful signature proves it actually holds a
usable private key.
Break-Glass Access#
With the only copy of the private key in 1Password and password login disabled, there is a single way in. A locked account, a service outage, or a lost device with no saved Emergency Kit leaves you shut out.
Prefer the cloud console's VNC or serial console as a fallback: it bypasses SSH entirely and adds no attack surface. Verifying once that it works is enough.
Windows as the Server#
Installing a Public Key for Passwordless Login#
Administrator accounts do not read ~/.ssh/authorized_keys. The Windows sshd_config
ends with this block:
So for any account in the Administrators group the key must go into
C:\ProgramData\ssh\administrators_authorized_keys. Writing it to the home directory does
nothing — and reports no error either.
One command from the mac side (a password is needed the first time):
All three parts matter:
[IO.File]::WriteAllText— used instead ofAdd-Contentto avoid a BOM; the trailing[char]10keeps the line ending as LF rather than CRLF. Either one going wrong makes sshd treat the key line as malformedicacls /inheritance:r— the step everyone forgets. Without tightened permissions sshd silently ignores the whole file; the symptom is "the key is installed but it still asks for a password", with nothing useful in the logRestart-Service sshd
Verify:
Switching the Default Shell to PowerShell 7#
An ssh session into Windows lands in cmd.exe by default. Switching to pwsh 7 takes three
registry values:
No sshd restart is needed — new sessions pick it up immediately.
Do it in two steps, not one. A wrong DefaultShell breaks ssh host 'command', which is
the only channel left for fixing it remotely; once it is gone the registry has to be edited at
the machine itself. Set the first two values, confirm command mode still works, then add
DefaultShellArguments:
If pwsh 7 is not installed yet:
On a machine that only has Windows PowerShell 5.1, use
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe instead. Note that 5.1 and 7 have
separate $PROFILE files (WindowsPowerShell\ vs PowerShell\), so anything configured in
the old one does not carry over.
Errors that surface after the switch#
While the default shell is still cmd.exe the profile never loads, so switching to PowerShell
is the moment any pre-existing problem in it shows up for the first time.
On Windows 11 24H2 and later the usual one is "untrusted mount point": an SSH session cannot read symbolic links or junctions, so every tool that relies on them for version switching — fnm, the WinGet shims, pnpm, vite-plus — trips over it here. See the windows page for the diagnosis and the fixes.