Privacy Tools Guide

A default SSH installation accepts password logins, allows root access, and supports legacy ciphers that have known weaknesses. Every internet-facing server running stock SSH config is one brute-force attempt away from disaster.

This guide walks through locking down sshd_config to a minimal, defensible state — key-only authentication, no root login, restricted algorithms, and basic rate limiting.

Prerequisites

Do not lock yourself out. Before making changes, open a second terminal session and keep it connected until you verify the new config works.

Generate an SSH Key Pair

If you don’t have a key pair:

# Ed25519 is preferred — smaller, faster, more secure than RSA 2048
ssh-keygen -t ed25519 -C "yourname@host" -f ~/.ssh/id_ed25519

# Copy the public key to the server
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@your-server-ip

Verify you can log in with the key before disabling password authentication:

ssh -i ~/.ssh/id_ed25519 user@your-server-ip

Why Ed25519 over RSA? Ed25519 uses elliptic curve cryptography on Curve25519. The keys are 256 bits, produce 64-byte signatures, and are significantly faster to verify than RSA-2048. Ed25519 is also resistant to side-channel attacks that affect RSA implementations. If you must use RSA for compatibility with older systems, use RSA-4096 at minimum.

Back Up the Default Config

sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak

If anything goes wrong, restore it:

sudo cp /etc/ssh/sshd_config.bak /etc/ssh/sshd_config
sudo systemctl restart sshd

The Hardened sshd_config

Open the file:

sudo nano /etc/ssh/sshd_config

Apply these settings. Comment out or remove conflicting defaults:

# ============================================================
# Port and Protocol
# ============================================================
Port 2222                        # Change from default 22
AddressFamily inet               # IPv4 only; use 'any' if you need IPv6
ListenAddress 0.0.0.0

# ============================================================
# Authentication
# ============================================================
PermitRootLogin no               # Never allow direct root SSH
PasswordAuthentication no        # Keys only
ChallengeResponseAuthentication no
UsePAM no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
MaxAuthTries 3                   # Limit guessing attempts per connection
LoginGraceTime 30                # Seconds before unauthenticated session drops

# ============================================================
# User / Group Restrictions
# ============================================================
AllowUsers deploy admin          # Whitelist specific users only
# AllowGroups sshusers           # Alternative: allow by group

# ============================================================
# Session Hardening
# ============================================================
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no            # Set to 'yes' only if you use tunnels
PermitTunnel no
PrintLastLog yes
Banner /etc/issue.net            # Legal warning banner

# ============================================================
# Idle Timeout
# ============================================================
ClientAliveInterval 300          # Send keepalive every 5 minutes
ClientAliveCountMax 2            # Drop after 2 missed keepalives (10 min)

# ============================================================
# Cryptography — Restrict to Modern Algorithms
# ============================================================
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

# ============================================================
# Logging
# ============================================================
SyslogFacility AUTH
LogLevel VERBOSE                 # Logs key fingerprints; useful for audits

Save and test the configuration syntax:

sudo sshd -t

If there are no errors, restart SSH:

sudo systemctl restart sshd

Why Each Cryptographic Setting Matters

The cipher list deserves explanation. Legacy algorithms still present in many default OpenSSH installs include arcfour (RC4, broken), 3des-cbc (triple DES with CBC mode — vulnerable to SWEET32), and aes128-cbc (CBC mode susceptible to BEAST and Lucky13 attacks).

The etm suffix on MAC algorithms stands for Encrypt-Then-MAC, which is the correct ordering. Older hmac-sha2-512 without etm uses MAC-Then-Encrypt, which enables padding oracle attacks. Always prefer ETM variants.

The KexAlgorithms list removes diffie-hellman-group14-sha256 and all diffie-hellman-group1 or group14 with SHA-1. Group 14 uses 2048-bit DH which is borderline acceptable, but groups 16 (4096-bit) and 18 (8192-bit) with SHA-512 are definitively modern. The curve25519 options are preferred when both sides support them — they are faster and resist attacks that target traditional finite-field DH.

Verify from a New Terminal

Before closing your existing session, open a new one and test:

ssh -p 2222 -i ~/.ssh/id_ed25519 user@your-server-ip

If it connects, the hardened config is working. If not, restore the backup from your existing session.

Update Your Firewall

If you changed the port, update your firewall rules:

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

# firewalld
sudo firewall-cmd --permanent --add-port=2222/tcp
sudo firewall-cmd --permanent --remove-service=ssh
sudo firewall-cmd --reload

Create the banner file:

sudo nano /etc/issue.net

Add something like:

Authorized access only. All activity is logged and monitored.
Unauthorized access will be prosecuted.

The Banner directive in sshd_config already points to this file.

Install and Configure fail2ban

fail2ban blocks IPs after repeated failed login attempts:

# Debian/Ubuntu
sudo apt install fail2ban

# RHEL/Fedora
sudo dnf install fail2ban

Create a local jail configuration:

sudo nano /etc/fail2ban/jail.local
[DEFAULT]
bantime  = 1h
findtime = 10m
maxretry = 3

[sshd]
enabled  = true
port     = 2222
filter   = sshd
logpath  = /var/log/auth.log
maxretry = 3
bantime  = 24h

Enable and start fail2ban:

sudo systemctl enable --now fail2ban

Check active bans:

sudo fail2ban-client status sshd

fail2ban Tuning for High-Traffic Servers

On servers that receive many legitimate connections from dynamic IPs (CI/CD systems, monitoring agents), aggressive ban settings cause operational pain. Use ignoreip to whitelist known good addresses:

[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 203.0.113.0/24
bantime  = 24h
findtime = 5m
maxretry = 3

You can also use bantime.increment = true with bantime.factor = 2 to double ban duration on repeat offenders — a useful escalating deterrent for persistent scanners.

Restrict SSH Access by IP (Optional but Effective)

If you connect from a known static IP, restrict access at the firewall level:

# UFW — only allow SSH from your IP
sudo ufw allow from 203.0.113.10 to any port 2222 proto tcp
sudo ufw deny 2222/tcp
sudo ufw reload

For dynamic IPs, use a VPN or jump host instead.

Using SSH Certificates Instead of Authorized Keys

For environments with multiple servers and multiple users, per-user authorized_keys files become difficult to manage. SSH certificates offer a more scalable alternative.

Generate a Certificate Authority (CA) key:

ssh-keygen -t ed25519 -f ~/.ssh/ca_key -C "Internal SSH CA"

Sign a user’s public key:

ssh-keygen -s ~/.ssh/ca_key -I "mike@example.com" -n deploy,admin -V +30d ~/.ssh/id_ed25519.pub

The -V +30d flag makes the certificate expire in 30 days, enforcing regular rotation. The -n flag specifies which principals (usernames) the certificate is valid for.

On each server, trust the CA rather than individual keys:

# Add to sshd_config
TrustedUserCAKeys /etc/ssh/ca.pub

# Disable per-user authorized_keys if going certificate-only
AuthorizedKeysFile none

Copy the CA public key to all servers:

sudo cp ~/.ssh/ca_key.pub /etc/ssh/ca.pub

This approach means revoking access requires only removing the user from valid principals at certificate signing time — no need to touch individual server authorized_keys files.

Audit Current Connections

View active SSH sessions:

who
ss -tnp | grep :2222

Check recent authentication attempts:

sudo journalctl -u sshd --since "24 hours ago" | grep -E "(Failed|Accepted|Invalid)"

SSH Key Management

List authorized keys on the server:

cat ~/.ssh/authorized_keys

Remove a key by deleting its line. To audit key fingerprints:

ssh-keygen -lf ~/.ssh/authorized_keys

For servers with many users, consider AuthorizedKeysCommand to fetch keys from a central directory service rather than per-user files.

Periodic Config Audit

Run ssh-audit (a Python tool) monthly to check your server configuration against current recommendations:

pip install ssh-audit
ssh-audit localhost -p 2222

The tool outputs a color-coded report of algorithms, highlighting deprecated entries and suggesting replacements. It tests both server-side configuration and the actual negotiated algorithms — catching cases where sshd_config looks correct but system-level defaults override individual settings.

Hardening Checklist

Built by theluckystrike — More at zovo.one