SSH key-based authentication is the gold standard for remote server access. Unlike passwords, SSH keys cannot be guessed by brute-force attacks. An Ed25519 private key is a 256-bit secret that would take longer than the age of the universe to crack by exhaustive search. When combined with a key passphrase, you have two-factor authentication: something you have (the key file) and something you know (the passphrase). This guide covers the full setup on RHEL 9, including generating modern Ed25519 and RSA-4096 keys, hardening the SSH daemon configuration, managing multiple keys with ssh-agent, configuring the local ~/.ssh/config file, and restricting individual keys with authorized_keys options.
Prerequisites
- RHEL 9 server with root or sudo access
- SSH server (
openssh-server) installed and running:systemctl status sshd - A local workstation with an SSH client (Linux or macOS have one built in; Windows users can use Windows Terminal or PuTTY)
- Current password-based SSH access to the server — you need this to install your key before disabling password auth
Step 1 — Generate an SSH Key Pair on Your Local Machine
Run this command on your local workstation, not the server. Choose Ed25519 for all modern systems.
# Ed25519 (recommended)
ssh-keygen -t ed25519 -C "[email protected]" -f ~/.ssh/web01_ed25519
If you need to connect to legacy systems that do not support Ed25519, use RSA-4096:
# RSA-4096 (use only for compatibility with very old SSH servers)
ssh-keygen -t rsa -b 4096 -C "[email protected]" -f ~/.ssh/web01_rsa4096
You will be prompted for a passphrase. Always set one. It encrypts the private key on disk using AES-256, so the file is useless to an attacker without the passphrase.
This creates two files:
~/.ssh/web01_ed25519— your private key. Never share this, never copy it to a server.~/.ssh/web01_ed25519.pub— your public key. This is what you install on the server.
View your public key to confirm it was created correctly:
cat ~/.ssh/web01_ed25519.pub
A valid Ed25519 public key starts with ssh-ed25519 AAAA...
Step 2 — Copy the Public Key to the Server
The most reliable method is ssh-copy-id:
ssh-copy-id -i ~/.ssh/web01_ed25519.pub [email protected]
This appends your public key to ~/.ssh/authorized_keys on the server with correct permissions automatically. You will be prompted for the current password one last time.
If ssh-copy-id is not available (Windows without WSL), copy the key manually on the server:
# On the server, create the .ssh directory for the target user
mkdir -p /home/adminuser/.ssh
chmod 700 /home/adminuser/.ssh
chown adminuser:adminuser /home/adminuser/.ssh
# Paste your public key (replace the key value below)
echo "ssh-ed25519 AAAAC3NzaC1... adminuser@laptop" >> /home/adminuser/.ssh/authorized_keys
chmod 600 /home/adminuser/.ssh/authorized_keys
chown adminuser:adminuser /home/adminuser/.ssh/authorized_keys
Verify the SELinux context is correct (common cause of SSH failures on RHEL):
restorecon -Rv /home/adminuser/.ssh
Step 3 — Harden the SSH Daemon Configuration
Edit /etc/ssh/sshd_config to disable password authentication and enforce key-only login:
vi /etc/ssh/sshd_config
Set or update these directives:
# Disable password-based login entirely
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
# Enable public key authentication
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
# Disable root login over SSH
PermitRootLogin no
# Use modern secure ciphers, MACs, and key exchange algorithms
Ciphers [email protected],[email protected],[email protected]
MACs [email protected],[email protected]
KexAlgorithms curve25519-sha256,diffie-hellman-group16-sha512
# Reduce attack surface
X11Forwarding no
AllowTcpForwarding no
AllowAgentForwarding no
MaxAuthTries 3
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2
# Add a warning banner
Banner /etc/issue.net
Test the configuration file for syntax errors before restarting:
sshd -t
No output means the configuration is valid. Restart SSH:
systemctl restart sshd
Step 4 — Test Key-Based Login in a New Terminal
Open a new terminal session on your local machine — keep the existing session open as a safety net:
ssh -i ~/.ssh/web01_ed25519 [email protected]
You should be prompted for your key passphrase, then logged in without entering a password. Only close your original session after confirming the new session works.
Enable verbose mode to debug connection issues:
ssh -v -i ~/.ssh/web01_ed25519 [email protected]
Step 5 — Configure ssh-agent to Avoid Repeated Passphrase Entry
ssh-agent holds decrypted keys in memory for the duration of your session, so you enter the passphrase only once per login:
# Start the agent (usually auto-started by desktop environments)
eval "$(ssh-agent -s)"
# Add your key — you will be prompted for the passphrase once
ssh-add ~/.ssh/web01_ed25519
# Confirm the key is loaded
ssh-add -l
On macOS, keys are stored in the system Keychain automatically. On Linux, add the following to ~/.bash_profile for automatic key loading:
if [ -z "$SSH_AUTH_SOCK" ]; then
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/web01_ed25519
fi
Step 6 — Configure ~/.ssh/config for Multiple Servers
A local SSH config file eliminates typing -i, -p, and usernames on every connection:
Host web01
HostName 203.0.113.10
User adminuser
IdentityFile ~/.ssh/web01_ed25519
Port 22
ServerAliveInterval 60
Host db01
HostName 10.0.0.5
User dbadmin
IdentityFile ~/.ssh/db01_ed25519
# Route through web01 as a bastion host
ProxyJump web01
With this config, connecting is simply ssh web01. The ProxyJump for db01 routes the SSH connection through web01 as a bastion host — the standard pattern for accessing servers on private networks without exposing them to the internet.
Step 7 — Restrict Keys with authorized_keys Options
Individual keys in authorized_keys can carry per-key restrictions:
# Allow a key only from a specific IP address
from="203.0.113.0/24" ssh-ed25519 AAAAC3Nz... adminuser@laptop
# Restrict a key to running one specific command (for automated backups via rsync)
command="/usr/bin/rsync --server --sender -avz . /backup",no-pty,no-agent-forwarding,no-port-forwarding ssh-ed25519 AAAAC3Nz... backup@ci-server
# Combine: allow only from specific IP and only one command
from="10.0.0.100",command="/usr/bin/df -h" ssh-ed25519 AAAAC3Nz... monitoring@nagios
Step 8 — Rotate SSH Keys
Rotate keys annually or when a team member leaves:
# Generate a new key
ssh-keygen -t ed25519 -C "adminuser@web01-2026" -f ~/.ssh/web01_ed25519_2026
# Copy the new key to the server (both keys will work temporarily)
ssh-copy-id -i ~/.ssh/web01_ed25519_2026.pub [email protected]
# Verify the new key works
ssh -i ~/.ssh/web01_ed25519_2026 [email protected]
# Remove the old key from authorized_keys on the server
grep -v "OLD_KEY_COMMENT" ~/.ssh/authorized_keys > /tmp/ak_new
mv /tmp/ak_new ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
Verification
# Confirm password auth is disabled in running config
sshd -T | grep -E "passwordauthentication|pubkeyauthentication|permitrootlogin"
# Confirm SELinux context on .ssh directory
ls -laZ /home/adminuser/.ssh
# Check for failed login attempts
grep "Failed" /var/log/secure | tail -20
# Confirm which keys are installed
cat /home/adminuser/.ssh/authorized_keys
Troubleshooting
- Permission denied (publickey) — check
/var/log/secureon the server:tail -f /var/log/secure. Common causes: wrong permissions on~/.ssh(must be 700) orauthorized_keys(must be 600), incorrect SELinux context, or the public key was not copied correctly. - SELinux blocking authorized_keys — run
restorecon -Rv ~/.ssh. The correct context forauthorized_keysisssh_home_t. - Too many authentication failures — ssh-agent is offering too many keys. Use
ssh -o IdentitiesOnly=yes -i ~/.ssh/web01_ed25519 ...to force only the specified key.
Security Considerations
- Use unique key pairs per server — a single compromised key should not grant access to your entire fleet.
- Never place SSH private keys on the server itself. Keys on the server can be stolen if the server is compromised.
- For production environments at scale, consider certificate-based SSH (using
ssh-keygen -sand a CA) to avoid managingauthorized_keyson every server manually. - Revoke access immediately when staff leave: remove their public key from
authorized_keyson every server they had access to.
Conclusion
Your RHEL 9 server now accepts only SSH key authentication, with password login fully disabled, modern ciphers configured, and root login blocked. You have set up ssh-agent for convenience, a ~/.ssh/config for connecting to multiple servers, and you understand how to restrict individual keys with authorized_keys options. Key rotation is now part of your regular security maintenance cycle.
Next steps: How to Configure Fail2Ban to Protect SSH on RHEL 9, How to Set Up Two-Factor Authentication for SSH on RHEL 9, and How to Harden SSH on RHEL 9.