Skip to content

SSH for GitHub on Windows — What It Is, Why It's Worth It, and How to Set It Up

Posted on:July 21, 2026 at 09:00 AM
13 min read

Most people meet SSH the same way: a tutorial tells you to paste four commands, one of them asks for a passphrase you weren’t expecting, and eventually git push stops asking for a password. It works, you move on, and you never quite find out what you set up.

I went through this properly on a Windows machine recently and wrote down not just the steps but the reasoning behind each one. That’s what this post is. Three questions, in order: what SSH authentication actually is, why it’s the right default for GitHub, and how to set it up on Windows in both PowerShell and Git Bash.

If you only want the commands, the How section stands alone. But the Why is the part that makes the commands stick.

Table of contents

Open Table of contents

What: a key pair, and what each half does

SSH authentication replaces “prove you’re you with a password” with “prove you’re you with a key you already hold.”

When you run ssh-keygen, you produce two files that are mathematically linked:

The handshake works like this: GitHub holds your public key. When you connect, it sends a challenge that can only be answered by someone holding the matching private key. Your machine answers it locally. The private key itself is never transmitted. That’s the fundamental difference from a password, which has to travel to the server to be checked.

On Windows, these files live in:

C:\Users\<YourUsername>\.ssh

That location is tied to your Windows user profile, not to a drive. This trips people up: if your repos live on D:\Projects and your keys live on C:, nothing needs configuring. Git looks for keys in your home directory regardless of where the repository sits. The two are entirely independent.

You’ll also see ~ used throughout. It’s shorthand for “my home directory” in Git Bash, Linux, and macOS — and PowerShell understands it too, resolving to $env:USERPROFILE. So ~/.ssh and C:\Users\you\.ssh are the same folder written two ways.

Why: three reasons, only one of which is convenience

1. Nothing secret crosses the network

With HTTPS, you authenticate with a personal access token that gets sent to GitHub on every operation. With SSH, your private key stays on disk and only a signed response goes over the wire. There’s no credential in transit to intercept.

2. You stop typing credentials

This is the reason most people actually make the switch, and it’s a legitimate one. Once the key is registered, git clone, push, and pull just work. No token to paste, no token to rotate every ninety days, no credential helper quietly caching something you’ve forgotten about.

3. Revocation is a single click

If a laptop is lost or a machine is decommissioned, you delete that one public key from your GitHub settings. Every other machine you own keeps working. Compare that to a leaked token, which usually means rotating and re-pasting across everything.

The natural extension of this: one key per machine, not one key copied between machines. A key that exists on three laptops can only be revoked on all three at once. A key that exists on one can be revoked precisely.

Why ed25519 and not rsa

The -t flag on ssh-keygen picks the key type — the underlying algorithm.

ed25519 is the modern default. It’s fast, it has a fixed 256-bit strength, and it needs no size flag because there’s no size to choose. rsa is the older option and needs -b 4096 spelled out to be adequately strong, producing a noticeably longer key. Unless something in your environment specifically requires RSA, use ed25519.

Why the passphrase question is a real decision

When ssh-keygen asks for a passphrase, it’s asking whether to encrypt the private key file on disk.

The argument for setting one: if that file is ever copied off your machine — stolen laptop, malware, a backup that ended up somewhere it shouldn’t — it’s inert without the passphrase. The private key is the single most sensitive file in this setup, and a passphrase is the only thing protecting it at rest.

The argument for skipping it: on a machine with full-disk encryption and a strong login password, the added protection is smaller, and you avoid being prompted during git operations.

The middle path, and the one I’d recommend, is: set a passphrase, then use ssh-agent so you only type it once per session. You get the at-rest protection without the repetition.

One thing worth being clear about: the passphrase is used purely locally, to decrypt your private key when it’s needed. It is never sent to GitHub. GitHub doesn’t know it exists.

Why ssh-agent and ssh-add exist

ssh-agent is a small background process that holds your decrypted private key in memory. ssh-add is how you hand a key to it.

Whether you need it depends on your situation:

Your setupDo you need the agent?
Default location, no passphraseNo. SSH reads the key straight from disk each time.
Default location, with passphraseFor convenience. Without it you’re prompted on every push and pull. With it, once per session.
Non-default locationYes — or set IdentityFile in ~/.ssh/config so SSH knows where to look.

There’s a meaningful difference between the two shells here. In PowerShell, ssh-agent is a Windows service — start it once, set it to Automatic, and it survives reboots. In Git Bash, eval "$(ssh-agent -s)" starts an agent scoped to that terminal session; close the window and it’s gone. Neither is wrong, they’re just different lifetimes.

Why the -C comment matters less than you’d think

The -C flag attaches a comment to the public key. That’s all it is: a label, stored as trailing text in the .pub file, so that when you’re looking at five keys in your GitHub settings you can tell which machine each one came from.

GitHub never validates it. It isn’t publicly visible. -C "work-laptop" is a perfectly good value.

That said, there’s a related decision that does have public consequences, and it’s easy to conflate the two.

Why your commit email is the one that’s actually public

Your SSH key comment is private. Your git commit email is baked into every commit you push and is visible to anyone who clones the repo. These are separate things that happen to both involve an email address, which is why they get confused.

If your repositories are public — a portfolio, open source, anything a recruiter might browse — you probably don’t want your personal email sitting in the commit log where scrapers can find it. GitHub provides a noreply address for exactly this:

GitHub → Settings → Emails → enable “Keep my email address private” → copy the address shown.

It looks like 123456789+username@users.noreply.github.com. Commits authored with it still link to your GitHub profile, your avatar, and your contribution graph, because GitHub recognises it as yours. You get attribution without publishing a mailbox.

The name is a different call. user.name isn’t validated by anything, but it is what shows up next to every commit. If the history is something you’d point an employer at, a real full name reads better than a handle.

So, for a public-facing setup:

Why SSH auth and commit identity are unrelated

This is the single most common confusion, so it’s worth stating directly:

Git will refuse to create a commit until user.name and user.email are set, even if you never touch a remote. And nothing stops you from setting your commit email to something you don’t own — Git doesn’t check. The only consequence is that the commit won’t link to any GitHub account.

Two different questions, two different mechanisms.

How: PowerShell

# 1. Confirm OpenSSH is available
Get-Command ssh-keygen
# If it's missing, install it (run PowerShell as Administrator):
Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0

# 2. Generate the key pair
ssh-keygen -t ed25519 -C "123456789+yourusername@users.noreply.github.com"
# Enter to accept the default path; set a passphrase when prompted

# 3. Verify the two files exist
Get-ChildItem C:\Users\<you>\.ssh
Get-Content C:\Users\<you>\.ssh\id_ed25519.pub

# 4. Start the SSH agent service (Administrator, if it's disabled)
Get-Service ssh-agent
Set-Service -Name ssh-agent -StartupType Automatic
Start-Service ssh-agent

# 5. Load the key into the agent
ssh-add C:\Users\<you>\.ssh\id_ed25519
ssh-add -l    # should list a fingerprint

# 6. Copy the PUBLIC key to the clipboard
Get-Content C:\Users\<you>\.ssh\id_ed25519.pub | Set-Clipboard

# 7. GitHub → Settings → SSH and GPG keys → New SSH key → paste → Add SSH key

# 8. Test the connection
ssh -T git@github.com
# Type 'yes' to accept GitHub's host fingerprint on first connect

# 9. Set your commit identity
git config --global user.name "Your Full Name"
git config --global user.email "123456789+yourusername@users.noreply.github.com"

# 10. Prove it end to end
cd D:\Projects
git clone git@github.com:yourusername/your-repo.git

Step 3 is worth not skipping. Confirming that both id_ed25519 and id_ed25519.pub were created — and reading the .pub file so you know what you’re about to paste — takes five seconds and rules out the most common failure, which is pasting the wrong file.

How: Git Bash

Same sequence, different shell. The two genuine differences are the agent (session-scoped, not a service) and the clipboard command.

# 1. Check for keys you may already have
ls -al ~/.ssh

# 2. Generate the key pair
ssh-keygen -t ed25519 -C "123456789+yourusername@users.noreply.github.com"
# Enter to accept the default path; set a passphrase when prompted

# 3. Verify
ls -al ~/.ssh
cat ~/.ssh/id_ed25519.pub

# 4. Start an agent for this session
eval "$(ssh-agent -s)"

# 5. Load the key
ssh-add ~/.ssh/id_ed25519
ssh-add -l

# 6. Copy the PUBLIC key to the clipboard
clip < ~/.ssh/id_ed25519.pub

# 7. GitHub → Settings → SSH and GPG keys → New SSH key → paste → Add SSH key

# 8. Test
ssh -T git@github.com

# 9. Commit identity
git config --global user.name "Your Full Name"
git config --global user.email "123456789+yourusername@users.noreply.github.com"

# 10. Real clone
cd /d/Projects
git clone git@github.com:yourusername/your-repo.git

Step 1 first, always. If ~/.ssh already contains an id_ed25519, you have a key — generating a new one at the same path will happily overwrite it and break every service that trusted the old one.

What success looks like

Hi <username>! You've successfully authenticated, but GitHub does not provide shell access.

That message reads like a failure the first time you see it. It isn’t. The -T flag in ssh -T means don’t allocate a pseudo-terminal — don’t try to open an interactive shell on the far end. We only want to know whether authentication works. GitHub is confirming that it does, and reminding you there’s no shell there to give you. That’s the correct, complete success case.

Verifying an existing setup

If you’re coming back to a machine and want to know what state it’s actually in, this sequence answers every question without changing anything:

ls -al ~/.ssh                    # do key files exist?
cat ~/.ssh/id_ed25519.pub        # which public key, and what comment?
ssh-add -l                       # is the agent running with a key loaded?
cat ~/.ssh/config                # any custom host rules? (missing file is normal)
ssh -T git@github.com            # does GitHub actually accept this key?
git config --global user.name    # commit identity
git config --global user.email
git config --global --list       # everything, if you want the full picture
git remote -v                    # inside a repo: SSH or HTTPS remote?
CheckCommandSuccess looks like
Key files existls -al ~/.sshid_ed25519 and id_ed25519.pub present
Public key readablecat ~/.ssh/id_ed25519.pubStarts with ssh-ed25519 AAAA...
Agent loadedssh-add -lA fingerprint, not “no identities”
GitHub authssh -T git@github.comHi <user>! You've successfully authenticated...
Commit identitygit config --global user.emailYour intended address
Clone worksgit clone git@github.com:...No password prompt
Remote uses SSHgit remote -vURL begins git@github.com:

That last one catches a quiet failure worth knowing about. You can set SSH up perfectly and still be pushing over HTTPS, because a repo cloned before the switch keeps its original remote URL. If git remote -v shows https://github.com/..., point it at SSH:

git remote set-url origin git@github.com:yourusername/your-repo.git

What those other files in ~/.ssh are

Two files appear on their own and cause a moment of “did I do something wrong?”

known_hosts stores the fingerprint of every server you’ve connected to and accepted. It’s written the first time you connect — that’s the yes you typed at step 8. On every subsequent connection, SSH checks the server against this record. If GitHub’s host key ever didn’t match, SSH would refuse to connect and warn you loudly, because the most likely explanation is that something is impersonating GitHub. It’s active man-in-the-middle protection, and it’s the reason that first-connect prompt exists rather than being silently auto-accepted.

known_hosts.old is just an automatic backup, created whenever known_hosts gets rewritten — for example by ssh-keygen -R github.com to clear a stale entry. SSH never reads it. Deleting it is harmless.

Common misconceptions

“The passphrase is my GitHub password.” It isn’t related to GitHub at all. It decrypts your private key locally and never leaves your machine.

“I should back up my private key somewhere safe.” Generate a new key per machine instead. Copying private keys around multiplies the number of places one can leak from and makes revocation all-or-nothing.

“My repos are on D:, so I need to configure something.” No. Keys live with your Windows user profile; repo location is irrelevant.

“SSH is set up, so my commits are authenticated as me.” SSH authenticates the connection. The commit author is whatever git config user.email says, and Git doesn’t verify it. Different layer entirely. (If you want commits themselves to be cryptographically verified, that’s commit signing — a separate setup.)

does not provide shell access means it failed.” It means it worked. Read the first half of the sentence.

“The -C email needs to be real.” It’s a comment. -C "desktop-2026" is fine. The email that actually matters publicly is git config user.email.

Key takeaways

FAQ

Do I have to use the SSH agent? Only if your key has a passphrase or lives outside the default location. A passphrase-free key at ~/.ssh/id_ed25519 is read straight from disk. But a passphrase plus an agent is a better trade than skipping both.

Can I use the same key on my work and personal machines? You can, but don’t. Separate keys mean you can revoke one machine without disturbing the other, and your GitHub key list tells you which machines have access.

Why doesn’t my agent survive closing Git Bash? Because eval "$(ssh-agent -s)" starts an agent bound to that shell session. The Windows ssh-agent service, set to Automatic in PowerShell, persists across reboots. If you want persistence in Git Bash, start the agent from your ~/.bashrc or use the Windows service.

HTTPS with a personal access token works fine. Is SSH really better? For day-to-day work, mostly it’s the same experience with fewer prompts and no token expiry to manage. The real advantages are that no credential is transmitted and revocation is per-machine. In an environment where tokens get pasted into config files and forgotten, that difference matters.

I set everything up but git still asks for a password. Almost always an HTTPS remote left over from an earlier clone. Check git remote -v and switch it with git remote set-url.

Does the commit email have to match my GitHub account? Only if you want commits to link to your profile and count toward your contribution graph. The noreply address counts. An unrecognised address produces valid commits that simply aren’t attributed to anyone.

Closing thought

None of this is difficult, but almost all of it is opaque the first time. The commands are four minutes of work; understanding what you’ve built is what stops you from being stuck the next time something doesn’t connect.

The shape worth carrying away: a secret that stays on your machine, a public half you can hand out freely and revoke individually, a fingerprint record that catches impersonation, and — completely separately — a name and email that label your work. Once those four ideas are distinct in your head, the commands stop being incantations and start being obvious.