RE:NODE
ჰოსტინგი

VDS12 წუთის საკითხავი

SSH keys and hardening: sshd_config, agents, jump hosts

Generate an ed25519 key, install it safely, use an agent and a jump host, and harden sshd_config without ever locking yourself out of the server.

ეს სტატია ჯერ ინგლისურადაა. ვთარგმნით.

0 მკითხველი

Three commands cover the common case. ssh-keygen -t ed25519 -a 100 makes a key. ssh-copy-id deploy@203.0.113.10 installs it. PasswordAuthentication no in the server's configuration closes the door behind you. Do those three and your server can no longer be brute-forced, which removes the single largest category of attack against it, because a private key is not something anybody guesses.

Everything after that is about making keys pleasant to live with rather than making them stronger: one key that unlocks once a day instead of a passphrase typed forty times, a config file that turns a long command into a short name, a jump host that gets you to machines with no public address, and the handful of sshd_config settings that are worth changing as opposed to the forty that are copied between blog posts without explanation.

One rule underpins the whole post. Never change SSH configuration without a working session already open, and never close that session until a brand new connection has succeeded. Every lockout story begins with somebody skipping that.

Which key type, and why ed25519#

TypeUse itNotes
ed25519Yes, by defaultSmall, fast, no parameters to get wrong
ed25519-skFor a hardware tokenNeeds a FIDO2 key and OpenSSH 8.2 or newer
rsaOnly for compatibilityUse -b 4096. Still fine cryptographically
ecdsaNo reason to choose itWorks, but nothing recommends it over ed25519
dsaNeverObsolete and removed from current OpenSSH

An ed25519 public key is one short line, the private key is about 400 bytes, and there is no key size to choose because the curve fixes it. The -b flag is silently ignored for ed25519, which occasionally confuses people following an RSA tutorial.

One version detail explains a confusing failure. OpenSSH 8.8 disabled the ssh-rsa signature algorithm, which uses SHA-1, by default. That is the signature algorithm, not the key type: an RSA key still works against a modern server as long as both ends can negotiate rsa-sha2-256 or rsa-sha2-512. What it means in practice is that a very old client or a very old RSA setup can suddenly be rejected by an updated server, and the error blames the key rather than the algorithm. If you meet that, generate an ed25519 key rather than re-enabling SHA-1.

Making and installing a key properly#

Generate the key on the machine you sit at, never on the server. A private key that has been on a server is a private key that was on somebody else's computer.

bash
$ ssh-keygen -t ed25519 -a 100 -C "davit@laptop-2026"Generating public/private ed25519 key pair.Enter file in which to save the key (/home/davit/.ssh/id_ed25519):Enter passphrase (empty for no passphrase):

Three parts of that line are worth understanding rather than copying.

  • -a 100 sets the number of rounds used to derive the key that encrypts your private key file. It makes a stolen key file slower to brute-force and costs you a fraction of a second at unlock time.
  • -C is a comment. Put the human and the machine in it. In three years, authorized_keys on a shared server is a list of comments and nothing else, and "which of these five keys belongs to the person who left" is a question you will be asked.
  • Give it a passphrase. A key without one is a password file that anyone with read access to your laptop can copy and use forever. The agent, in the next section, means you type it once a day.

Install it with ssh-copy-id, which handles the permissions and the appending correctly:

bash
$ ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@203.0.113.10

If password login is already disabled and you need to add a second key, append it through the session you still have open. The permissions matter more than people expect, because sshd ignores an authorized_keys file it considers unsafe and gives the client no clue why:

PathModeOwner
/home/deploy755 or stricter, not group-writabledeploy
/home/deploy/.ssh700deploy
/home/deploy/.ssh/authorized_keys600deploy
~/.ssh/id_ed25519 (your machine)600you

When a key is refused and you cannot see why, ask the client and then the server:

bash
$ ssh -v deploy@203.0.113.10          # which keys were offered, what was accepted$ journalctl -u ssh -n 50             # on the server, with LogLevel VERBOSE

Roughly nine times in ten the answer is in the first output: the key you expected was never offered, because the agent had a different one, or IdentityFile pointed somewhere else.

The agent, so you type the passphrase once#

ssh-agent holds decrypted private keys in memory and signs challenges on request, so your passphrase is entered once per session rather than once per connection.

bash
$ eval "$(ssh-agent -s)"$ ssh-add ~/.ssh/id_ed25519$ ssh-add -l256 SHA256:5xM8n... davit@laptop-2026 (ED25519)

Most desktops start an agent for you. On macOS, ssh-add --apple-use-keychain stores the passphrase in the keychain. On Windows, the OpenSSH Authentication Agent is a service that needs setting to start automatically before ssh-add will work. Password managers and gpg-agent can act as agents too, which is convenient and does not change any of the rules below.

Two settings in ~/.ssh/config make it automatic:

~/.ssh/config
Host *    AddKeysToAgent yes    IdentitiesOnly yes    ServerAliveInterval 30    ServerAliveCountMax 3

IdentitiesOnly yes is the important one and it fixes a real failure. Without it, the client offers every key in the agent, in turn, and each offer counts as an authentication attempt. With five keys loaded and a server set to MaxAuthTries 3, you are disconnected with "Too many authentication failures" while holding the correct key. IdentitiesOnly tells the client to offer only the key named for that host.

Agent forwarding deserves a warning. ssh -A exposes your agent socket on the remote machine, and anyone with root there can use it to authenticate as you to anything your keys open. Do not use it habitually, and never towards a machine you do not administer. The right tool for reaching a second machine is ProxyJump, which keeps the authentication on your laptop.

The config file that saves the most time#

~/.ssh/config turns a long command into a word, and it is where the good habits live.

~/.ssh/config
Host vds    HostName 203.0.113.10    User deploy    IdentityFile ~/.ssh/id_ed25519Host db    HostName 10.0.0.20    User deploy    ProxyJump vdsHost *    AddKeysToAgent yes    IdentitiesOnly yes    ControlMaster auto    ControlPath ~/.ssh/cm-%r@%h:%p    ControlPersist 10m

ssh vds now works, and so do scp file vds:/tmp/ and rsync -a ./site/ vds:/var/www/, because they all read the same file.

ProxyJump is the part worth adopting today. ssh db opens a connection through vds to a machine on a private address, with the authentication happening on your laptop at each hop and no key or agent socket ever landing on the intermediate host. It replaces every reason people used to have for agent forwarding.

ControlMaster reuses one TCP connection for subsequent sessions to the same host, so the second and third ssh vds are instant and do not re-authenticate. It is a large quality-of-life improvement on a slow link and it is not supported by the Windows OpenSSH client. ControlPersist 10m keeps the shared connection alive for ten minutes after the last session closes.

ServerAliveInterval 30 stops a home router or a middlebox from silently dropping an idle session, which is the cause of most connections that freeze rather than closing.

Hardening sshd#

These go on the server. On Debian and Ubuntu, note that /etc/ssh/sshd_config starts with Include /etc/ssh/sshd_config.d/*.conf, and sshd uses the first value it obtains for a keyword, so a file in that directory beats the main config below it. Check what is already there before writing anything, as described in the first hour on a new VDS.

DirectiveSet toWhy
PasswordAuthenticationnoRemoves brute force as a category
KbdInteractiveAuthenticationnoThe other way a password can be accepted
PermitRootLoginnoOr prohibit-password if automation needs root
PubkeyAuthenticationyesThe default, worth stating
AuthenticationMethodspublickeyMakes the intent explicit and enforced
AllowUsers or AllowGroupsYour accountsNothing else can even try
MaxAuthTries3Fewer offers per connection
LoginGraceTime20Unauthenticated sockets do not linger
X11ForwardingnoNothing on a server needs it
PermitEmptyPasswordsnoDefault, and worth being certain of
ClientAliveInterval300Reaps dead sessions holding locks
LogLevelVERBOSELogs the fingerprint of the key that logged in

LogLevel VERBOSE is underrated. It records which key authenticated, by fingerprint, on every login. After an incident that single line is the difference between knowing whose credential was used and guessing. Logs worth keeping covers retaining it, and what to do when your server is hacked covers what you will want it for.

Match blocks apply settings to a subset, and they run to the end of the file or the next Match, so put them last:

/etc/ssh/sshd_config.d/10-hardening.conf
PasswordAuthentication noPermitRootLogin noAllowGroups ssh-usersMatch Group sftp-only    ChrootDirectory /srv/sftp/%u    ForceCommand internal-sftp    AllowTcpForwarding no

Always validate before restarting, and always from a session you already have:

bash
$ sshd -t && systemctl restart ssh

What is not worth doing: changing the port (a scan finds it in seconds, though it does quieten your logs), disabling every cipher a modern client would negotiate anyway, or pasting a forty-line sysctl block you cannot explain. What is worth adding is fail2ban if anything on the machine still accepts a password, and a firewall that allows SSH only from the addresses you use - the ufw guide has the syntax.

Restricting what a key may do#

A key does not have to be a general-purpose login. Options at the start of a line in authorized_keys apply to that key only, and they are how a backup job or a deploy hook gets exactly the access it needs.

~/.ssh/authorized_keys
restrict,from="203.0.113.0/24" ssh-ed25519 AAAAC3Nza... davit@laptop-2026restrict,command="/usr/local/bin/backup-receive" ssh-ed25519 AAAAC3Nza... backup@nas

The options worth knowing:

  • restrict (OpenSSH 7.2 and later) disables everything optional at once: port forwarding, agent forwarding, X11, a tty, and user environment variables. Start from it and add back only what is needed, with pty or port-forwarding.
  • from="..." limits the key to a source address or range. For a fixed office or a known server, this is a strong, free control.
  • command="..." forces that command no matter what the client asked for. The client's original request is available to the script in SSH_ORIGINAL_COMMAND, which is how restricted rsync or git access is built.
  • expiry-time="20270101" (OpenSSH 8.2 and later) stops the key working after a date. Useful for a contractor.

Review authorized_keys on every server twice a year. It is an append-only file in practice: keys go in when somebody joins and nobody remembers to take them out. That review is the same exercise as auditing panel subusers, and subusers and least privilege makes the case on the account side.

Host keys, known_hosts and trusting the server#

Authentication is mutual. The server proves who it is with its host key, and your client remembers it in ~/.ssh/known_hosts. The first connection is trust on first use: you are shown a fingerprint and asked to accept it, and almost everybody types yes without looking, which makes the whole scheme decorative.

Do it properly once per machine. On the server, from the provider's console:

bash
$ ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub256 SHA256:5xM8nQ... root@web-01 (ED25519)

Compare that string with what your client shows on the first connection. It takes ten seconds and it is the only moment the check is possible.

After a rebuild the host key changes and you get a loud warning about a possible attack. It is usually legitimate, and the fix is to forget the old entry:

bash
$ ssh-keygen -R 203.0.113.10

Only do that when you know why the key changed. A host key that changes on a machine you did not touch is exactly the event the warning exists for. Modern OpenSSH also rotates host keys for you: with UpdateHostKeys on, a server that adds a new host key type can hand it to clients that have already authenticated, so known_hosts stays current without warnings.

Two-factor, certificates, and when they are worth it#

Two more layers exist and most small setups do not need them.

Two-factor on top of a key. AuthenticationMethods publickey,keyboard-interactive requires a key and then a second factor through PAM, typically a TOTP code. It is genuinely stronger for a shared production machine. It also breaks unattended jobs, so combine it with a Match block that exempts the automation account, and be sure you understand what happens when the PAM module misbehaves before you rely on it.

SSH certificates. Instead of copying public keys to every server, a certificate authority signs short-lived user certificates, and servers trust the authority with TrustedUserCAKeys. Joining and removing people becomes a signing decision rather than an edit on thirty machines, and certificates expire on their own.

bash
$ ssh-keygen -s ca_key -I davit -n deploy -V +8w ~/.ssh/id_ed25519.pub

For one person with two servers this is overhead with no benefit. Past roughly five people or twenty machines it becomes the obviously correct answer, and the crossover is where authorized_keys edits start being forgotten.

Locked out: what actually saves you#

In order of how much you will wish you had it:

  1. The session you kept open. Undo the change, restart sshd, breathe.
  2. `sshd -t` before every restart. It catches typos, which is what most lockouts are.
  3. Your provider's console or rescue mode. A VDS with full root access has one; it is slow and it works. Find out where it is before you need it, not during.
  4. A second key from a second machine, installed while things worked. A phone with an SSH client counts.
  5. A second account in `AllowUsers` with its own key, used for nothing else.

A managed game, app or web server is a different situation entirely: there is no SSH daemon to lock yourself out of, file access is over SFTP with credentials issued per server, and the console lives in the panel. That is the trade VDS or a game panel describes - a VDS gives you every setting in this post, and the responsibility for all of them.

FAQ#

Should my key have a passphrase if only I use this laptop?

Yes. The passphrase protects the key file, not the connection, and the file is what gets copied when a laptop is stolen or when malware goes looking for ~/.ssh. With an agent you type it once per session, so the cost is close to zero and the benefit is that a stolen file is not immediately a stolen server.

Can I use the same key on several servers?

Yes, that is normal and fine: the private key never leaves your machine, and the public key is public by design. Use a different key per person, not per server. Separate keys per server only become useful when you want the option of revoking access to one machine without touching the others.

What if I lose my private key?

You lose access, and nothing else is at risk. Get in another way - a second key, the provider's console, a colleague with access - and append a new public key to authorized_keys, then remove the old line. This is why a break-glass key or a console you know how to reach is worth arranging in advance.

Is it safe to email or paste a public key?

Yes. The public key is meant to be published; it is in authorized_keys on every server you use. What must never leave your machine is the file without the .pub extension. If a private key is ever pasted anywhere, treat it as burnt and generate a new pair.

Does disabling password authentication lock out my other tools?

It locks out anything that authenticates with a password, including some file transfer clients configured that way. Point them at the key instead - every mainstream SFTP client supports keys - and check any automation before you make the change rather than after.

Do I still need fail2ban with keys only?

Not for SSH, strictly. Failed key attempts cannot succeed, so the benefit is log volume rather than security. It still earns its place in front of web logins, mail and anything else that accepts a password, and it costs very little to leave running.


კომენტარები

სრულიად ანონიმურად: ანგარიშის, ელფოსტის და cookie-ის გარეშე. ინახება მხოლოდ სახელი, ტექსტი და დრო - სხვა არაფერი. ბმულების რაოდენობა ლიმიტირებულია.

0/2000