Harden SSH and require key-based login
Introduce per-user keys, validate a drop-in, test a second session, and only then disable password and root login.
Reduce remote-login attack surface without locking administrators out.
- Ubuntu Server 22.04 LTS, 24.04 LTS
- OpenSSH 8.9+, 9.x
- Recovery path Keep console access and the working SSH session open.
- Client key Create an Ed25519 key with a passphrase on the client.
- Administrator inventory Know every account that must retain access.
OneLiners never runs these steps or stores secrets. Review placeholders, versions, current state, and change-control requirements before using a command.
Full guide
What you will build
- A passphrase-protected Ed25519 administrator key that remains on the client and a matching public key installed for one named server account.
- An Ubuntu OpenSSH drop-in that requires public-key authentication, denies direct root login and disables interactive password paths.
- A lockout-resistant validation sequence using sshd syntax checks, a retained session, a second positive login and an explicit negative password test.
- A new SSH session authenticates with the intended key while the original administration session remains available for recovery.
- sshd -T reports public-key authentication enabled and password, keyboard-interactive and root login disabled.
- A client that deliberately disables public-key authentication receives Permission denied (publickey).
Architecture
How the parts fit together
The client proves possession of a private key; sshd matches its public half in the target account's authorized_keys file and applies the effective server policy assembled from the main configuration plus ordered drop-ins.
- The client and server establish an encrypted SSH transport and negotiate a host key.
- The client offers a public key; the server checks account ownership, permissions and authorized_keys.
- The client signs the exchange with the private key and sshd applies the effective authentication policy.
- Only after a separate key-authenticated session works is password authentication considered safely disabled.
Assumptions
- The server runs Ubuntu 22.04 or 24.04 with OpenSSH and includes /etc/ssh/sshd_config.d/*.conf from the main file.
- You have console access or an existing privileged SSH session that will remain open throughout validation.
- Every administrator who must retain access has a separately attributable public key installed before passwords are disabled.
- Host-key verification is already trusted. This guide does not tell users to bypass a changed-host-key warning.
Key concepts
- Private key
- The client-side credential used to create authentication signatures. It is never copied to the server.
- authorized_keys
- The account-owned file listing public keys that may authenticate as that account, optionally with per-key restrictions.
- Effective configuration
- The values sshd will actually use after includes, ordering, Match blocks and defaults are resolved; sshd -T displays it.
- Reload
- A configuration refresh that preserves existing sessions, unlike a disruptive service restart.
Before you copy
Values used in this guide
{{identity}}Comment that identifies the key owner and purpose.
Example: alice@ops-laptop-2026{{user}}Named non-root administrator account on the server.
Example: alice{{host}}Verified DNS name or address of the SSH server.
Example: admin01.example.comSecurity and production boundaries
- A key without a passphrase becomes immediately usable if the client file is stolen. Use a passphrase and a trusted agent or hardware-backed key where appropriate.
- Disabling passwords reduces online guessing but does not protect a stolen private key. Keep per-person keys, remove departed users promptly and review authorized_keys.
- Do not disable host-key checking. An unexpected host-key change is a separate security incident to investigate.
Stop before continuing if
- Stop if no console or retained privileged session is available.
- Stop if sudo sshd -t emits any error or sshd -T does not show the intended effective values.
- Stop if the new key-authenticated session fails; do not close the original session or disable another authentication path.
- Stop and investigate any unexpected SSH host-key warning instead of accepting it automatically.
command
Inspect effective settings
Record current port, authentication, include, and root-login policy.
Why this step matters
Capture the effective policy before changing it so you can distinguish an existing setting from the effect of the new drop-in and preserve a rollback reference.
What to understand
sshd -T resolves compiled defaults and included configuration instead of merely printing text found in one file.
The anchored grep keeps the review focused on the listener, root policy, public-key and password paths, and any explicit user allowlist.
System changes
- No persistent state changes; the command reads the effective daemon configuration.
Syntax explained
sshd -T- Tests and prints the effective OpenSSH server configuration.
grep -E- Uses an extended regular expression to select the named directives.
^- Anchors each alternative at the start of a configuration line.
sudo sshd -T | grep -E '^(port|permitrootlogin|passwordauthentication|pubkeyauthentication|allowusers)'port 22 permitrootlogin prohibit-password passwordauthentication yes
Checkpoint: Save the baseline
sudo sshd -T | grep -E '^(port|permitrootlogin|passwordauthentication|pubkeyauthentication|kbdinteractiveauthentication|allowusers)'Continue whenThe output records the current effective values and exits without an sshd error.
Stop whensshd cannot parse the existing configuration or the expected administrative port is unknown.
Security notes
- Effective policy can vary inside Match blocks; later validate the actual user and source context when such blocks exist.
Alternatives
- Use sshd -T -C with user, host and address when context-specific Match rules are present.
Stop conditions
- Repair an already invalid configuration before adding a hardening drop-in.
command
Generate a client key
Keep the private key on the client and protect it with a passphrase.
Why this step matters
Create a modern, attributable client credential before disabling passwords; without a proven replacement authentication path the server can be locked out.
What to understand
Ed25519 provides a compact modern key type. The key comment identifies owner and device but is not an authorization control.
The KDF rounds make offline passphrase guessing more expensive. Enter a strong unique passphrase when ssh-keygen prompts; the command intentionally does not place one on the command line.
System changes
- Creates a private key and .pub public key in the client user's ~/.ssh directory.
Syntax explained
-t ed25519- Selects the Ed25519 key type.
-a 64- Uses 64 KDF rounds when protecting the private-key file with its passphrase.
-C- Stores an operator-readable identity comment in the public key.
Values stay on this page and are never sent or saved.
ssh-keygen -t ed25519 -a 64 -C "{{identity}}"Your public key has been saved in ~/.ssh/id_ed25519.pub
Checkpoint: Confirm the public half and private permissions
ssh-keygen -lf ~/.ssh/id_ed25519.pub && stat -c '%a %n' ~/.ssh/id_ed25519Continue whenA SHA256 fingerprint is shown and the private key has mode 600 or stricter.
Stop whenThe private key was created without the intended passphrase, exposed, or has broad permissions.
If this step fails
ssh-keygen reports that id_ed25519 already exists.
Likely causeThe client already has a key using the default filename.
ssh-keygen -lf ~/.ssh/id_ed25519.publs -l ~/.ssh/id_ed25519*
ResolutionDo not overwrite it blindly. Reuse it only if policy permits, or choose a distinct filename and configure that identity explicitly.
Security notes
- The private key never belongs on the server or in a browser form.
Alternatives
- Use a hardware-backed ed25519-sk key where supported and operationally recoverable.
Stop conditions
- Rotate the pair if private material is printed or copied outside the trusted client.
command
Install only the public key
Append the public key and verify ownership and permissions.
Why this step matters
Install only the public half for the exact named account, then let ssh-copy-id preserve the account-specific authorized_keys layout.
What to understand
The client authenticates using the currently available method and appends the selected .pub line remotely.
The operation is additive. Review the complete authorized_keys file afterward so an unexpected pre-existing key is not silently ignored.
System changes
- Creates or appends to the target account's ~/.ssh/authorized_keys file on the server.
Syntax explained
ssh-copy-id- Authenticates to the target and installs selected public keys in authorized_keys.
-i ~/.ssh/id_ed25519.pub- Limits installation to the explicitly named public key file.
user@host- Selects the exact remote account and verified server endpoint.
Values stay on this page and are never sent or saved.
ssh-copy-id -i ~/.ssh/id_ed25519.pub {{user}}@{{host}}Number of key(s) added: 1
Checkpoint: Test the specific identity before hardening
ssh -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 {{user}}@{{host}} trueContinue whenThe remote command exits 0 using the new key.
Stop whenThe client still requires the account password for authentication or reports a host-key change.
If this step fails
ssh-copy-id reports success but the explicit-key test fails.
Likely causeWrong target account, unsafe ownership, a restricted key option, or sshd reading another authorized-keys path.
ssh -vvv -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 user@hostsudo journalctl -u ssh -n 50 --no-pager
ResolutionCorrect account ownership and the configured AuthorizedKeysFile path from the retained access session, then re-test.
Security notes
- Verify the server host key out of band before sending credentials to a new hostname.
Alternatives
- Provision public keys through approved configuration management for fleets.
Stop conditions
- Do not disable passwords until the explicit key-only command succeeds.
config
Create a hardening drop-in
Keep package defaults visible and limit authentication methods deliberately.
Why this step matters
A dedicated drop-in keeps local policy separate from package defaults and makes rollback a single-file operation while clearly removing interactive password paths.
What to understand
Ubuntu includes sshd_config.d near the beginning of its main configuration. OpenSSH generally uses the first obtained value, so review all earlier drop-ins and Match blocks.
PasswordAuthentication and KbdInteractiveAuthentication cover different prompts. Disabling both avoids leaving keyboard-interactive password authentication available.
System changes
- Creates /etc/ssh/sshd_config.d/50-oneliners-hardening.conf and changes authentication policy after reload.
Syntax explained
PubkeyAuthentication yes- Allows public-key user authentication.
PasswordAuthentication no- Disables the SSH password authentication method.
KbdInteractiveAuthentication no- Disables keyboard-interactive challenges, including many password or OTP PAM prompts.
PermitRootLogin no- Rejects all direct SSH logins as root.
MaxAuthTries 4- Limits authentication attempts permitted for one connection.
LoginGraceTime 30- Allows 30 seconds to authenticate before disconnecting.
X11Forwarding no- Disables forwarding of X11 display connections.
/etc/ssh/sshd_config.d/50-oneliners-hardening.confPubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no
MaxAuthTries 4
LoginGraceTime 30
X11Forwarding noHardening drop-in saved
Checkpoint: Review the file and include order
sudo grep -RnsE '^(Include|PasswordAuthentication|KbdInteractiveAuthentication|PermitRootLogin|PubkeyAuthentication)' /etc/ssh/sshd_config /etc/ssh/sshd_config.dContinue whenThe intended drop-in is visible and no earlier unexpected directive overrides the planned value.
Stop whenThe main file does not include the drop-in directory or a Match block changes the target account unexpectedly.
Security notes
- Disabling keyboard-interactive authentication also disables OTP flows that depend on it; choose AuthenticationMethods deliberately if MFA is required.
Alternatives
- Use an account-scoped Match block for a staged migration, with explicit tests for every affected account.
Stop conditions
- Do not reload until every administrator has a working key and recovery access is present.
verification
Validate syntax and effective values
Never reload sshd after a failed configuration test.
Why this step matters
A syntax check must precede every reload, and the effective-value check proves that include ordering produced the intended policy rather than merely accepting valid text.
What to understand
sshd -t is silent on success and non-zero on failure. Chaining with && prevents the second command from masking a parse error.
sshd -T prints normalized lowercase directives, which is why the grep pattern uses lowercase names.
System changes
- No persistent changes; both commands parse and resolve configuration.
Syntax explained
sshd -t- Checks server configuration syntax and required key files without starting a listener.
&&- Runs the effective-value inspection only if validation exits successfully.
sshd -T- Prints the effective normalized configuration.
sudo sshd -t && sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication|pubkeyauthentication)'permitrootlogin no passwordauthentication no pubkeyauthentication yes
Checkpoint: Require exact effective values
sudo sshd -t && sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication|kbdinteractiveauthentication|pubkeyauthentication)'Continue whenNo validation error; root and interactive password paths are no while public-key authentication is yes.
Stop whenAny error appears or any effective value differs.
Security notes
- Syntax validity does not prove account access; the second-session test remains mandatory.
Alternatives
- Add -C context values when Match rules make the generic output insufficient.
Stop conditions
- Never reload ssh after a failed or ambiguous validation.
command
Reload without dropping sessions
Reload rather than restart during a remote-only change.
Why this step matters
Reload applies the validated policy to new connections while preserving the original session that can perform rollback.
What to understand
Ubuntu names the OpenSSH server unit ssh. A reload asks the existing daemon to re-read configuration without terminating established sessions.
The chained service-state check gives immediate evidence that the daemon remains active, but it is not a substitute for a new login.
System changes
- Reloads the ssh service so subsequent connections use the new authentication policy.
Syntax explained
systemctl reload ssh- Requests a configuration reload without a full service stop/start.
systemctl is-active ssh- Returns active only when systemd sees the service running.
sudo systemctl reload ssh && systemctl is-active sshactive
Checkpoint: Keep recovery open
systemctl is-active ssh && sudo journalctl -u ssh -n 20 --no-pagerContinue whenThe service is active and recent logs show no configuration or listener error.
Stop whenThe service is failed, logs show an error, or the original session is lost.
Security notes
- Do not restart remotely when a reload can apply the policy with less disruption.
Alternatives
- Use console access to restart only when the installed service explicitly cannot reload.
Stop conditions
- Keep the original privileged session open until both positive and negative tests pass.
decision
Open a second key-authenticated session
If it fails, keep the first session open, restore the drop-in, and inspect logs.
Why this step matters
A truly separate connection proves the server accepts a fresh key authentication under the new policy; the retained session alone proves nothing about new logins.
What to understand
PreferredAuthentications asks the client to try publickey first. For strict diagnosis also specify IdentitiesOnly and the intended key path.
Run the test from the real administration network so source-address Match rules, firewall policy and DNS are represented.
System changes
- No persistent server changes; opens a new authenticated session.
Syntax explained
-o PreferredAuthentications=publickey- Prioritizes the public-key method for this client invocation.
user@host- Tests the exact named account on the intended server.
Values stay on this page and are never sent or saved.
ssh -o PreferredAuthentications=publickey {{user}}@{{host}}Welcome to Ubuntu 24.04 LTS
Checkpoint: Prove key-only administration
ssh -o PasswordAuthentication=no -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 {{user}}@{{host}} 'id && sudo -n true'Continue whenThe session authenticates by key, prints the intended account, and confirms the expected sudo policy.
Stop whenAuthentication fails, the wrong account opens, or expected privilege escalation is unavailable.
If this step fails
The new session returns Permission denied (publickey).
Likely causeThe key, account, permissions or effective Match context differs from the tested baseline.
Keep the first session openRun ssh -vvv with IdentitiesOnlyTail sudo journalctl -fu ssh from the retained session
ResolutionRestore interactive authentication if needed, correct the key path or context, validate and reload before repeating.
Security notes
- A successful login should still verify the expected username and privilege path.
Alternatives
- Use a second trusted administrator client if the primary client environment may hide agent behavior.
Stop conditions
- Do not close the original session until this independent login works repeatedly.
verification
Confirm passwords are refused
Explicitly request password authentication from a trusted client.
Why this step matters
Positive key login does not prove passwords were disabled, so deliberately remove public keys from one test connection and require the server to refuse it.
What to understand
PubkeyAuthentication=no prevents the client from succeeding with an agent key. PreferredAuthentications=password asks only for the method that should now be unavailable.
Permission denied (publickey) indicates the server advertises only the allowed public-key path. A password prompt or successful login is a failed hardening result.
System changes
- No persistent changes; performs a deliberately unsuccessful authentication attempt.
Syntax explained
-o PubkeyAuthentication=no- Disables client use of public keys for this one test.
-o PreferredAuthentications=password- Requests password authentication, which the server should refuse.
Values stay on this page and are never sent or saved.
ssh -o PubkeyAuthentication=no -o PreferredAuthentications=password {{user}}@{{host}}Permission denied (publickey).
Checkpoint: Require a controlled refusal
ssh -o PubkeyAuthentication=no -o PreferredAuthentications=password {{user}}@{{host}}Continue whenThe connection ends with Permission denied (publickey) and never offers a password prompt.
Stop whenA password prompt appears or authentication succeeds.
Security notes
- Run only a small number of controlled attempts so monitoring and lockout systems are not flooded.
Alternatives
- Inspect sshd -T -C as supporting evidence, but keep the network-level negative test.
Stop conditions
- Do not declare hardening complete while any unintended interactive method is still offered.
Finish line
Verification checklist
sudo sshd -tNo output and exit code 0.ssh -o PasswordAuthentication=no user@host trueExit code 0.systemctl is-active sshReturns active.Recovery guidance
Common problems and safe checks
The server still asks for a password after the public key was copied.
Likely causeThe client offered another key, authorized_keys ownership or modes are unsafe, or the account is reading a different home directory.
ssh -vvv -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 user@hostnamei -l ~user/.ssh/authorized_keyssudo journalctl -u ssh -n 50 --no-pager
ResolutionSelect the intended identity explicitly, correct owner-only write permissions and re-run the positive login before changing server policy.
sshd -t reports a bad configuration option or syntax error.
Likely causeA directive is misspelled, unavailable in the installed version, or placed incorrectly in the drop-in.
sudo sshd -tsudo sshd -Tsshd -V 2>&1 | head -n 1
ResolutionRestore or correct the drop-in from the retained session. Reload only after sshd -t exits successfully.
A second key login works but password login still succeeds.
Likely causeAn earlier drop-in sets the first effective value, a Match block changes the account, or keyboard-interactive authentication remains enabled.
sudo sshd -T | grep -E '^(passwordauthentication|kbdinteractiveauthentication|authenticationmethods)'sudo sshd -T -C user=user,host=host,addr=client_ip
ResolutionCorrect drop-in ordering and applicable Match rules, validate the context-specific effective configuration, then reload and repeat the negative test.
New sessions fail after reload while the original session remains connected.
Likely causeThe account key was not installed correctly, the policy is more restrictive than intended, or a network control changed at the same time.
Keep the original session opensudo journalctl -fu sshsudo sshd -T
ResolutionRestore or rename the hardening drop-in, validate, reload, and prove recovery login before investigating a new hardening attempt.
Reference
Frequently asked questions
Why keep the original session open after reload?
Existing SSH sessions normally survive a reload and provide the safest immediate path to inspect logs or restore the drop-in if new authentication fails.
Is PermitRootLogin prohibit-password enough?
It still permits direct root login by public key. This baseline uses PermitRootLogin no and expects administrators to log in as named accounts and elevate through sudo.
Should I delete all passwords?
Not as part of this change. First prove key-only SSH, preserve console recovery and manage local or emergency credentials under a separate reviewed account policy.
Recovery
Rollback
Restore authentication from the open session or console.
- Remove or rename the hardening drop-in.
- Run sudo sshd -t.
- Reload ssh only after validation passes.
- Confirm recovery login before closing the original session.
Evidence