The Problem: Every Server Is Under Attack
If your server has a public IP address, it is being attacked right now. This is not hyperbole. Within hours of deploying a new server with SSH on port 22, automated scanners will find it and begin trying username/password combinations. The same applies to any publicly accessible login form — WordPress admin panels, FTP servers, database interfaces, and API endpoints.
The attackers are not humans typing passwords. They are botnets running automated tools across the entire IPv4 address space. Tools like Masscan can scan all 4.3 billion IPv4 addresses in under five minutes. Once a target with an open service is identified, specialized brute-force tools like Hydra, Medusa, or custom scripts begin systematic credential testing. The wordlists they use combine common defaults (root/admin, admin/password) with billions of credentials leaked from data breaches.
The consequence of a successful brute force attack depends on what service was compromised. An SSH breach gives the attacker full shell access, which typically leads to cryptocurrency mining, botnet recruitment, data theft, or ransomware deployment. A WordPress breach leads to SEO spam injection, malware distribution via the site, or use as a phishing host. A database breach means direct access to sensitive data.
SSH Protection
SSH is the most targeted service on the internet. Protecting it properly is the single most impactful thing you can do for server security.
Disable password authentication entirely
The most effective measure. If password authentication is off, brute force becomes impossible regardless of attack volume.
# Generate SSH key pair (on your local machine) ssh-keygen -t ed25519 -C "your-email@example.com" # Copy public key to server ssh-copy-id -i ~/.ssh/id_ed25519.pub user@your-server # Verify you can log in with the key ssh -i ~/.ssh/id_ed25519 user@your-server # THEN disable password authentication on the server # Edit /etc/ssh/sshd_config: PasswordAuthentication no PubkeyAuthentication yes PermitRootLogin prohibit-password ChallengeResponseAuthentication no UsePAM yes # Reload SSH (keep your current session open!) sudo systemctl reload sshd
Warning: Always test key-based login in a separate terminal before disconnecting. If your key doesn't work after disabling passwords, you'll lock yourself out.
Install fail2ban
fail2ban monitors authentication logs and temporarily bans IPs after repeated failures. It's the standard defense for servers that must keep password authentication available.
# Install sudo apt install fail2ban # Debian/Ubuntu sudo yum install fail2ban # RHEL/CentOS # Create local configuration (survives package updates) sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local # Edit /etc/fail2ban/jail.local: [sshd] enabled = true port = ssh filter = sshd logpath = /var/log/auth.log maxretry = 3 # Ban after 3 failed attempts bantime = 3600 # Ban for 1 hour (increase for production) findtime = 600 # Within 10-minute window ignoreip = 127.0.0.1/8 YOUR_TRUSTED_IP # Start and enable sudo systemctl enable fail2ban sudo systemctl start fail2ban # Check status sudo fail2ban-client status sshd
Change the default SSH port
Moving SSH to a non-standard port eliminates over 95% of automated scanning traffic. It's not security through obscurity when combined with real controls — it's noise reduction.
# Edit /etc/ssh/sshd_config: Port 2222 # Choose any unused port above 1024 # Update firewall before reloading SSH! sudo ufw allow 2222/tcp # OR sudo iptables -A INPUT -p tcp --dport 2222 -j ACCEPT # Reload SSH (keep current session open!) sudo systemctl reload sshd # Connect with: ssh -p 2222 user@host
Restrict which users can log in
# In /etc/ssh/sshd_config: AllowUsers deploy admin # Only these users can SSH in DenyUsers root test guest # Explicitly deny common targets # Or restrict by group: AllowGroups sshusers # Then add users: sudo usermod -aG sshusers deploy
HTTP / WordPress Protection
Web applications are the second most targeted surface. WordPress alone powers 40% of the web, making /wp-login.php and /xmlrpc.php universal brute force targets.
Rate limit login endpoints
# nginx rate limiting for WordPress
limit_req_zone $binary_remote_addr zone=login:10m rate=3r/m;
server {
# Rate limit wp-login
location = /wp-login.php {
limit_req zone=login burst=3 nodelay;
limit_req_status 429;
include fastcgi_params;
fastcgi_pass php-fpm;
}
# Block xmlrpc entirely (used for brute force and DDoS amplification)
location = /xmlrpc.php {
deny all;
return 403;
}
# Restrict wp-admin to specific IPs (optional, very effective)
location /wp-admin/ {
allow 192.168.1.0/24; # Your office IP
allow 10.0.0.0/8; # VPN range
deny all;
}
}
Enable two-factor authentication
Even if an attacker guesses the password, 2FA stops them. For WordPress, plugins like WP 2FA or Wordfence provide TOTP-based two-factor. For custom applications, use a library like PyOTP (Python) or speakeasy (Node.js). For critical infrastructure, FIDO2/WebAuthn hardware keys provide phishing-resistant authentication that cannot be brute forced.
fail2ban for HTTP brute force
# /etc/fail2ban/jail.local [wordpress-login] enabled = true port = http,https filter = wordpress-login logpath = /var/log/nginx/access.log maxretry = 5 bantime = 3600 findtime = 300 # /etc/fail2ban/filter.d/wordpress-login.conf [Definition] failregex = ^.* "POST /wp-login.php ^ .* "POST /xmlrpc.php
FTP and Database Protection
FTP: Disable it entirely
FTP transmits credentials in plaintext and is inherently insecure. Use SFTP (which runs over SSH) instead. If you absolutely must use FTP, use FTPS (FTP over TLS) and restrict access to specific IP ranges.
# Disable FTP service sudo systemctl stop vsftpd sudo systemctl disable vsftpd # If you must keep FTP, restrict to specific IPs: # /etc/vsftpd.conf tcp_wrappers=YES # /etc/hosts.allow vsftpd: 192.168.1.0/24 # /etc/hosts.deny vsftpd: ALL
Databases: Never expose to the internet
MySQL (3306), PostgreSQL (5432), MongoDB (27017), and Redis (6379) should never listen on public interfaces. This is the most common cause of database breaches — not sophisticated attacks, but databases left open to the world with default credentials.
# PostgreSQL: bind to localhost only # /etc/postgresql/14/main/postgresql.conf listen_addresses = 'localhost' # MySQL: bind to localhost only # /etc/mysql/mysql.conf.d/mysqld.cnf bind-address = 127.0.0.1 # Redis: bind to localhost and require authentication # /etc/redis/redis.conf bind 127.0.0.1 requirepass YOUR_STRONG_PASSWORD protected-mode yes # Verify nothing is listening publicly: sudo ss -tlnp | grep -E '3306|5432|27017|6379' # Should show 127.0.0.1 only, NEVER 0.0.0.0
For remote database access, use SSH tunnels instead of exposing the port:
# Create SSH tunnel to remote database ssh -L 5432:localhost:5432 user@database-server # Then connect locally: psql -h localhost -p 5432 -U dbuser mydb
Network-Level Defense
Firewall: Default deny, explicit allow
# UFW (Uncomplicated Firewall)
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw allow 2222/tcp # SSH (custom port)
sudo ufw enable
# nftables (modern replacement for iptables)
nft add table inet filter
nft add chain inet filter input { type filter hook input priority 0 \; policy drop \; }
nft add rule inet filter input ct state established,related accept
nft add rule inet filter input iif lo accept
nft add rule inet filter input tcp dport { 80, 443, 2222 } accept
VPN-only access for management
The strongest network-level protection: require a VPN connection for SSH and all management interfaces. WireGuard is lightweight and fast:
# Install WireGuard sudo apt install wireguard # Allow SSH only from WireGuard interface sudo ufw allow in on wg0 to any port 22 sudo ufw deny 22/tcp # Block SSH from public internet
Port knocking (alternative to VPN)
Port knocking hides SSH behind a sequence of connection attempts to specific ports. Only after the correct sequence does the SSH port open for the source IP. Less robust than a VPN but useful on systems where VPN is impractical.
Proactive Defense: Pre-Block Known Attackers
The most advanced defense is proactive: block known attackers before they ever reach your login forms. By integrating threat intelligence feeds, you can deny connections from IPs with a proven track record of brute force activity.
#!/bin/bash
# Proactive threat blocking via WAYSCloud API
# Run via cron every 15 minutes
BLOCKLIST="/etc/wayscloud-blocklist.txt"
# Fetch top attacking IPs (high confidence, critical/high risk)
curl -s "https://ip.wayscloud.services/api/threats/live?limit=200" \
| jq -r '.threats[] | select(.threat_score > 70) | .ip_address' \
> "$BLOCKLIST.tmp"
# Apply blocks
while IFS= read -r ip; do
iptables -C INPUT -s "$ip" -j DROP 2>/dev/null \
|| iptables -A INPUT -s "$ip" -j DROP
done < "$BLOCKLIST.tmp"
mv "$BLOCKLIST.tmp" "$BLOCKLIST"
echo "$(date): Blocked $(wc -l < $BLOCKLIST) IPs from threat feed"
For detailed integration instructions including fail2ban reporting and nginx configurations, see the WAYSCloud integration guide.
For a deep dive into SSH brute force specifically, see our detailed guide: What is SSH Brute Force? →
Server Hardening Checklist
Use this checklist to verify your server is protected against common brute force vectors:
- SSH: Key-only authentication, password login disabled
- SSH: Root login disabled or key-only
- SSH: Non-standard port (if practical)
- fail2ban installed and active on all login-capable services
- Firewall: Default deny incoming, only required ports open
- FTP: Disabled (use SFTP instead)
- Databases: Bound to localhost only, not publicly accessible
- WordPress: xmlrpc.php blocked, login rate limited, 2FA enabled
- Management interfaces: Behind VPN or IP restriction
- Threat intelligence: Proactive blocking of known attackers via API
- Logs: Reviewed regularly or monitored with automated alerting
- Updates: Automatic security patches enabled (unattended-upgrades on Debian/Ubuntu)