How to automatically block failed SSH login attempts

Repeated failed SSH logins usually mean password spraying, bot scans, or stale automation hitting a public endpoint. Automatically banning the addresses that keep failing reduces noise in the logs and forces repeated guesses off the server before they can continue cycling through credentials.

On current Ubuntu and Debian systems, fail2ban watches authentication failures that match the packaged sshd jail and adds temporary firewall bans when one address crosses the configured retry threshold. The normal workflow is to leave the packaged jail files alone and add only a local override for the retry limit, detection window, and ban duration.

Automatic bans can also catch legitimate administrators when several people share one public address behind NAT, a VPN concentrator, or a bastion host. Keep the current administrative session open while testing, store local changes in /etc/fail2ban/jail.d/ or /etc/fail2ban/jail.local/ instead of editing the packaged /etc/fail2ban/jail.conf/, and confirm the ban from a second host before relying on it in production.

Steps to automatically block failed SSH login attempts with fail2ban:

  1. Stay in a current administrative session on the server with an account that can use sudo until the ban policy is verified.
    $ whoami
    user

    Do not close the current administrative session until a second host confirms that the new ban settings work as expected.

  2. Install the fail2ban package from the distribution repository.
    $ sudo apt install --assume-yes fail2ban
    Reading package lists...
    Building dependency tree...
    Reading state information...
    fail2ban is already the newest version.
    0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.

    On current Ubuntu packages, /etc/fail2ban/jail.d/defaults-debian.conf keeps the packaged sshd jail enabled by default.

  3. Create a dedicated local override for the sshd jail.
    $ sudoedit /etc/fail2ban/jail.d/sshd.local
    [sshd]
    enabled = true
    maxretry = 5
    findtime = 10m
    bantime = 1h

    Place the same block in /etc/fail2ban/jail.local if that is the override file your system already uses. Do not edit /etc/fail2ban/jail.conf directly because package upgrades can replace it.

    If several administrators share one public source address, shorten the ban or exempt that trusted address or subnet before using longer bans.

  4. Test the fail2ban configuration before reloading the service.
    $ sudo fail2ban-client --test
    OK: configuration test is successful

    A successful test confirms that the local jail file parses cleanly and that the daemon can load the updated sshd jail.

  5. Enable and start fail2ban so it also comes back after reboot.
    $ sudo systemctl enable --now fail2ban
    Synchronizing state of fail2ban.service with SysV service script with /usr/lib/systemd/systemd-sysv-install.
    Executing: /usr/lib/systemd/systemd-sysv-install enable fail2ban
    
    $ sudo systemctl is-enabled fail2ban
    enabled
    
    $ sudo systemctl is-active fail2ban
    active

    The package may already start fail2ban automatically, but the command above makes the boot-time state explicit and confirms the service is active now.

  6. Reload the fail2ban jails so the new sshd override takes effect immediately.
    $ sudo fail2ban-client reload
    OK

    Use sudo systemctl restart fail2ban instead if the daemon is not responding to a normal reload.

  7. Check the live sshd jail so you know the watcher started cleanly.
    $ sudo fail2ban-client status sshd
    Status for the jail: sshd
    |- Filter
    |  |- Currently failed:	0
    |  |- Total failed:	0
    |  `- Journal matches:	_SYSTEMD_UNIT=sshd.service + _COMM=sshd
    `- Actions
       |- Currently banned:	0
       |- Total banned:	0
       `- Banned IP list:	

    The current Ubuntu package resolves the packaged sshd jail to the systemd backend with the nftables ban action, so an explicit /var/log/auth.log path is not required on that default setup.

  8. Check the retry window and ban duration that fail2ban actually loaded for the sshd jail.
    $ sudo fail2ban-client get sshd maxretry
    5
    
    $ sudo fail2ban-client get sshd findtime
    600
    
    $ sudo fail2ban-client get sshd bantime
    3600

    findtime and bantime are reported in seconds even when the configuration file uses abbreviations such as 10m or 1h.

  9. Review recent ban activity after repeated failed logins from another host.
    $ sudo tail --lines 6 /var/log/fail2ban.log
    2026-04-14 03:57:07,646 fail2ban.filter         [84]: INFO      maxRetry: 5
    2026-04-14 03:57:07,646 fail2ban.actions        [84]: INFO      banTime: 3600
    2026-04-14 03:58:12,306 fail2ban.filter         [84]: INFO    [sshd] Found 198.51.100.24 - 2026-04-14 03:58:12
    2026-04-14 03:58:16,023 fail2ban.filter         [84]: INFO    [sshd] Found 198.51.100.24 - 2026-04-14 03:58:15
    2026-04-14 03:58:19,772 fail2ban.filter         [84]: INFO    [sshd] Found 198.51.100.24 - 2026-04-14 03:58:19
    2026-04-14 03:58:29,028 fail2ban.actions        [84]: NOTICE  [sshd] Ban 198.51.100.24

    The address shown above is a documentation placeholder. Your server log will show the real remote address that crossed the retry threshold.

  10. Recheck the sshd jail after those failed logins to confirm that the source was actually banned.
    $ sudo fail2ban-client status sshd
    Status for the jail: sshd
    |- Filter
    |  |- Currently failed:	0
    |  |- Total failed:	5
    |  `- Journal matches:	_SYSTEMD_UNIT=sshd.service + _COMM=sshd
    `- Actions
       |- Currently banned:	1
       |- Total banned:	1
       `- Banned IP list:	198.51.100.24

    Generate the test failures from a different host or a temporary source address, not from the only SSH session you have left open to the server.