SecurityNetworkingSelf-Hosting

SSH Hardening Guide: How to Secure Your Homelab Server Without Locking Yourself Out

Step-by-step SSH hardening guide for homelab servers. Learn to set up key authentication, configure sshd_config, set up fail2ban, and lock down your firewall - without getting locked out.

AU

Author

David Okonkwo

Disclosure: This article may contain affiliate links. If you purchase through these links, we may earn a commission at no additional cost to you. We only recommend products we have personally tested or thoroughly researched.

Key Takeaways

  • SSH is the most common attack vector on homelab servers - default configurations leave you exposed to brute force attacks within hours of going online.
  • Switching to Ed25519 key-based authentication and disabling password login is the single most impactful hardening step you can take.
  • Always keep an existing SSH session open while testing changes - this simple habit prevents the dreaded lockout scenario.
  • Fail2ban adds an automatic second line of defense by banning IPs after failed login attempts.
  • A complete hardening approach combines key auth, config lockdown, firewall rules, and fail2ban - no single step is enough on its own.

If you have ever spun up a new server - whether it is a Proxmox VM, a Raspberry Pi running Ubuntu, or a Docker host on a mini PC - you have probably SSHed into it with a password and thought, "I will set up proper security later." That later never comes, and meanwhile your SSH port is sitting there getting hammered by bots.

Here is the uncomfortable truth: automated SSH brute force attacks begin within minutes of a server going online. I have seen servers with default SSH configurations log over 1,000 failed login attempts in a single day. That is not a theoretical risk - it is what happens to every internet-facing SSH server right now.

This guide walks you through hardening SSH on your homelab server step by step. Every command works on Ubuntu, Debian, and most Linux distributions. I will explain what each setting does and why it matters, so you are not just copying commands without understanding them. And I will make sure you do not lock yourself out in the process.

Why SSH Hardening Matters for Your Homelab

Most homelab servers are accessible over the network. You SSH into them to manage Docker containers, configure Proxmox, update services, or troubleshoot problems. That makes SSH the front door to your entire homelab.

Here is what a default SSH configuration looks like on most Linux distributions:

  • Root login is allowed (sometimes with a password)
  • Password authentication is enabled
  • The default port (22) is open
  • No rate limiting or brute force protection
  • Weak cryptographic algorithms may be enabled

This configuration is essentially a welcome mat for attackers. Automated bots scan the internet for open SSH ports and try common usernames and passwords millions of times per day. Even if you think your password is strong, the volume of attempts makes it a matter of time.

The good news: hardening SSH takes about 15 minutes, and the steps are straightforward. You do not need to be a security expert - you just need to follow the checklist in order.

Before You Start: The Golden Rule

This is the most important section of the entire article. Read it before doing anything else.

The Golden Rule: Never close your current SSH session until you have verified the new configuration works.

Here is why: if you change your SSH configuration and something goes wrong, you need your existing session to fix it. If you close that session and try to reconnect with the broken config, you are locked out.

Here is the safe workflow:

  1. Open your current SSH session
  2. Make your configuration changes
  3. Open a second, new SSH session in a separate terminal window
  4. Test the new connection in the second session
  5. If it works, close the old session
  6. If it does not work, use the old session to fix the problem

This one habit will save you from the most common SSH hardening disaster. I have locked myself out more times than I care to admit, and every single time it was because I closed the session before testing.

If you do get locked out, skip to the Locked Out Recovery section at the end of this article. It covers your options depending on how your server is hosted.

Step 1: Generate SSH Keys (Ed25519)

The first step is replacing password authentication with key-based authentication. An SSH key pair is like a digital lock and key - your server has the lock (public key) and you have the key (private key). Only someone with the private key can log in.

We will use Ed25519, which is faster and more secure than the older RSA algorithm. Unless you have a specific reason to use RSA, Ed25519 is the right choice in 2026.

Generate your key pair on your local machine (not the server):

ssh-keygen -t ed25519 -C "your-email@example.com"

When prompted:

  • File location: Press Enter for the default (~/.ssh/id_ed25519)
  • Passphrase: Enter a strong passphrase (this encrypts your private key - use it)

The passphrase protects your key if someone gets access to your machine. It is an extra layer of security on top of the key itself. I know it adds one more password to remember, but it is worth it.

Why Ed25519 over RSA?

Ed25519 keys are 32 bytes compared to RSA's typical 2048+ byte keys. They are faster to generate, faster to verify, and cryptographically stronger at a smaller size. OpenSSH has supported Ed25519 since version 6.5 (2014), so there is no compatibility concern on any modern system.

If you need to check what key type you already have:

ssh-keygen -l -f ~/.ssh/id_ed25519

or for RSA keys:

ssh-keygen -l -f ~/.ssh/id_rsa

Step 2: Copy Your Key to the Server

Now you need to get your public key onto the server. The easiest way is ssh-copy-id:

ssh-copy-id -i ~/.ssh/id_ed25519.pub username@server-ip

Replace username with your actual username on the server and server-ip with the server's IP address.

You will be prompted for your password one last time - after this, you will log in with your key instead.

Verify it works:

ssh username@server-ip

You should be prompted for your key passphrase (not your password). If it works, you are ready to move on.

If ssh-copy-id is not available (some systems do not have it), you can do it manually:

cat ~/.ssh/id_ed25519.pub | ssh username@server-ip "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"

Then set the correct permissions on the server:

chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

Incorrect permissions on ~/.ssh or authorized_keys are a common cause of key authentication failing silently. SSH is picky about these - the .ssh directory must be 700 and authorized_keys must be 600.

Step 3: Edit sshd_config

Now we harden the SSH server configuration. Open the config file on your server:

sudo nano /etc/ssh/sshd_config

Here are the settings to change. I will explain each one:

# Disable root login - always log in as a regular user
PermitRootLogin no

# Only allow specific users (replace with your actual username)
AllowUsers yourusername

# Disable password authentication (key-only)
PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM no

# Use Ed25519 host key
HostKey /etc/ssh/ssh_host_ed25519_key

# Disable weak key exchange algorithms
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512

# Disable weak ciphers
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com

# Disable weak MACs
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com

# Set login grace time and max attempts
LoginGraceTime 30
MaxAuthTries 3

# Set idle timeout (in seconds, 300 = 5 minutes)
ClientAliveInterval 300
ClientAliveCountMax 2

# Disable X11 forwarding (not needed for servers)
X11Forwarding no

# Disable TCP forwarding unless you specifically need it
AllowTcpForwarding no

# Disable agent forwarding by default
AllowAgentForwarding no

# Logging
LogLevel VERBOSE

What each setting does:

  • PermitRootLogin no - Forces you to log in as a regular user, then use sudo. This is a fundamental security practice. Even if an attacker has your key, they cannot directly access root.

  • AllowUsers yourusername - Whitelists only your user account. Even if other accounts exist on the system, they cannot SSH in. This is a powerful restriction that many guides skip.

  • PasswordAuthentication no - The big one. After this change, only key-based login works. Make sure your keys are set up before enabling this.

  • LoginGraceTime 30 - Gives the client 30 seconds to authenticate before disconnecting. The default is 120 seconds, which gives attackers more time to try credentials.

  • MaxAuthTries 3 - Limits each connection to 3 authentication attempts. Combined with fail2ban (Step 5), this creates a strong defense against brute force.

  • LogLevel VERBOSE - Logs more detail about connections, including key fingerprints. This is invaluable for troubleshooting and monitoring.

Important: Make your changes in the file rather than appending to the end. If you add duplicate directives, SSH uses the last one, which can lead to confusing behavior.

After editing, validate the config:

sudo sshd -t

This checks for syntax errors without restarting the service. If it returns nothing, the config is valid. If there is an error, it will tell you the line number.

Test your changes (remember the Golden Rule - keep your existing session open):

Open a new terminal and try to connect:

ssh username@server-ip

If it works, restart SSH to apply changes:

sudo systemctl restart sshd

If it does not work, use your existing session to fix the problem. Do not close it.

Step 4: Change the Default SSH Port (Optional)

Changing SSH from port 22 to something else (like 2222 or 443) reduces the number of automated scan attempts. It is not true security - security through obscurity is not a substitute for proper hardening - but it does reduce log noise and bot traffic.

My take: In a homelab, this is worth doing. The downside is minimal (you type one extra character when connecting), and the reduction in automated probes is noticeable. For a production server with proper key auth and fail2ban, it matters less.

To change the port, edit /etc/ssh/sshd_config:

Port 2222

Then restart SSH:

sudo systemctl restart sshd

Test the new port before closing your current session:

ssh -p 2222 username@server-ip

Update your client-side SSH config to use the new port by default (see Step 7 below).

If you are using UFW, do not forget to allow the new port:

sudo ufw allow 2222/tcp
sudo ufw delete allow 22/tcp
sudo ufw reload

Step 5: Set Up Fail2ban

Fail2ban monitors SSH log files and bans IPs that show malicious behavior (too many failed login attempts). It is the standard brute force protection for SSH and should be on every homelab server.

Install fail2ban:

sudo apt update
sudo apt install fail2ban -y

Create a local config file (never edit jail.conf directly - it gets overwritten on updates):

sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

Edit the local config:

sudo nano /etc/fail2ban/jail.local

Find and modify these settings:

[DEFAULT]
# Ban for 1 hour
bantime = 3600

# Detection window of 10 minutes
findtime = 600

# Ban after 3 failed attempts
maxretry = 3

# Use systemd (for modern Ubuntu/Debian)
backend = systemd

[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600

For extra protection, add a persistent jail for repeat offenders:

[sshd-ddos]
enabled = true
port = ssh
filter = sshd-ddos
logpath = /var/log/auth.log
maxretry = 6
bantime = 86400

This bans IPs for 24 hours if they trigger 6 attempts against the SSH daemon.

Start and enable fail2ban:

sudo systemctl enable fail2ban
sudo systemctl start fail2ban

Check its status:

sudo fail2ban-client status sshd

You will see current banned IPs, total bans, and the list of recently banned IPs.

Manually unban an IP (if you accidentally lock yourself out):

sudo fail2ban-client set sshd unbanip 192.168.1.100

Log rotation: Fail2ban does not rotate its own logs by default. On Ubuntu, the default logrotate configuration handles /var/log/fail2ban.log, but verify it exists:

cat /etc/logrotate.d/fail2ban

If it is missing, fail2ban logs will grow unbounded, which is a problem on small servers.

Step 6: Configure the Firewall (UFW)

A firewall restricts which ports are open on your server. Even with SSH hardened, you should only allow the ports you actually use.

Install UFW (if not already installed):

sudo apt install ufw -y

Set default policies:

sudo ufw default deny incoming
sudo ufw default allow outgoing

Allow SSH (use your custom port if you changed it):

sudo ufw allow 2222/tcp comment "SSH"

Allow other services you need:

# Example: HTTP/HTTPS for web services
sudo ufw allow 80/tcp comment "HTTP"
sudo ufw allow 443/tcp comment "HTTPS"

# Example: Docker (if running containers that need port access)
# Be careful with Docker and UFW - Docker bypasses UFW by default

Docker and UFW caveat: Docker manages its own iptables rules and bypasses UFW by default. This is a well-known issue. If you are running Docker services, you need to configure Docker to respect UFW rules. The standard approach is to add iptables: false to /etc/docker/daemon.json and manage Docker ports through UFW directly. See our Docker networking deep dive for a full explanation of how Docker interacts with host networking.

Enable the firewall:

sudo ufw enable

Check the status:

sudo ufw status verbose

Step 7: Client-Side SSH Configuration

Hardening is not just about the server. Your SSH client should also be configured securely. Create or edit ~/.ssh/config on your local machine:

Host homelab-*
    User yourusername
    Port 2222
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes
    ServerAliveInterval 60
    ServerAliveCountMax 3

Host homelab-proxmox
    HostName 192.168.1.100

Host homelab-docker
    HostName 192.168.1.101

Host homelab-pi
    HostName 192.168.1.102

With this config, you can simply type ssh homelab-proxmox and SSH will use the correct user, port, key, and hostname automatically.

Key settings explained:

  • IdentitiesOnly yes - Forces SSH to only use the specified key file, preventing it from trying other keys in your agent. This avoids leaking unnecessary key material.

  • ServerAliveInterval 60 - Sends a keepalive message every 60 seconds to prevent idle disconnections.

  • ServerAliveCountMax 3 - Disconnects after 3 missed keepalives (3 minutes of no response).

  • IdentityFile - Explicitly specifies which key to use, rather than letting SSH try all keys in your agent.

Step 8: Monitoring and Auditing

Hardening is not a one-time task. You need to know what is happening on your server.

Check SSH authentication logs:

# View recent successful logins
sudo journalctl -u sshd --since "1 hour ago" | grep "Accepted"

# View recent failed attempts
sudo journalctl -u sshd --since "1 hour ago" | grep "Failed"

# Count failed attempts today
sudo journalctl -u sshd --since "today" | grep -c "Failed"

Run a quick SSH audit:

If you have ssh-audit installed, you can check your SSH server's cryptographic strength:

pip install ssh-audit
ssh-audit localhost

This tool checks which key exchange algorithms, ciphers, MACs, and host keys your server supports and flags any that are weak or deprecated.

Check for unusual login patterns:

# List all successful SSH logins in the last 7 days
sudo journalctl -u sshd --since "7 days ago" | grep "Accepted" | awk '{print $1, $2, $3, $9, $11}'

Look for logins at unusual times or from unexpected IP addresses. In a homelab, all SSH logins should come from your local network (192.168.x.x or 10.x.x.x). If you see external IPs, something is wrong.

Locked Out Recovery

Despite best efforts, lockouts happen. Here are your options depending on your setup:

Proxmox VM: Use the Proxmox web console (noVNC) to access the VM directly. Edit /etc/ssh/sshd_config, fix the issue, and restart SSH.

Physical server or Raspberry Pi: Connect a monitor and keyboard directly, or use a serial console if your board supports it.

Cloud VM (Hetzner, DigitalOcean, etc.): Most providers offer a web console or rescue mode in their control panel. Use it to fix your SSH config.

Docker container: If your SSH server is in a Docker container, you can exec into it:

docker exec -it container_name bash

Then fix the configuration.

Fail2ban lockout: If fail2ban banned your IP:

sudo fail2ban-client set sshd unbanip YOUR_IP_ADDRESS

Run this from the Proxmox console, direct keyboard access, or any other available access method.

The nuclear option - restore from backup:

If nothing else works and you have a backup of the original sshd_config, you can restore it from a rescue environment. This is why having a backup strategy matters even for configuration files.

Common Mistakes to Avoid

  1. Closing the testing session too early. This is the number one cause of lockouts. Always test in a new terminal first.

  2. Disabling password auth before copying your key. Verify key authentication works with ssh username@server-ip before changing PasswordAuthentication to no.

  3. Not checking permissions. SSH is strict about file permissions. ~/.ssh must be 700, authorized_keys must be 600, and sshd_config must be 644 (readable by root only for sensitive parts).

  4. Editing sshd_config incorrectly. Duplicate directives cause unpredictable behavior. Always check for existing settings before adding new ones, and use sshd -t to validate before restarting.

  5. Forgetting to restart SSH. Changes to sshd_config do not take effect until you restart the SSH daemon. Always run sudo systemctl restart sshd after editing.

  6. Not updating fail2ban regularly. Fail2ban needs updated filters to recognize new attack patterns. Run sudo apt update && sudo apt upgrade periodically.

  7. Using the same SSH key for everything. Create separate keys for different servers or purposes. If one key is compromised, the damage is limited.

Quick Reference: Complete Hardened sshd_config

Here is a consolidated configuration you can use as a starting point:

# /etc/ssh/sshd_config - Hardened for Homelab

Port 2222
ListenAddress 0.0.0.0

# Authentication
PermitRootLogin no
AllowUsers yourusername
PubkeyAuthentication yes
PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM no
AuthenticationMethods publickey

# Security
LoginGraceTime 30
MaxAuthTries 3
MaxSessions 5

# Cryptography
HostKey /etc/ssh/ssh_host_ed25519_key
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com

# Session
ClientAliveInterval 300
ClientAliveCountMax 2
X11Forwarding no
AllowTcpForwarding no
AllowAgentForwarding no

# Logging
LogLevel VERBOSE

Copy this, replace yourusername with your actual username, and adjust the port if needed. Then validate with sshd -t before restarting.

What to Learn Next

SSH hardening is one piece of the homelab security puzzle. Once you have this in place, consider:

  • Authentication with Authelia - Add single sign-on and two-factor authentication to your homelab services. See our Authelia setup guide for a complete walkthrough.

  • Fail2ban configuration - We covered the basics here, but there is more to learn about custom jails and email alerts. Our complete fail2ban guide goes deeper.

  • VPN before SSH - Instead of exposing SSH to your network, use WireGuard or Tailscale to access it only through an encrypted tunnel. This is the approach I use for my own homelab.

  • Homelab security checklist - SSH is just the beginning. Our homelab security best practices covers firewalls, updates, user management, and more.

  • Docker container security - If you run Docker services, check our guide on Docker security hardening for container-specific hardening.

Frequently Asked Questions

Is changing the SSH port security through obscurity?

Yes, technically. Changing from port 22 does not make your server any more secure against a targeted attack. However, it significantly reduces automated scan traffic and log noise. In a homelab, where the benefit is high and the cost is typing one extra character, it is worth doing. Just do not rely on it as your only security measure.

Should I use Ed25519 or RSA for SSH keys?

Ed25519 in almost every case. It is faster, cryptographically stronger, and has been supported by OpenSSH since 2014. The only reason to use RSA is if you need compatibility with very old systems (pre-2014) or specific hardware tokens that only support RSA. For a modern homelab, Ed25519 is the default choice.

Do I need 2FA for SSH in my homelab?

For most homelabs, key-based authentication with a passphrase provides sufficient security. If your homelab is accessible from the internet (not just your local network), consider adding TOTP-based two-factor authentication using Google Authenticator or a FIDO2 security key. For local-only access behind a firewall, the extra step may not be worth the convenience trade-off.

Can I lock myself out when hardening SSH?

Yes, but it is preventable. The two most common causes are: (1) disabling password authentication before verifying your SSH key works, and (2) introducing a syntax error in sshd_config. Follow the Golden Rule: keep your existing session open and test changes in a new terminal. If you do get locked out, see the [Locked Out Recovery](#locked-out-recovery) section above.

What is fail2ban and do I need it?

Fail2ban is a service that monitors log files and automatically bans IPs that show malicious behavior, such as repeated failed login attempts. Yes, you need it. Even with key-based authentication, fail2ban protects against denial-of-service attempts, protects against zero-day vulnerabilities, and reduces log noise from automated bots. Install it on every server with SSH exposed.