# SSH Security Hardening Checklist for DomainIndia VPS
TL;DR
Your VPS faces hundreds of SSH brute-force attempts daily. This checklist hardens SSH against every common attack — key-only auth, non-root user, fail2ban, port hopping, 2FA, jump host — all tested on DomainIndia AlmaLinux / Ubuntu VPS.
## The attack surface
Scanners probe every public IP's port 22 continuously. Logs on a new VPS show 500-2000 failed SSH attempts per day within the first hour. Default configuration is a ticking bomb.
**The eight layers of SSH defense:**
1
Disable password auth entirely
2
Require SSH key authentication
4
Create a non-root sudo user
7
(Optional) Change SSH port
8
(Optional) Two-factor auth + jump host
## Layer 1 — Generate strong SSH key (on your laptop)
```bash
ssh-keygen -t ed25519 -C "
[email protected]" -f ~/.ssh/domainindia_vps -N 'strong-passphrase'
```
- `ed25519` is faster and more secure than RSA
- Passphrase protects the key if your laptop is stolen
Add to SSH agent:
```bash
ssh-add ~/.ssh/domainindia_vps
```
## Layer 2 — Add public key to VPS
```bash
# From laptop
ssh-copy-id -i ~/.ssh/domainindia_vps.pub root@your-vps-ip
# Or manually — paste contents of .pub file into:
# /root/.ssh/authorized_keys on VPS
```
Test before continuing:
```bash
ssh -i ~/.ssh/domainindia_vps root@your-vps-ip
# Should succeed without prompting for password
```
**Critical — don't proceed until key login works.**
## Layer 3 — Create non-root user
```bash
# On VPS as root
adduser admin # or your name
usermod -aG wheel admin # AlmaLinux (sudo group)
# or on Ubuntu:
usermod -aG sudo admin
# Copy authorized_keys
mkdir -p /home/admin/.ssh
cp /root/.ssh/authorized_keys /home/admin/.ssh/
chown -R admin:admin /home/admin/.ssh
chmod 700 /home/admin/.ssh
chmod 600 /home/admin/.ssh/authorized_keys
```
Test login as new user:
```bash
ssh -i ~/.ssh/domainindia_vps admin@your-vps-ip
sudo whoami # should return "root" after password prompt
```
Configure passwordless sudo (optional, for automation):
```bash
echo 'admin ALL=(ALL) NOPASSWD:ALL' | sudo tee /etc/sudoers.d/admin
```
## Layer 4 — Harden sshd config
Edit `/etc/ssh/sshd_config`:
```bash
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup
sudo vi /etc/ssh/sshd_config
```
Set these:
```
# Disable root login entirely
PermitRootLogin no
# Keys only
PasswordAuthentication no
PubkeyAuthentication yes
ChallengeResponseAuthentication no
UsePAM yes
# Limit to specific users
AllowUsers admin deploy
# Disable empty passwords
PermitEmptyPasswords no
# Limit protocol
Protocol 2
# Disable X11 forwarding if not needed
X11Forwarding no
# Reduce login grace time
LoginGraceTime 30
# Max auth tries per connection
MaxAuthTries 3
# Session timeout (idle disconnect)
ClientAliveInterval 300
ClientAliveCountMax 2
# Modern ciphers only
Ciphers
[email protected],
[email protected],aes256-ctr
KexAlgorithms curve25519-sha256,
[email protected],diffie-hellman-group16-sha512
MACs
[email protected],
[email protected]
```
Validate + reload:
```bash
sudo sshd -t # validate syntax — must return no output
sudo systemctl reload sshd
```
**Critical:** keep your current SSH session open. Open a NEW terminal to test. If locked out, use the old session to revert.
## Layer 5 — fail2ban (auto-ban brute-forcers)
```bash
# AlmaLinux
sudo dnf install -y epel-release
sudo dnf install -y fail2ban
# Ubuntu
sudo apt install -y fail2ban
```
Configure `/etc/fail2ban/jail.local`:
```ini
[DEFAULT]
bantime = 24h
findtime = 10m
maxretry = 3
ignoreip = 127.0.0.1/8 YOUR_HOME_IP/32
[sshd]
enabled = true
port = ssh
logpath = /var/log/secure
backend = systemd
```
Start:
```bash
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
# Currently banned: 2
# IP list: 45.123.x.x 62.45.x.x
```
## Layer 6 — Firewall
### firewalld (AlmaLinux / Rocky)
```bash
sudo systemctl enable --now firewalld
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
```
### UFW (Ubuntu)
```bash
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
```
### Restrict SSH to specific IP
If you have a static IP at office:
```bash
# firewalld
sudo firewall-cmd --permanent --remove-service=ssh
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="YOUR.OFFICE.IP/32" service name="ssh" accept'
sudo firewall-cmd --reload
# ufw
sudo ufw delete allow 22/tcp
sudo ufw allow from YOUR.OFFICE.IP to any port 22
```
## Layer 7 — Change SSH port (security through obscurity)
Scanners mostly hit port 22. Moving to 2222 or 22022 cuts 95% of noise (not a real defense, just log hygiene).
`/etc/ssh/sshd_config`:
```
Port 2222
# or add alongside 22 during transition:
# Port 22
# Port 2222
```
Open firewall for new port, reload sshd, test, then close port 22:
```bash
# AlmaLinux: SELinux may block new port
sudo semanage port -a -t ssh_port_t -p tcp 2222
sudo firewall-cmd --permanent --add-port=2222/tcp
sudo firewall-cmd --reload
sudo systemctl reload sshd
ssh -p 2222 admin@your-vps
```
Tell team. Update `~/.ssh/config` on laptops:
```
Host domainindia-vps
HostName your-vps-ip
User admin
Port 2222
IdentityFile ~/.ssh/domainindia_vps
```
Now `ssh domainindia-vps` just works.
## Layer 8 — Two-factor auth (Google Authenticator)
For extra paranoia:
```bash
sudo dnf install -y google-authenticator
# Ubuntu: sudo apt install libpam-google-authenticator
# As user (admin), run:
google-authenticator
# Scan QR with Authy/Google Auth on phone
# Save backup codes!
```
Edit `/etc/pam.d/sshd` — add at top:
```
auth required pam_google_authenticator.so
```
Edit `/etc/ssh/sshd_config`:
```
ChallengeResponseAuthentication yes
AuthenticationMethods publickey,keyboard-interactive
```
Reload sshd. Now login requires: SSH key AND 6-digit code.
## Layer 9 — Jump host / bastion
For multiple VPS, expose SSH only on one bastion host. All others only accept SSH from bastion's internal IP.
`~/.ssh/config`:
```
Host bastion
HostName bastion.yourcompany.com
User admin
Host internal-*
User admin
ProxyJump bastion
```
Usage:
```bash
ssh internal-db # routes via bastion automatically
```
Attack surface: only bastion is public.
## Monitoring
### Watch SSH logs
```bash
sudo journalctl -u sshd --follow
# or
sudo tail -f /var/log/secure
```
### Alert on successful root-like sudo
Set up a simple cron that monitors and emails:
```bash
# /etc/cron.hourly/ssh-monitor
#!/bin/bash
LAST_HOUR=$(date -d '1 hour ago' +'%Y-%m-%d %H')
SUDO_COUNT=$(journalctl --since "$LAST_HOUR:00:00" | grep -c 'sudo.*COMMAND')
if [ "$SUDO_COUNT" -gt 20 ]; then
mail -s "High sudo activity on $(hostname)"
[email protected] <
Q
Do I need fail2ban if I disabled password auth?
Yes — still useful. It blocks scanning bots wasting CPU on auth attempts even if they'd never succeed. Also catches misconfigured services exposing other ports.
Q
Should I change port from 22?
Security benefit is marginal (scanners scan other ports too). Main benefit: cleaner logs. If you want it, fine; don't rely on it as defense.
Q
SSH key compromised — what do I do?
1) Remove pub key from authorized_keys on every server. 2) Generate new key, add to authorized_keys. 3) Rotate any service account keys. 4) Review logs for unauthorized access since compromise.
Q
Can I disable SSH entirely?
On a VPS — no, you lose access. On a cPanel/DirectAdmin shared server — yes, use the panel. VPS admins always need some recovery path.
Harden your DomainIndia VPS from day one.
Get a VPS