SSH keys
Intermediate Git CLI GitLab UI GitHub UI
Why this matters
An SSH key is the closest thing to "log in once and forget about it". Generating one takes thirty seconds, but the two files it makes have very different rules: one is public and meant to be pasted into websites, the other is the thing that is your identity. Mixing them up is the mistake this lesson exists to prevent.
Generate the pair
$ ssh-keygen -t ed25519 -C "ana@northwind-trails.example"Generating public/private ed25519 key pair.
Your identification has been saved in /Users/you/.ssh/id_ed25519
Your public key has been saved in /Users/you/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:YMp4KTlbeWOy2ELLvIbfcMl2e0oRPmBhrggl5ND78rg ana@northwind-trails.exampleThree prompts appear: the file location (press Enter for the default) and a passphrase, twice. -t ed25519 picks the modern key type, which is short and fast; -C adds a comment, conventionally your email, so you can recognize the key in a list later.
Two files result, with deliberately different permissions:
$ ls -l ~/.ssh-rw-------@ id_ed25519
-rw-r--r--@ id_ed25519.pub| File | What it is | Rules |
|---|---|---|
id_ed25519 |
The private key. Proves you are you. | Never leaves your computer. Never committed, never pasted anywhere, never emailed. Readable only by you (-rw-------). |
id_ed25519.pub |
The public key. A lock that only your private key opens. | Safe to paste into GitLab, GitHub, anywhere. Publishing it gives nobody access. |
The passphrase
An empty passphrase means anyone with your laptop's files has your key. A passphrase encrypts the private key on disk, so a stolen file is useless without it, and the SSH agent remembers it for the session so you type it at most once a day. Use one on any machine you carry.
Upload the public half
$ cat ~/.ssh/id_ed25519.pubssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKOn7GbsU2ijtkxOAIdg0TSJ… ana@northwind-trails.exampleCopy the whole line, from ssh-ed25519 to the comment, and paste it:
- GitLab: avatar → Edit profile → SSH keys → Add new key. Give it a title naming the machine ("work laptop"), and optionally an expiry date.
- GitHub: avatar → Settings → SSH and GPG keys → New SSH key. Title, key type Authentication Key, paste, Add SSH key.
On macOS, pbcopy < ~/.ssh/id_ed25519.pub copies it to the clipboard; on Windows Git Bash, clip < ~/.ssh/id_ed25519.pub.
Test it
$ ssh -T git@gitlab.comThe first connection asks you to confirm the server's fingerprint; type yes. A working key answers with a greeting by name (Welcome to GitLab, @ana!, or on GitHub Hi ana! You've successfully authenticated…) and then closes, because these servers do not give you a shell. That greeting is the whole test.
Then use the SSH URL:
$ git clone git@gitlab.com:northwind-trails/trailguide.gitOr switch an existing clone:
$ git remote set-url origin git@gitlab.com:northwind-trails/trailguide.gitThe agent
The SSH agent holds your decrypted private key in memory so the passphrase is asked once per session. macOS and most Linux desktops start it automatically. If you are asked for the passphrase on every push:
$ eval "$(ssh-agent -s)"
$ ssh-add ~/.ssh/id_ed25519On macOS, ssh-add --apple-use-keychain ~/.ssh/id_ed25519 stores the passphrase in the Keychain so it survives restarts, and a matching entry in ~/.ssh/config makes it automatic:
Host gitlab.com
IdentityFile ~/.ssh/id_ed25519
UseKeychain yes
AddKeysToAgent yesTwo accounts on one machine
Work and personal accounts on the same host need one key each and a ~/.ssh/config that distinguishes them by a made-up host alias:
Host gitlab-work
HostName gitlab.com
User git
IdentityFile ~/.ssh/id_ed25519_work
Host gitlab-personal
HostName gitlab.com
User git
IdentityFile ~/.ssh/id_ed25519_personalThen clone with the alias in place of the host: git clone git@gitlab-work:northwind-trails/trailguide.git. Existing clones are repointed with git remote set-url. Pair this with a per-repository user.email (lesson 3.4) so the commits carry the right identity too.
How to do it
$ ssh-keygen -t ed25519 -C "you@example.com"
$ cat ~/.ssh/id_ed25519.pub # copy this, paste on the website
$ ssh -T git@gitlab.com # expect a greeting by name
$ git remote set-url origin git@gitlab.com:group/project.gitVS Code has no key management; it uses the system's SSH just as the terminal does. Generate and upload the key from the integrated terminal, and cloning an SSH URL then works from Git: Clone.
If a clone hangs waiting for a passphrase you cannot see, the agent is not running; run ssh-add in the terminal first.
Same: IntelliJ uses the system SSH by default. Settings → Version Control → Git → SSH executable offers Native (the system's, recommended) or Built-in (IntelliJ's own, which can read a key file directly and ask for the passphrase in a dialog).
If native SSH cannot find your key, switching to Built-in and pointing it at the file is a quick workaround.
Edit profile → SSH keys lists every key with its title, fingerprint, creation date, last-used date and optional expiry. Add new key takes the pasted public key. Deleting a key takes effect immediately, which is what you do if a machine is lost.
GitLab also supports deploy keys (per project, for servers and automation) under Settings → Repository → Deploy keys; those are read-only unless write access is granted deliberately.
Settings → SSH and GPG keys lists authentication keys and signing keys separately; a key added as an Authentication Key is the one Git uses. Each entry shows its fingerprint and when it was last used.
GitHub's per-repository equivalent for automation is Settings → Deploy keys on the repository, again read-only by default.
If your organization uses SAML single sign-on, a new key must be authorized for the organization before it works (lesson 8.5).
Common mistakes
Permission denied (publickey). The most common message. Runssh -T git@gitlab.comto test in isolation, then check: is the public key uploaded, is the private key present in~/.ssh, is the agent running (ssh-add -l), and is the remote URL really SSH?- Uploading the private key. Delete the pair and start again; see the warning above.
Host key verification failed. The server's fingerprint changed or you answerednoat the first prompt. Reconnect and confirm, after checking the platform's published fingerprints if you are cautious.- A key that works in the terminal but not in the IDE. The IDE's environment lacks the agent; use the terminal to
ssh-add, or switch IntelliJ to its built-in SSH. - Wrong permissions. SSH refuses to use a private key that other users can read:
chmod 600 ~/.ssh/id_ed25519.
Try it yourself
Goal: create a key pair, inspect both files, and understand which is which — without uploading anything.
- Generate a practice pair in a scratch location, so your real key is untouched:
ssh-keygen -t ed25519 -C "practice" -f ~/practice_key -N "". - Run
ls -l ~/practice_key*and compare the permissions of the two files. - Run
cat ~/practice_key.puband note that it is one line startingssh-ed25519. - Run
head -2 ~/practice_keyand note that it saysOPENSSH PRIVATE KEY. - Delete both:
rm ~/practice_key ~/practice_key.pub.
Expected result: the private key is -rw------- (only you) and the public key -rw-r--r-- (anyone); the public one is a single short line, the private one is a block beginning -----BEGIN OPENSSH PRIVATE KEY-----.
Show solution
The permissions are the operating system enforcing the distinction, and the file contents make it unmistakable: if what you are about to paste begins with -----BEGIN, stop, because that is the private half. -N "" in step 1 means "no passphrase", which is fine for a throwaway key and not what you want for a real one.