Debian

Using SSH and SSH Keys on Debian 13

Run ssh-keygen on Debian 13 with no options and you get an Ed25519 key, not RSA. Ask for DSA and it answers unknown key type dsa, because OpenSSH 10.0 on trixie dropped it entirely. The rest of the client side has changed less, but three errors still trip almost everyone: a changed host key, a private key with loose permissions, and an agent that offers too many keys.

Original content from computingforgeeks.com - post 69819

This guide is about using SSH and SSH keys on Debian as a client, from creating a key and loading it into ssh-agent to naming hosts, copying files and hopping through a bastion. Setting up the server end is covered in our guide to installing and hardening the SSH server on Debian. We ran everything on Debian 13.7 with OpenSSH 10.0p2 in October 2026, with the client and a second account on the same VM.

Prerequisites

The client tools are part of every Debian install, so there is usually nothing to add. What you do need is a server to practise on, because a key is only useful once some account trusts it, and that first trust step needs a password or another way in. Check this list before starting.

  • Debian 13 with openssh-client (installed by default)
  • a server running sshd that you can log in to with a password once
  • rsync on both ends for the rsync example (sudo apt install rsync; a minimal Debian 13 install lacks it)

Check the client version with ssh -V; ours printed OpenSSH_10.0p2 Debian-7+deb13u4, OpenSSL 3.5.7 9 Jun 2026, even though apt policy openssh-client lists the package as 1:10.0p1-7+deb13u4, so don’t be thrown when the two disagree. If you are learning SSH properly rather than fixing one login, the books in our SSH book guide explain keys, agents and tunnels far better than scattered man pages. Set the server address and the remote account once, because most commands below use them.

export HOST_IP="10.0.1.50"
export ADMIN_USER="deploy"

Swap in your own server address and account name. The variables last for the current shell only, so export them again if you open a new terminal.

Generate an Ed25519 key pair

Ed25519 is the right key type for anything you’ll connect to from Debian 13. The keys are short, fast to verify and supported by every OpenSSH server from the last decade, and since OpenSSH 9.5 they’re what ssh-keygen makes when you don’t choose. Pass -t ed25519 anyway so the command means the same thing on older clients, and use the comment to say which machine the key lives on.

ssh-keygen -t ed25519 -C "josphat@laptop"

If ~/.ssh/id_ed25519 already exists, answer n at the overwrite prompt and pass -f ~/.ssh/id_ed25519_work (any new name) instead, because overwriting it cuts you off from every server that trusts the old key. Otherwise accept the default path and set a passphrase when prompted. The passphrase encrypts the private key on disk, so a stolen laptop or a leaked backup does not hand over your servers, and ssh-agent below means you type it once per session rather than per login. The output ends with the fingerprint and randomart.

Generating public/private ed25519 key pair.
Your identification has been saved in /home/josphat/.ssh/id_ed25519
Your public key has been saved in /home/josphat/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:VSWbtihIQO/ZIkpJopb2hrqR9ux1ptAof7i+Mg+tLGw josphat@laptop
The key's randomart image is:
+--[ED25519 256]--+
|   .o       o..  |
|     o     . +   |
|. .   o   . +    |
|.o.. o + . o .   |
|.+o . = S . .    |
|oo+.o. . .       |
|+=.*.o o         |
|=EO.o.+          |
|=+*X+.           |
+----[SHA256]-----+

The private key must be readable by you alone. ssh-keygen creates it as 600 and the .pub file as 644, and ls -l ~/.ssh/ should show exactly that. RSA still works if an old appliance demands it (-t rsa -b 4096), but DSA is gone: on Debian 13, ssh-keygen -t dsa fails with unknown key type dsa and exit code 255. If an old DSA key still lives in ~/.ssh, it no longer works at all, so replace it. If you created the key without a passphrase and want to add one later, our guide on how to change or remove an SSH key passphrase does it without making a new key.

Copy the public key to the server

ssh-copy-id is the tool for this. It logs in with your password once, appends the key to ~/.ssh/authorized_keys on the server, creates the directory with the right permissions if it is missing, and skips keys that are already there.

ssh-copy-id -i ~/.ssh/id_ed25519.pub "${ADMIN_USER}@${HOST_IP}"

After the password prompt, look for the count of added keys. This is the output of our run, where password logins were temporarily enabled on the server for the copy. The lab supplied the password non-interactively, so your terminal also shows the password prompt line:

/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/josphat/.ssh/id_ed25519.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys

Number of key(s) added: 1

Now try logging into the machine, with: "ssh -i /home/josphat/.ssh/id_ed25519 '[email protected]'"
and check to make sure that only the key(s) you wanted were added.

Running it a second time is harmless. Our rerun added nothing and exited 0 with a warning instead of a count (last two lines of its output):

/usr/bin/ssh-copy-id: WARNING: All keys were skipped because they already exist on the remote system.
		(if you think this is a mistake, you may want to use -f option)

When the server already has passwords switched off, ssh-copy-id cannot log in, so you add the key through an access that still works: another key, the console, or a cloud provider’s metadata. We used an existing key to append the new one, which is the same pipe you’d use on any machine that lacks ssh-copy-id:

cat ~/.ssh/id_ed25519.pub | ssh "${ADMIN_USER}@${HOST_IP}" 'cat >> ~/.ssh/authorized_keys'

Use >>, never >, or you wipe every other key on that account, including the one you just logged in with. Then test a fresh login that can only use the new key, because an agent still holding an older key would make any login look fine: ssh -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 "${ADMIN_USER}@${HOST_IP}" should ask for the key’s passphrase, not the account password.

Load the key into ssh-agent

The agent keeps the decrypted key in memory, so you type the passphrase once and every later ssh, scp and git call uses it. Desktop sessions often start an agent for you; on a server or a plain console, start one in the current shell.

eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519

ssh-add asks for the passphrase and confirms with Identity added: /home/josphat/.ssh/id_ed25519 (josphat@laptop). The agent only lives as long as that shell, so a new terminal needs the same two lines unless your desktop session runs one. List what the agent holds.

ssh-add -l

One line per key appears, with the fingerprint you saw at generation time, so you can tell keys apart even when two share a comment.

256 SHA256:VSWbtihIQO/ZIkpJopb2hrqR9ux1ptAof7i+Mg+tLGw josphat@laptop (ED25519)

Keep the agent lean. Every key in it is offered to every server in turn, and that causes the “Too many authentication failures” error further down. ssh-add -D empties it. ssh-add -t 3600 loads a key for one hour only, which is a sensible habit on a shared jump box.

Name your hosts in ~/.ssh/config

A host entry turns ssh -i ~/.ssh/id_ed25519 [email protected] into ssh web01, and every tool that uses SSH (scp, sftp, rsync, git, Ansible) picks the entry up.

vim ~/.ssh/config

Add a block per server, plus a Host * block at the end for settings every host shares. This is the file we tested with.

Host web01
    HostName 10.0.1.50
    User deploy
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes

Host *
    AddKeysToAgent yes
    ServerAliveInterval 60

IdentitiesOnly yes makes ssh offer only the named key to this host, whatever else sits in the agent. AddKeysToAgent yes loads a key into a running agent the first time you use it, which is why our ssh-add -l showed the key after a plain ssh web01 into a fresh agent. ServerAliveInterval 60 keeps idle sessions from being dropped by NAT routers. Order matters, because ssh takes the first value it finds: specific hosts go above Host *. Lock the file down with chmod 600 ~/.ssh/config, then check what ssh will actually use.

ssh -G web01 | grep -E '^(hostname|user|port|identitiesonly|serveraliveinterval|addkeystoagent) '

ssh -G prints the merged settings without connecting, so it is the quickest way to debug a config that does not do what you meant.

user deploy
hostname 10.0.1.50
port 22
identitiesonly yes
serveraliveinterval 60
addkeystoagent true

The key, the agent and the alias working together look like this on our VM, with a shorter ssh -G filter than the one above:

Ed25519 key, ssh-agent and ssh config alias on Debian 13

Once you have more than a handful of servers, our longer guide to managing SSH connections with the config file covers wildcards, per-environment includes and multiplexing.

Copy files with scp, sftp and rsync

All three ride on the same SSH connection, so the alias, key and agent above apply to each of them with no extra flags. Pick by job.

  • scp for a single file in either direction
  • sftp for browsing and fetching interactively
  • rsync for directories, repeat copies and resumable transfers

Push a file up, then pull one down. The remote path comes after the colon, and both commands exited 0 for us.

scp ~/report/q3.txt web01:/tmp/
scp web01:/etc/os-release /tmp/remote-os-release

Since OpenSSH 9.0, scp uses the SFTP protocol underneath, so it needs the server’s SFTP subsystem, which Debian enables by default. For whole directories, rsync only sends what changed and can resume. A trailing slash on the source copies its contents rather than the directory itself.

rsync -av ~/report/ web01:report/

Our second run sent 80 bytes and listed no files, because nothing had changed. The first run created the remote directory and sent the one file:

sending incremental file list
created directory report
./
q3.txt

sent 141 bytes  received 67 bytes  138.67 bytes/sec
total size is 11  speedup is 0.05

Our rsync usage guide goes through deletes, excludes and bandwidth limits, which are the options people reach for next.

Hop through a bastion with ProxyJump

When a server is only reachable through a jump host, -J chains the connection without copying your private key to the jump host. Your client opens an SSH session to the bastion, asks it to forward a TCP connection, and authenticates to the inner server end to end. The bastion never sees your key or your session contents, only an encrypted TCP stream.

ssh -J web01 web01 'echo via jump host'

We jumped through our only VM to itself, which proves the mechanics and printed via jump host with exit code 0. With a real bastion you’d write ssh -J bastion app01, where app01 (or the HostName you set for it) must resolve from the bastion, so use an IP address in HostName if the bastion can’t resolve it, or put ProxyJump bastion in the app01 block of ~/.ssh/config so plain ssh app01 takes the same route.

Fix the three client errors you will meet

We reproduced each of these on the Debian 13 client. They look alike at first glance because all three end in a failed login, but each has a different cause and a different fix, so read the first line of the message before reaching for a fix.

WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED

ssh refuses to connect when a server presents a different host key from the one in ~/.ssh/known_hosts, and exits with code 255. The message tells you the new fingerprint and the line number of the old entry.

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the ED25519 key sent by the remote host is
SHA256:PS5/09VzvifcZDFNP7Eukb/nGlpMRyp8OUeg04djM8Q.
Please contact your system administrator.
Add correct host key in /home/josphat/.ssh/known_hosts to get rid of this message.
Offending ED25519 key in /home/josphat/.ssh/known_hosts:2
  remove with:
  ssh-keygen -f '/home/josphat/.ssh/known_hosts' -R '10.0.1.50'
Host key for 10.0.1.50 has changed and you have requested strict checking.
Host key verification failed.

Confirm the change is expected before you delete anything. We produced this one by planting a stale entry for the server in known_hosts, so the server’s real key had not changed, and the check proved it: on the server’s console, sudo ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub printed 256 SHA256:PS5/09VzvifcZDFNP7Eukb/nGlpMRyp8OUeg04djM8Q root@web01 (ED25519), the same fingerprint as the warning. In real life a match like that means the server was rebuilt or reinstalled and your entry is old; a mismatch means stop and find out why. Only then remove the stale entry and reconnect.

ssh-keygen -R "${HOST_IP}"

It reports the line it removed and keeps a backup as known_hosts.old. On the next login, accept the key once (-o StrictHostKeyChecking=accept-new does that without a prompt for a host you have just verified).

WARNING: UNPROTECTED PRIVATE KEY FILE

A private key that any other user or group can read is ignored outright, and the login falls through to the next method or fails. We set a copy of our key to 644 and got this.

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for '/tmp/loosekey' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "/tmp/loosekey": bad permissions
[email protected]: Permission denied (publickey).

This usually happens after copying keys from a USB stick, a Windows share or a backup that did not keep permissions. chmod 600 on the private key fixes it. Run chmod 700 ~/.ssh as well if the directory was loosened too.

Received disconnect: Too many authentication failures

ssh offers every key in the agent, one per attempt, and the server counts each rejected key against MaxAuthTries. The default is 6, but our server runs the hardening from our Debian server guide, which lowers it to 3 (sudo sshd -T | grep ^maxauthtries printed maxauthtries 3), so with four unrelated keys loaded the server hung up before the right key was ever tried.

Received disconnect from 10.0.1.50 port 22:2: Too many authentication failures
Disconnected from 10.0.1.50 port 22

The fix is to name the key for that host and stop ssh offering the rest: ssh -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 "${ADMIN_USER}@${HOST_IP}" logged straight in, and the IdentitiesOnly yes line in the config block above makes that permanent. Don’t hammer the retry either: repeated failed connections can trigger sshd’s PerSourcePenalties (OpenSSH 9.8 and later), which refuses your address for a while. Our lab simply waited 20 seconds before the retry, and that was enough.

SSH client commands at a glance

Every command in this guide sits in the table below, in the order you’d normally need them. Bookmark it for the day you set up a new laptop.

TaskCommand
Create a keyssh-keygen -t ed25519 -C "user@host"
Show a key’s fingerprintssh-keygen -lf ~/.ssh/id_ed25519.pub
Install a key on a serverssh-copy-id -i ~/.ssh/id_ed25519.pub user@host
Start an agent and load a keyeval "$(ssh-agent -s)" then ssh-add
List or clear agent keysssh-add -l / ssh-add -D
Show effective config for a hostssh -G host
Copy a filescp file host:/path/
Sync a directoryrsync -av dir/ host:dir/
Go through a bastionssh -J bastion host
Forget a changed host keyssh-keygen -R host

For tunnels, port forwarding and the less common flags, keep our SSH commands cheat sheet open next to this table.

Keep reading

Configure Samba File Share on Debian 13 / 12 Debian Configure Samba File Share on Debian 13 / 12 Backup and Restore Linux Systems with Timeshift Debian Backup and Restore Linux Systems with Timeshift Install KVM on Debian 13 / Debian 12: Complete Guide KVM Install KVM on Debian 13 / Debian 12: Complete Guide Upgrade a Single Package on Ubuntu / Debian with apt Debian Upgrade a Single Package on Ubuntu / Debian with apt APT Cheat Sheet: apt and apt-get Commands for Ubuntu/Debian Cheat Sheets APT Cheat Sheet: apt and apt-get Commands for Ubuntu/Debian Install LevelDB on Ubuntu 24.04 / Debian 13 Databases Install LevelDB on Ubuntu 24.04 / Debian 13

Leave a Comment

Press ESC to close