A padlock and its key
ssh-keygen makes two files that belong together. Think of the public key as a padlock: you can hand out copies to every server you use, and fixing it on a door gives nobody anything. The private key is the only key that opens that padlock. It stays on your Mac.
So the work splits between two machines. The emitter, your Mac, makes the pair and keeps the private half. The receiver, the server, stores the public half in a file called authorized_keys: one line per key that may come in.
Why ed25519? It's the modern key type: short keys, fast, and accepted by every current server, GitHub and shared hosting included. Older guides use -t rsa -b 4096, which still works but is four times longer for no gain.
Two machines, four steps
Each step happens on one side. Left, what you type on your Mac. Right, what happens on the server.
-
1On your Mac
Make the pair
Press Enter to accept the file name. A passphrase is optional: it protects the private key if your Mac is stolen.
Terminal, on your Macssh-keygen -t ed25519 -C "you@mac"- ~/.ssh/id_ed25519
- private, never share
- ~/.ssh/id_ed25519.pub
- public, safe to copy
Nothing changes on the server yet.
-
2On your Mac
Send the public half
Copy the
.pubfile to your home folder on the server. It's the last time you type the server password.Terminal, on your Macscp ~/.ssh/id_ed25519.pub user@server.example:Replace
user@server.examplewith your SSH login. Keep the colon at the end.Arrives on the server
The file lands as
~/id_ed25519.pub. A padlock lying on the floor: it does nothing until step 3 fixes it on the door. -
Log in once more with your password:
ssh user@server.example3On the server
Let the key in
Add the key as a new line of
authorized_keys, then lock the permissions: SSH ignores the file if anyone else can write to it.Terminal, on the servermkdir -p ~/.ssh && chmod 700 ~/.ssh cat ~/id_ed25519.pub >> ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys rm ~/id_ed25519.pubTwo chevrons,
>>. They add a line. A single>replaces the whole file and locks out every other key already in it. -
4On your Mac
Knock without a password
Log out of the server, then connect again. No password prompt means it worked.
Terminal, on your Macssh user@server.exampleThe server checks
It finds your public key in
authorized_keys, asks your Mac to prove it holds the matching private key, and opens the door. The handshake is below.
Steps 2 and 3 in one line
Same result, typed on your Mac: the public key travels through the pipe and the server appends it.
cat ~/.ssh/id_ed25519.pub | ssh user@server.example 'mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys'
Or with the dedicated tool, when your system has it: ssh-copy-id user@server.example. On shared hosting without a shell, paste the .pub line in the SSH page of the hosting panel instead.
What happens when you connect
The private key never crosses the network. Your Mac only proves it has it, by signing a challenge that changes every time.
-
Mac: “I'm user, and this is the public key I'd like to use.”
key id
Server looks for it in
authorized_keys. -
Found: the padlock is on this door.
challenge
Server: “Sign this random message with the matching private key.”
-
Mac: signs it with
id_ed25519, after the passphrase if there is one.signature
Only the private key can produce it.
-
You get a shell, no password asked.
Server: checks the signature with the public key. It matches: door open.
A key for GitHub Actions
An automatic deployment works the same way, with GitHub Actions as the emitter. Give it a key of its own, so you can revoke it without touching yours: no passphrase (nobody is there to type it), and a name that says what it's for.
ssh-keygen -t ed25519 -f ~/.ssh/site_deploy -N "" -C "github-actions"
cat ~/.ssh/site_deploy.pub | ssh user@server.example 'cat >> ~/.ssh/authorized_keys'
ssh -i ~/.ssh/site_deploy user@server.example # must log in without a password
- The public half goes on the server
- As above, one more line in
authorized_keys. - The private half goes to GitHub, and only there
- In the repository, Settings › Secrets and variables › Actions, add a secret such as
DEV_SSH_KEYand paste the whole content of~/.ssh/site_deploy, from theBEGINline to theENDline. The workflow writes it to a file before itsrsyncstep.
Then every push deploys by itself, and gpush lets you watch it from the terminal.
Good to know
- Type the passphrase once per session
- On macOS,
ssh-add --apple-use-keychain ~/.ssh/id_ed25519keeps it in the Keychain, so SSH stops asking. - One pair per computer
- A new laptop gets its own pair and its own line in
authorized_keys. The comment after-Ctells the lines apart; delete one to lock that computer out. - A short name for the server
- In
~/.ssh/configon your Mac, aHost mysiteblock withHostName,UserandIdentityFilelines turns the whole command intossh mysite.
If something doesn't work
The server still asks for a password
Nine times out of ten, permissions. On the server, chmod 700 ~/.ssh and chmod 600 ~/.ssh/authorized_keys, and your home folder must not be writable by others. Check too that you added the key for the same user you log in as.
Permission denied (publickey)
The server only accepts keys and yours isn't on its list. Run ssh -v user@server.example: the lines starting with Offering public key show which keys your Mac tried. If yours isn't there, point to it with -i ~/.ssh/id_ed25519.
WARNING: UNPROTECTED PRIVATE KEY FILE
Your private key is readable by other users of your Mac, so SSH refuses it. chmod 600 ~/.ssh/id_ed25519 fixes it.
The GitHub deployment fails at the SSH step
Test the deploy key from your Mac first, with ssh -i ~/.ssh/site_deploy. If that works, the secret is usually incomplete: it must contain the private key, not the .pub, with its BEGIN and END lines and the newline after the last one.
I pasted my private key somewhere by mistake
Treat it as lost. Remove its line from authorized_keys on every server, make a new pair, and install the new public key.