Create an SSH key
An SSH key replaces the password when logging into a server: the private key stays on your machine, the public one goes into authorized_keys. This guide covers why Ed25519 has been the default since OpenSSH 9.5 and DSA no longer exists as of 10.0, how to generate and transfer the key pair, which file permissions sshd insists on, and how to keep several keys apart with ~/.ssh/config.
Four commands, in this order
Say you reach a server with a password today and want that to stop. On your side the job is four commands: check the OpenSSH version, generate a key, copy the public half across, test the login. The order matters more than any single option, so this guide runs through the steps first and explains the choices behind them afterwards: which key type, why the server sometimes refuses a key, how several keys stay apart.
The payoff shows up in the server log. A machine on the open internet collects login attempts with guessed usernames and passwords around the clock. Against a key there is nothing to guess. Once key login works and PasswordAuthentication no is set in /etc/ssh/sshd_config, those attempts keep arriving and stop mattering.
Step 1: find out which OpenSSH you have
The version decides what ssh-keygen produces when you name no type, and whether DSA exists on your system at all:
ssh -V
Debian 12 “Bookworm” ships OpenSSH 9.2, Debian 13 “Trixie” ships 10.0. Under 9.2, a bare ssh-keygen still creates an RSA key. Under 10.0 you get Ed25519. You can request Ed25519 explicitly on either, because OpenSSH has supported it since version 6.5 in January 2014; only genuinely old machines on the far end force you back to RSA.
No root rights, no extra package. Write access to your own home directory is all ssh-keygen needs.
Linux and macOS have OpenSSH in the base system. On Windows the client has been an optional feature since Windows 10 build 1809, and Microsoft’s overview lists it as not installed on Windows 10, so it may still need adding through the optional features (Microsoft overview). If ssh -V answers in PowerShell, it is there. It lives in C:\Windows\System32\OpenSSH (Microsoft documentation) and behaves exactly as it does in a Linux terminal.
Two other Windows tools are still common:
PuTTY with PuTTYgen, currently version 0.85 released on 16 August 2026 (download page). PuTTY keeps keys in its own
.ppkformat; for an OpenSSH server you export the key from PuTTYgen into OpenSSH format first.Git Bash, part of Git for Windows, with a complete OpenSSH environment inside.
With the built-in client available, you need neither.
Step 2: generate the key pair
ssh-keygen -t ed25519 -C "your_email@example.com"
-t chooses the type. -C adds a comment to the public key, typically an email address or user@host. It ends up at the end of the line in authorized_keys, and two years from now it is the only clue to which entry can go. From OpenSSH 9.5 on you could leave out -t ed25519; writing it anyway means your shell history records what actually happened.
ssh-keygen then asks where to save the key. The default is ~/.ssh/, with the filename taken from the type: id_ed25519 and id_ed25519.pub. Enter accepts. One trap: if a key of the same type already sits there, the program asks whether to overwrite it, and a reflexive “y” destroys it. Every server holding the matching public key is then unreachable with it, and nothing brings it back.
Next comes the passphrase, entered twice. It encrypts the private key file on disk. A key without one is just a file, copyable out of any backup, any sync folder, by anyone who can read your home directory. An empty passphrase is still a legitimate choice for one case: a backup script that runs alone at night cannot type anything. Such a key should be fenced in with restrict and command= in authorized_keys (see “Restricting what a key may do” below). The key you use yourself gets a passphrase, and ssh-agent means typing it once per session rather than once per connection.
Two files now exist. The private key id_ed25519 has no extension and never leaves this machine, not even “just briefly” by mail to yourself. The .pub file is the one you hand out. A quick look confirms it:
cat ~/.ssh/id_ed25519.pub
One line, three parts: type, key, comment. A line starting with ssh-ed25519 means the type is right, and this exact line is what the server needs, complete and without a stray line break. ssh-keygen -l -f ~/.ssh/id_ed25519.pub prints the fingerprint, the value to compare later when you want to know which key a server really has on file.
Step 3: put the public key on the server
ssh-copy-id username@serveraddress
ssh-copy-id logs in with whatever works right now, usually your password, creates ~/.ssh and authorized_keys if they are missing and appends your key. It first tries which of your keys are installed already and only transfers the ones that are not (ssh-copy-id(1)).
Which keys those are is less obvious than it looks. Without -i, the program asks the agent first (ssh-add -L) and sends every key loaded there. Only with an empty agent does it fall back to the most recent file matching ~/.ssh/id*.pub. On a machine with several keys, that easily puts the wrong one on the server, or one too many. So name it:
ssh-copy-id -i ~/.ssh/id_ed25519.pub username@serveraddress
Step 4: test with the old session still open
Keep the session you are logged in with and open a second one. If the new login fails, you are still inside and can fix things.
ssh -v username@server shows which key the client offers and whether the server accepts it. Look for Authentications that can continue: publickey and, further down, Authenticated to … using "publickey". That is the proof. Only after seeing it is PasswordAuthentication no worth considering, and the old session stays open until the new one demonstrably works.
When the server ignores your key
The typical symptom: you have just installed the key, and the client asks for a password anyway. In most cases the cause is file permissions, not the key. sshd checks ownership and modes before it trusts authorized_keys, and refuses the key if that file, the ~/.ssh directory or the home directory is writable by other users. The check is called StrictModes and is on by default (sshd_config(5)).
The server log then mentions bad ownership or modes. Where to read it depends on the system. Debian stopped installing rsyslog by default with version 12 (Debian release notes), so /var/log/auth.log is usually absent there and journalctl -u ssh shows the message instead. The fix on the server:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
The client is just as strict about its own side. The private key must be readable by you alone, chmod 600 ~/.ssh/id_ed25519; otherwise OpenSSH refuses to use it and prints an “UNPROTECTED PRIVATE KEY FILE” warning.
The second common cause is the opposite of a missing key: too many. That case gets its own section further down, under several keys.
The same thing by hand
Whatever ssh-copy-id does, you can do yourself in four steps. It is worth doing once, if only to know where to look when something fails.
Make sure you can get in at all, by password or with a key that is already installed. Without any working access, only the hosting provider’s console helps.
Print the public key and copy the whole line:
cat ~/.ssh/id_ed25519.pub
Log into the server:
ssh username@server_ip_address
Append the line to
~/.ssh/authorized_keys. Mind the two angle brackets: a single>truncates the file and throws away every key already in it.
echo "your_public_key" >> ~/.ssh/authorized_keys
Pasting the line in nano or vim works just as well. Afterwards set the permissions as shown above, then test from a second session.
Which key type, and why Ed25519
Older guides present four key types side by side as if it were a matter of taste. Today two are serious options, one is pointless and one no longer exists.
Ed25519 has been the default since OpenSSH 9.5, released on 4 October 2023 (OpenSSH release notes). Its length is fixed, so -b does nothing, and the public key fits on one short line.
RSA remains the fallback for old peers. Without -b, ssh-keygen creates 3072 bits, with 1024 as the minimum (ssh-keygen(1)). 4096 is the length GitHub names for legacy systems:
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
The weak point of RSA was never the key but the signature. OpenSSH 8.8, released 26 September 2021, switched off RSA signatures with SHA-1 because SHA-1 is considered broken. Existing RSA keys keep working as long as both ends speak RSA/SHA-256 or RSA/SHA-512.
GitHub draws its own lines. It stopped accepting DSA keys (ssh-dss) on 15 March 2022. For RSA the cutoff is earlier: keys generated after 2 November 2021 must sign with SHA-2, while older ones may still use any signature algorithm (GitHub documentation).
ECDSA works, but offers nothing over Ed25519 and depends on the NIST curves. No reason to start with it now.
DSA is gone. OpenSSH disabled DSA keys by default back in 2015, dropped them from the default build in 9.9 on 19 September 2024 and removed the algorithm completely in 10.0 on 9 April 2025. Debian 13 ships 10.0 (openssh-client 1:10.0p1), so there is not even an option left. A guide still showing -t dsa is older than its date suggests.
What happens during a key login
Asymmetric cryptography, in practice: two files that belong together. Whatever the private key signs, only the public key can verify. The server does not ask whether you know a secret, it checks whether you hold one. A password crosses the network on every login and can be guessed. A private key never leaves your machine.
The exchange is short. Your client names the public key it wants to use. If the server finds it in the target user’s ~/.ssh/authorized_keys, it sends a challenge that only the matching private key can answer. The client signs, the server checks the signature. That is also why the public key has to be on the server beforehand: it is the one manual step, and the one ssh-copy-id takes over.
The public key is no secret. It can sit in a ticket or an email, since the private key cannot be derived from it. The private key, on the other hand, is the access right itself: whoever has the file gets onto every server that carries its counterpart. authorized_keys takes any number of public keys, one per line, so several people or devices can share an account and any one of them can be removed without touching the rest.
That is why keys turn up wherever someone works on remote machines regularly. Administrators log into servers with them, developers push to GitHub, GitLab or Codeberg without typing anything at each git push. Unattended jobs have no alternative: a backup script at three in the morning cannot enter a password, and neither can a deploy step in a CI pipeline.
Several keys, several servers
A second key raises a problem that one key never has. Without instructions, the client offers the server everything it has, one after another, and the server counts each offered key as a failed attempt. At MaxAuthTries, six by default, it closes the connection (sshd_config(5)). Carry enough keys in your agent and you get locked out of a server where you are, in fact, authorised.
~/.ssh/config solves that and saves typing on top:
Name keys after their purpose:
id_ed25519_backupsays more thanid_ed25519_2, and later the name is all that tells you what the key was for.One entry per host, holding
IdentityFilealong with any non-standard user and port, so they no longer travel on every command line.
Host server1
HostName server1.example.com
User username
IdentityFile ~/.ssh/id_ed25519_project1
IdentitiesOnly yes
Port 2222
Host server2
HostName server2.example.com
User username
IdentityFile ~/.ssh/id_ed25519_project2
IdentitiesOnly yes
Port 4321
Now ssh server1 is enough. IdentitiesOnly yes is the line that actually fixes the lockout: without it the client still offers every agent key before the configured one and hits the same MaxAuthTries limit.
Tidying up belongs here too. A key you no longer need has to be removed from authorized_keys on the server. Deleting it locally only locks you out, while the server entry keeps working for anyone who still has a copy of the file.
Keeping keys safe
Four places where things go wrong in practice: weak passphrases, keys that outlive their reason, automation keys with too many rights, and missing or careless backups.
Passphrases that hold up offline
A passphrase does not protect a service, it protects a file, and an attacker who has the file can try guesses offline at leisure. Length beats character variety here. Four to six random words from a password manager or a Diceware list are easier to remember and harder to crack than “MyD0g!s$weet”; substitutions like that have been in every dictionary attack for years.
The file format helps. Since OpenSSH 7.8, released 24 August 2018, ssh-keygen writes private keys in the OpenSSH format instead of OpenSSL’s PEM format, explicitly because it resists offline password guessing better. -a sets the number of key derivation rounds, 16 by default (ssh-keygen(1)); more rounds make each guess more expensive:
ssh-keygen -t ed25519 -a 100 -C "your_email@example.com"
Store the passphrase in the password manager. Not on a note beside the screen, and never in a file next to the key.
When a key has to be replaced
The OpenSSH documentation recommends no fixed rotation interval. The six to twelve months you often read are a convention, not a measured value, and a key does not get weaker with age. Clear reasons to replace one immediately do exist:
The device with the private key is lost, stolen or compromised.
The key sat without a passphrase in a backup that someone else had in hand.
Someone leaves the team, the project or the company.
The sequence never changes: create the new key, install it, test it, and only then delete the old line from authorized_keys. Delete first and find out the new key does not work, and you are locked out of your own server.
To tell the lines apart, ssh-keygen -lf ~/.ssh/authorized_keys prints a fingerprint and the comment for every entry. Comparing it with ssh-keygen -lf ~/.ssh/id_ed25519.pub shows which line is yours.
Restricting what a key may do
Each line in authorized_keys may start with options, and for keys without a passphrase they are the real protection. restrict disables port forwarding, agent forwarding, X11 and PTY allocation in one word. command="…" binds the key to a single command, from="…" to an origin address. A backup key limited like that can run its backup and nothing else, even in the wrong hands.
Staying current costs only attention: OpenSSH security updates arrive through the normal package manager. Which of those commands need root and how to run them properly is covered in How to use Sudo on Debian.
Backing up keys
A private key without a backup is access that ends with a failed disk. A private key in the wrong backup is access for someone else. Both are solved together: back it up only with a passphrase set, and only to where your other secrets live, an encrypted volume or the password manager, not a sync folder that happens to replicate to three devices.
Often a second key is the better backup. Two keys on two devices, both in authorized_keys, and a lost laptop costs one deleted line instead of the access. The public half needs no backup at all; it can be regenerated from the private key at any time:
ssh-keygen -y -f ~/.ssh/id_ed25519 > ~/.ssh/id_ed25519.pub
Conclusion
The short path stays four steps: ssh-keygen -t ed25519, a passphrase, ssh-copy-id -i, a test in a second session. Everything else in this guide answers questions that come up along the way.
Three points to keep. Ed25519 has been the default since OpenSSH 9.5 and DSA was removed in 10.0, so a guide that opens with -t dsa or recommends RSA first is out of date. Permissions on ~/.ssh are the most common reason a key seems not to work. And the old session stays open until the new one has been proven.
With key login in place, the next step is usually tightening the server: Show used ports in Linux reveals what is listening outward, and Restore iptables after a reboot keeps the firewall alive across a restart.
Further reading at the source: the ssh-keygen(1) manual page, the OpenSSH release notes for what was removed when, the GitHub documentation for the route to your own repository, and a short German guide at heise.de.