Advanced SSH Hardening: Ed25519, 2FA and port knocking
Secure your SSH access beyond just changing the port. This guide covers Ed25519 keys (safer and faster than RSA), TOTP 2FA authentication, port knocking and advanced hardening best practices.
Introduction
SSH is the main entry point to your VPS. A password, even a strong one, is no longer sufficient in 2025: constant brute-force bots, credential leaks and growing compute power make any password authentication obsolete.
This guide goes beyond just "change the port and disable root": we build SSH authentication with:
- Ed25519 keys (modern algorithm)
- 2FA via TOTP (Google Authenticator)
- Port knocking (SSH port only opens after secret sequence)
- IP limitation + Match groups
Prerequisites
- Linux VPS with SSH installed
- Root or sudo access
- A second SSH session open to avoid locking yourself out during tests
- A TOTP app (Google Authenticator, Authy, Bitwarden, etc.)
Step 1: Backup current SSH config
Crucial: before touching anything, backup:
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
And open a second SSH session in parallel, leave it open. If you lock yourself out, you keep access.
Step 2: Generate an Ed25519 key
Ed25519 replaces RSA: safer, faster, shorter keys. On your local machine (not the VPS):
ssh-keygen -t ed25519 -C "[email protected]"
Press Enter for the default path, and set a passphrase (strengthens private key security).
Two files are created:
~/.ssh/id_ed25519(private key — never share)~/.ssh/id_ed25519.pub(public key — to copy to server)
Step 3: Copy public key to VPS
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@VPS_IP
Or manually:
cat ~/.ssh/id_ed25519.pub | ssh user@VPS_IP "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
Test key login:
ssh -i ~/.ssh/id_ed25519 user@VPS_IP
You should log in without password (just local key passphrase).
Step 4: Disable password authentication
⚠️ Only do this once the key is tested AND with a second session open.
sudo nano /etc/ssh/sshd_config
Modify or add:
# Disable password
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
# Disable root login
PermitRootLogin no
# Force protocol 2 (already default)
Protocol 2
# Modern algorithms only
HostKeyAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256
KexAlgorithms [email protected],curve25519-sha256,diffie-hellman-group16-sha512
Ciphers [email protected],[email protected],[email protected],aes256-ctr,aes192-ctr,aes128-ctr
MACs [email protected],[email protected],[email protected]
# Limits
MaxAuthTries 3
MaxSessions 5
LoginGraceTime 30
# Disable X11 (rarely needed on a server)
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no
# Logs
LogLevel VERBOSE
Test config:
sudo sshd -t
No error? Reload:
sudo systemctl reload sshd
Test from your second session or open a third to validate.
Step 5: Change SSH port (optional but recommended)
Port 22 receives 99% of brute-forces. Changing port kills 99% of noise (security by obscurity, doesn't replace hardening).
sudo nano /etc/ssh/sshd_config
Port 22022
Open the new port:
sudo ufw allow 22022/tcp
On Netrix (VeryCloud), also allow the custom port.
Reload:
sudo systemctl reload sshd
From now on connect with:
ssh -i ~/.ssh/id_ed25519 -p 22022 user@VPS_IP
To simplify, add in local ~/.ssh/config:
Host myvps
HostName VPS_IP
Port 22022
User user
IdentityFile ~/.ssh/id_ed25519
Connection: ssh myvps.
Step 6: Add TOTP 2FA
Installation:
sudo apt install -y libpam-google-authenticator
On the relevant user (not root):
google-authenticator
Answer:
- Time-based tokens: y
- Update
~/.google_authenticator: y - Disallow multiple uses: y
- Increase time window: n
- Rate limit: y
A QR code is displayed. Scan it with your TOTP app (Google Authenticator, Authy, Bitwarden).
Note the emergency backup codes displayed: if you lose your phone, they're your only way to reconnect.
Step 7: Configure PAM for SSH 2FA
sudo nano /etc/pam.d/sshd
At the top of the file (before @include common-auth):
auth required pam_google_authenticator.so
Then:
sudo nano /etc/ssh/sshd_config
Add/modify:
UsePAM yes
AuthenticationMethods publickey,keyboard-interactive
ChallengeResponseAuthentication yes
KbdInteractiveAuthentication yes
⚠️ AuthenticationMethods publickey,keyboard-interactive forces SSH key AND 2FA (both methods mandatory).
Reload:
sudo systemctl reload sshd
Test: connection now asks for key + TOTP code. Perfect.
Step 8: Port knocking (advanced)
Port knocking completely hides the SSH port: it's permanently closed and only opens for your IP after a "knock" sequence on predefined ports.
sudo apt install -y knockd
Configure:
sudo nano /etc/knockd.conf
[options]
UseSyslog
[openSSH]
sequence = 7000,8000,9000
seq_timeout = 5
command = /sbin/iptables -A INPUT -s %IP% -p tcp --dport 22022 -j ACCEPT
tcpflags = syn
[closeSSH]
sequence = 9000,8000,7000
seq_timeout = 5
command = /sbin/iptables -D INPUT -s %IP% -p tcp --dport 22022 -j ACCEPT
tcpflags = syn
Enable knockd:
sudo nano /etc/default/knockd
START_KNOCKD=1
sudo systemctl enable --now knockd
On firewall side, close the SSH port by default:
sudo ufw deny 22022/tcp
Now, to connect (from your local machine):
# Install knock client-side (Linux: apt install knockd, macOS: brew install knock)
# Knock
knock VPS_IP 7000 8000 9000
# Then SSH
ssh myvps
To close: knock VPS_IP 9000 8000 7000.
Step 9: Group and IP restrictions
Add to sshd_config:
# Only ssh-users group can connect
AllowGroups ssh-users
# Whitelist IP for a specific user
Match User admin
AllowUsers [email protected]
Create the group:
sudo groupadd ssh-users
sudo usermod -aG ssh-users your_user
Step 10: Limit attempts per IP with ufw
UFW can limit SSH connections (complementary anti brute-force to Fail2ban / CrowdSec):
sudo ufw limit 22022/tcp
Limits to 6 connections per 30 seconds per IP. Beyond, temporary block.
Step 11: Audit the config
sudo apt install -y ssh-audit
ssh-audit localhost -p 22022
The tool checks all negotiated algorithms and gives a grade. Goal: all green.
Troubleshooting
Locked out of VPS
If you kept a session open, restore:
sudo cp /etc/ssh/sshd_config.bak /etc/ssh/sshd_config
sudo systemctl reload sshd
Otherwise, use noVNC from VeryCloud customer area (KVM console doesn't go through SSH).
"Permission denied (publickey)"
Check:
ls -la ~/.ssh/
Expected permissions:
drwx------ ~/.ssh
-rw------- ~/.ssh/authorized_keys
-rw------- ~/.ssh/id_ed25519
-rw-r--r-- ~/.ssh/id_ed25519.pub
If issue:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
2FA blocks too often
If VPS clock is out of sync, TOTP code is invalid:
sudo apt install -y chrony
sudo systemctl enable --now chrony
Useful commands
# Test SSH config
sudo sshd -t
# Reload sshd
sudo systemctl reload sshd
# View active sessions
who
w
last -10
# View failed attempts
sudo journalctl -u ssh | grep -i "failed\|invalid"
# SSH audit
ssh-audit localhost -p 22022
# Temporarily disable a key
mv ~/.ssh/authorized_keys ~/.ssh/authorized_keys.disabled
# View enrolled TOTP peers
ls -la /home/*/.google_authenticator
Conclusion
Your SSH is now locked down across multiple layers:
- ✅ Ed25519 key (modern crypto)
- ✅ Password disabled
- ✅ Non-standard port
- ✅ Mandatory TOTP 2FA
- ✅ Port knocking (port closed except after sequence)
- ✅ Group/IP restrictions
- ✅ UFW rate limiting
Even if an attacker finds the private key, they still need 2FA. Even with everything, the port is hidden by knocking.
Going further:
- Combine with CrowdSec or Fail2ban (auto-ban malicious IPs)
- Use a dedicated bastion if you have multiple VPS
- Set up SSH certificate authorities for large-scale key rotation
Resources
- ssh-audit: https://github.com/jtesta/ssh-audit
- Mozilla SSH Guidelines: https://infosec.mozilla.org/guidelines/openssh
- VeryCloud guide — Basic SSH key configuration: https://verycloud.fr/docs/article/configure-sshkey-linux

















