Within minutes of a server going online, bots start guessing passwords: SSH, WordPress logins, mail accounts, database ports, RDP on Windows servers. Most attempts use common passwords or credential stuffing, which means username and password pairs leaked from other sites. Protecting against brute force is about two things: make guessing pointless, and stop the noise early.

See what’s happening

sudo journalctl -u ssh --since today | grep -c 'Failed password\|Invalid user'
sudo lastb | head
sudo grep -c 'POST /wp-login.php' /var/log/nginx/access.log

Thousands of attempts a day on SSH is normal for a public IP. The question is whether any could succeed.

SSH: make passwords irrelevant

  1. Key-only authentication, with password login disabled. This single change ends SSH brute force as a risk. See key-only SSH.
  2. No root login over SSH. See disable root login.
  3. Limit who can log in: AllowUsers deploy admin in sshd_config.
  4. Fail2Ban or CrowdSec to ban scanners, reducing log noise and load. See Fail2Ban and CrowdSec.
  5. Optional: restrict port 22 to known IPs, or reach SSH only through a VPN or bastion. A non-standard port reduces noise but doesn’t add real security. See change SSH port.
  6. For high-value servers: 2FA or hardware keys. See SSH 2FA and YubiKey SSH.

Web application logins

Web logins (WordPress, admin panels, customer accounts) still use passwords, so layers matter:

  • Rate limiting per IP on login endpoints, in Nginx. See Nginx rate limiting.
  • Fail2Ban jails for failed logins. See Fail2Ban for WordPress and Fail2Ban for Nginx.
  • Edge challenges (Cloudflare Managed Challenge on /wp-login.php). See Cloudflare WAF rules.
  • CAPTCHA or Turnstile on login and registration forms, which stops automated tools.
  • MFA for admins: TOTP apps or passkeys.
  • Account lockout or progressive delays after failed attempts, carefully, since attackers can lock out real users deliberately. Per-IP limits plus per-account delays work better than hard lockouts.
  • Breached-password checks: refuse passwords known from leaks (the Have I Been Pwned API or library integrations).
  • Hide or protect admin URLs: an IP allow-list for /wp-admin or the admin panel, or HTTP basic auth in front of it. See Nginx basic auth.

Credential stuffing

Distributed attacks use thousands of IPs, each trying just a few passwords, so per-IP limits barely trigger. Defences: MFA, breached-password checks, bot detection at the edge, and watching for many failed logins across many accounts rather than per IP.

Mail servers

SMTP AUTH, IMAP and POP3 are constant targets. A cracked mailbox gets used to send spam immediately, which puts your IP on blocklists.

  • Fail2Ban jails for Postfix SASL (postfix-sasl) and Dovecot (dovecot).
  • Strong, unique mailbox passwords, and app passwords where supported.
  • Rate limits on outgoing mail per user.
  • Monitor the outbound queue (mailq). A sudden spike means a compromised account.

See SMTP security.

Databases and other services

Never expose MySQL, PostgreSQL, Redis, MongoDB or Elasticsearch to the internet. Bind them to localhost or a private network, and firewall the ports. Exposed databases are brute-forced and exploited automatically. See check open ports.

Windows servers (RDP)

RDP brute force is relentless. Restrict RDP to your IPs, use Network Level Authentication, strong passwords and account lockout policies, or reach RDP through a VPN. See RDP brute-force protection and Windows VPS.

Monitoring

  • Alerts on successful logins. See SSH login alerts.
  • Ban statistics: fail2ban-client status sshd, or cscli metrics for CrowdSec.
  • Unusual spikes in failed logins on the web app.

Quick reference

Service Key defences
SSH Keys only, no root, Fail2Ban, IP restrictions
WordPress / web Rate limits, Fail2Ban, edge challenge, MFA, CAPTCHA
Mail Fail2Ban jails, strong passwords, outbound limits
Databases Never public
RDP IP allow-list or VPN, NLA, lockout policy

Related: Linux server hardening and password policy.

  • Security
  • Fail2Ban
  • SSH