Skip to the tutorial
webmasterbulletin.net All tutorials

Log in to your server with a key, not a password

Two commands: one on your Mac makes a pair of keys, one on the server lets the public half in. After that, ssh, rsync and your GitHub deployments get in without typing anything.

See the two sides
  • macOS or Linux
  • About 5 minutes
  • Needs one SSH login

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.

  1. 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 Mac
    ssh-keygen -t ed25519 -C "you@mac"
    ~/.ssh/id_ed25519
    private, never share
    ~/.ssh/id_ed25519.pub
    public, safe to copy
  2. 2On your Mac

    Send the public half

    Copy the .pub file to your home folder on the server. It's the last time you type the server password.

    Terminal, on your Mac
    scp ~/.ssh/id_ed25519.pub user@server.example:

    Replace user@server.example with 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.

  3. 3On 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 server
    mkdir -p ~/.ssh && chmod 700 ~/.ssh
    cat ~/id_ed25519.pub >> ~/.ssh/authorized_keys
    chmod 600 ~/.ssh/authorized_keys
    rm ~/id_ed25519.pub

    Two chevrons, >>. They add a line. A single > replaces the whole file and locks out every other key already in it.

  4. 4On your Mac

    Knock without a password

    Log out of the server, then connect again. No password prompt means it worked.

    Terminal, on your Mac
    ssh user@server.example

    The 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.

Terminal, on your Mac
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.

  1. Mac: “I'm user, and this is the public key I'd like to use.”

    key id

    Server looks for it in authorized_keys.

  2. Found: the padlock is on this door.

    challenge

    Server: “Sign this random message with the matching private key.”

  3. Mac: signs it with id_ed25519, after the passphrase if there is one.

    signature

    Only the private key can produce it.

  4. 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.

Terminal, on your Mac
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_KEY and paste the whole content of ~/.ssh/site_deploy, from the BEGIN line to the END line. The workflow writes it to a file before its rsync step.

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_ed25519 keeps 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 -C tells the lines apart; delete one to lock that computer out.
A short name for the server
In ~/.ssh/config on your Mac, a Host mysite block with HostName, User and IdentityFile lines turns the whole command into ssh 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.

Next: deploy on every push

With the key in place, a GitHub Actions workflow can copy your site to the server, and one command commits, pushes and follows it.