OpenSSH is one of the first services you touch on a new Linux server. It gives you remote shell access, file copy, and tunneling. If SSH is weak, the rest of your security plan is weak. For entry-level technicians, the safest path is simple: use keys instead of passwords, harden the daemon in small steps, and test each change before you close your terminal. In production, this reduces brute-force noise, lowers account takeover risk, and keeps maintenance access predictable during incidents.
Start with keys: faster login and better account security
Password authentication is easy to start with, but it is also easy to attack. Bots scan public IP ranges and try common usernames all day. SSH keys do not remove all risk, but they remove password guessing from the normal attack path. Use a modern key type and protect the private key with a passphrase.
# On your admin workstation
ssh-keygen -t ed25519 -a 100 -f ~/.ssh/prod-admin-ed25519 -C "ops-admin@linuxprofessionals"
# Copy the public key to the server account
ssh-copy-id -i ~/.ssh/prod-admin-ed25519.pub adminuser@server01
# Test key login (do this before changing sshd_config)
ssh -i ~/.ssh/prod-admin-ed25519 adminuser@server01
-a 100 increases key-derivation work for passphrase guessing, which helps if your laptop is stolen. In teams, each admin should have a unique key and account mapping. Shared keys hide who logged in and make incident review harder.
Control what each key can do on the server
Most beginners stop at key login. A stronger setup also controls key scope. In ~/.ssh/authorized_keys, you can lock a key to source IP ranges, disable PTY access, or force one command. This is useful for backup jobs, monitoring pull scripts, and CI tasks.
# Example: backup key with restricted behavior
from="10.10.50.12",no-agent-forwarding,no-port-forwarding,no-pty \
command="/usr/local/bin/run-backup.sh" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... backup@runner
# Required permissions for SSH to trust key files
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
Production consequence: if this key leaks, an attacker cannot open a full shell and cannot tunnel internal ports. They only get the forced backup command from the allowed source address. That is a much smaller blast radius than a normal shell key.
Harden sshd_config with a safe rollout plan
Hardening is where lockouts happen. The safe method is to keep one active session open, apply one group of settings, validate with a second session, and only then continue. Do not disable passwords until key logins work for all required admin accounts.
# /etc/ssh/sshd_config
Protocol 2
PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
AuthenticationMethods publickey
MaxAuthTries 3
LoginGraceTime 30
AllowUsers adminuser opsuser
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding local
ClientAliveInterval 300
ClientAliveCountMax 2
# Validate config syntax before reload
sudo sshd -t
# Debian/Ubuntu service name is usually ssh
sudo systemctl reload ssh
# Fedora/RHEL service name is usually sshd
sudo systemctl reload sshd
AllowTcpForwarding local keeps local forwards available for admin workflows but blocks remote forwarding, which is often abused for covert pivoting. If your team needs remote forwarding for a known workflow, allow it only on bastion hosts and document that exception.
Changing the SSH port can reduce log spam, but it is not real hardening by itself. Keep firewall rules strict and use tools like fail2ban or upstream rate-limits where appropriate.
Use SSH tunneling for admin tasks without exposing extra ports
Tunneling lets you reach internal services without publishing them on public interfaces. This is useful for databases, web admin panels, and temporary debug access. Treat tunnels as privileged access paths and close them when done.
# Local forward: access remote PostgreSQL securely from your laptop
# Laptop port 15432 -> server01 localhost:5432
ssh -i ~/.ssh/prod-admin-ed25519 -L 127.0.0.1:15432:127.0.0.1:5432 adminuser@server01
# Remote forward: publish local service to remote host (use carefully)
# Remote port 18080 -> your workstation localhost:8080
ssh -i ~/.ssh/prod-admin-ed25519 -R 127.0.0.1:18080:127.0.0.1:8080 adminuser@server01
# Dynamic SOCKS proxy for controlled troubleshooting
ssh -i ~/.ssh/prod-admin-ed25519 -D 127.0.0.1:1080 adminuser@bastion01
Practical impact: local forwarding can replace opening a database port to the internet. That usually cuts risk and change-management work. Remote forwarding is more sensitive because it can expose your local service path back into the server side. Keep it disabled by default in sshd_config unless there is an approved use case.
Audit and troubleshoot: know where SSH failures are logged
When SSH access breaks, use logs first, not guesswork. Client debug output shows key negotiation and auth methods. Server logs show permission errors, denied users, and configuration problems.
# Client-side debug
ssh -vvv -i ~/.ssh/prod-admin-ed25519 adminuser@server01
# Server logs on systemd hosts
sudo journalctl -u ssh -n 100 --no-pager # Debian/Ubuntu common
sudo journalctl -u sshd -n 100 --no-pager # Fedora/RHEL common
# File-based logs when journal forwarding is configured
sudo tail -n 100 /var/log/auth.log # Debian/Ubuntu
sudo tail -n 100 /var/log/secure # Fedora/RHEL
A very common root cause is bad file ownership in home directories. If ~/.ssh or authorized_keys is too open, SSH may ignore keys and silently fall back to password attempts. Another common issue is SELinux context drift on RHEL and Fedora after manual file copy; if needed, restore contexts with restorecon -Rv ~/.ssh.
Compatibility notes for Debian 13.3, Ubuntu 24.04.3 LTS and 25.10, Fedora 43, RHEL 10.1, and RHEL 9.7
- Debian 13.3: OpenSSH server package is
openssh-server. Service is typicallyssh. Default settings are conservative; verifysshd_config.dincludes if your image uses drop-in files. - Ubuntu 24.04.3 LTS and Ubuntu 25.10: same package and similar service behavior as Debian. Cloud images often have key-based login ready, but hardening options still need review per environment.
- Fedora 43: service is usually
sshd. SELinux is enforcing by default in many installs, so permission and context checks are part of normal SSH troubleshooting. - RHEL 10.1 and RHEL 9.7: procedures are largely compatible. Teams running mixed 9.7 and 10.1 fleets can keep one hardening baseline, then add host-specific exceptions through config management.
# Quick distro-aware checks
ssh -V
sudo sshd -T | head -n 30
sudo systemctl status ssh 2>/dev/null || sudo systemctl status sshd
# Verify listening socket and bound addresses
sudo ss -tlpn | grep ':22'
Summary
For production SSH, keys are the baseline, not an optional improvement. Build unique keys per admin, restrict sensitive automation keys, and harden sshd_config with a staged test process so you do not lock yourself out. Use tunnels to reach internal services without opening public ports, and keep a logging workflow ready for quick incident response. This approach works cleanly across Debian 13.3, Ubuntu 24.04.3 LTS and 25.10, Fedora 43, and both RHEL 10.1 and RHEL 9.7 environments.