# Security against Brute attack

This volume of failed logins is **normal background noise** for any server with port 22 open to the internet — automated bots scan all IPv4 addresses and start hammering common usernames within minutes of your server going online. Your fail2ban and UFW are doing exactly what they're supposed to: stopping them from actually getting in.

That said, you can eliminate most of this traffic entirely. If you only have SSH keys enabled, the thousands of password guesses are harmless and simply never succeed.

## Is this normal?

<div class="group/final-text flex flex-col min-w-0 gap-4" data-workflow-final-text="" id="bkmrk-1564-failed-logins-%2F"><div class="break-words min-w-0 flex-1"><div><div class="prose dark:prose-invert inline leading-relaxed break-words min-w-0 [word-break:break-word] [&amp;_>*:first-child]:mt-0 [&amp;_>*:last-child]:mb-0" data-renderer="lm">

- **1564 failed logins / 192 bans / 7329 firewall blocks** is a typical-to-calm level for a public server. Many freshly-unprotected boxes see tens of thousands of attempts daily.
- The important test: do any of the failed logins *succeed*? If they don't (key-based auth only), this is nuisance traffic, not a breach.
- One thing worth checking: if most failures come from a **single IP**, fail2ban may be misconfigured or its filter may not match your auth log format — otherwise it should be banning that IP. Verify with `sudo fail2ban-client status sshd`.

</div></div></div></div>## What you can do to cut it down

The single most effective change is to make password login impossible, so the brute-force attempts can never succeed:

<div class="group/final-text flex flex-col min-w-0 gap-4" data-workflow-final-text="" id="bkmrk-bash-%23-%2Fetc%2Fssh%2Fsshd"><div class="break-words min-w-0 flex-1"><div><div class="prose dark:prose-invert inline leading-relaxed break-words min-w-0 [word-break:break-word] [&amp;_>*:first-child]:mt-0 [&amp;_>*:last-child]:mb-0" data-renderer="lm"><div class="w-full md:max-w-[90vw]"><figure class="relative flex w-full flex-col overflow-hidden rounded-lg bg-subtle font-mono font-medium text-primary selection:bg-super-soft selection:text-super-primary [&amp;_.token.deleted]:!text-negative-primary [&amp;_.token.inserted]:!text-positive-primary text-sm"><figcaption class="flex min-h-8 items-center justify-between gap-2 border-b border-subtlest bg-soft px-2 py-1 text-xs text-secondary">bash <div class="ml-auto flex items-center gap-2">  
</div></figcaption><code>&#60;span class&#61;&#34;token token&#34;&#62;&#35; &#47;etc&#47;ssh&#47;sshd&#95;config&#46;d&#47;01&#45;hardening&#46;conf&#60;&#47;span&#62;PasswordAuthentication noKbdInteractiveAuthentication noPermitRootLogin noPubkeyAuthentication &#60;span class&#61;&#34;token token&#34;&#62;yes&#60;&#47;span&#62;MaxAuthTries &#60;span class&#61;&#34;token token&#34;&#62;3&#60;&#47;span&#62;LoginGraceTime &#60;span class&#61;&#34;token token&#34;&#62;20&#60;&#47;span&#62;</code></figure></div>

- **Use SSH keys only** — this alone eliminates the overwhelming majority of brute-force risk.
- **Disable root login** (`PermitRootLogin no`) so attackers can't target root directly.
- **Rate-limit at the firewall**: `sudo ufw limit OpenSSH` (allows ~6 connections per 30s per IP, dropping brute-force spikes at the kernel level).
- **Use a non-standard port** (e.g., 2222) — reduces scanner traffic dramatically, though it's security-by-obscurity.
- **Restrict access** to known admin IPs or a VPN (Tailscale/WireGuard) if your IP is static — then port 22 isn't exposed at all.

</div></div></div></div>## Before you change anything

<div class="group/final-text flex flex-col min-w-0 gap-4" data-workflow-final-text="" id="bkmrk-confirm-key-based-lo"><div class="break-words min-w-0 flex-1"><div><div class="prose dark:prose-invert inline leading-relaxed break-words min-w-0 [word-break:break-word] [&amp;_>*:first-child]:mt-0 [&amp;_>*:last-child]:mb-0" data-renderer="lm">

- Confirm key-based login **already works** from a fresh session before disabling passwords, or you'll lock yourself out.
- Keep one SSH session open, back up the config (`cp`, then `sshd -t` to validate syntax), then `sudo systemctl reload ssh` — not restart, so existing sessions survive.
- Check that your cloud provider has a web/serial console as a break-glass fallback.

</div></div></div></div>**Bottom line:** there's nothing special or alarming in your numbers — it's the standard pre-scan traffic every internet-exposed server receives. If you want to reduce it (and the log noise), the fixes are key-only auth + disable root + firewall rate limiting. Once that's in place, the 1564 failures are wasted effort against a door that's already locked.

Want me to check whether any of the failed logins actually succeeded on a real account, or walk through a specific hardening step?

<div id="bkmrk-used-ssh-keys-only-a"><div class="contents" dir="auto" lang="en"><div class="group group/user-bubble flex items-start justify-end gap-2"><div class="flex flex-col items-end gap-1 max-w-[600px]"><div class="inline-flex flex-col items-end relative"><div class="min-w-[48px] select-none p-3 bg-subtle rounded-2xl flex items-center justify-center"><span class="min-w-0 font-sans text-base text-primary font-normal select-text break-words selection:bg-super-selection selection:text-primary dark:selection:text-super-primary"><span class="flex min-w-0 max-w-full flex-col items-start"><span class="block min-w-0 max-w-full max-h-[144px] overflow-hidden"><span class="block max-w-full whitespace-pre-line break-words [word-break:break-word]">Used ssh keys only and disabled root login and I am using custom port 2010 for ssh. Is there anything if I changed ssh port for fail2ban ?</span></span></span></span></div></div></div></div></div></div><div class="contents" id="bkmrk-"><div class="flex flex-col min-w-0 text-secondary relative"><div class="flex items-center gap-2 relative group/tool-wrapper z-10"><div class="_sharedDuration_orhia_1 min-w-0 flex-1">  
</div><button aria-expanded="false" class="reset interactable min-w-0 flex items-center gap-2 w-fit max-w-full group/tool-button cursor-pointer" type="button"><svg class="inline-flex fill-current shrink-0 text-secondary  group-hover/tool-wrapper:text-primary" height="14" role="img" stroke-width="1.75" width="14"></svg></button></div><div class="relative">  
</div></div></div>Yes — you should update fail2ban so its **ban action** targets your port 2010. The good news: fail2ban detects attacks by parsing your auth log, not by watching ports, so it still *sees* the failed logins on any port. The problem is only the ban itself — if fail2ban's action still blocks port 22 (or the <code>OpenSSH</code> app profile that points to 22), the banned IPs aren't actually being blocked from connecting to 2010.

## What to change

On Ubuntu with UFW, the cleanest way is a dedicated UFW application profile for port 2010, then point the sshd jail at it.

**1. Add an app profile** for your port in `/etc/ufw/applications.d/openssh-server`:

<div class="group/final-text flex flex-col min-w-0 gap-4" data-workflow-final-text="" id="bkmrk-text-%5Bopenssh-2010%5D-"><div class="contents" dir="auto" lang="en"><div class="break-words min-w-0 flex-1"><div><div class="prose dark:prose-invert inline leading-relaxed break-words min-w-0 [word-break:break-word] [&amp;_>*:first-child]:mt-0 [&amp;_>*:last-child]:mb-0" data-renderer="lm"><div class="w-full md:max-w-[90vw]"><figure class="relative flex w-full flex-col overflow-hidden rounded-lg bg-subtle font-mono font-medium text-primary selection:bg-super-soft selection:text-super-primary [&amp;_.token.deleted]:!text-negative-primary [&amp;_.token.inserted]:!text-positive-primary text-sm"><figcaption class="flex min-h-8 items-center justify-between gap-2 border-b border-subtlest bg-soft px-2 py-1 text-xs text-secondary">text <div class="ml-auto flex items-center gap-2">  
</div></figcaption><code>&#91;OpenSSH&#45;2010&#93;title&#61;Secure shell server &#40;custom&#41;description&#61;OpenSSH on custom portports&#61;2010&#47;tcp</code></figure></div></div></div></div></div></div>**2. Point the sshd jail at it** in <code>&#47;etc&#47;fail2ban&#47;jail&#46;local</code>:

<div class="group/final-text flex flex-col min-w-0 gap-4" data-workflow-final-text="" id="bkmrk-text-%5Bsshd%5D-enabled-"><div class="contents" dir="auto" lang="en"><div class="break-words min-w-0 flex-1"><div><div class="prose dark:prose-invert inline leading-relaxed break-words min-w-0 [word-break:break-word] [&amp;_>*:first-child]:mt-0 [&amp;_>*:last-child]:mb-0" data-renderer="lm"><div class="w-full md:max-w-[90vw]"><figure class="relative flex w-full flex-col overflow-hidden rounded-lg bg-subtle font-mono font-medium text-primary selection:bg-super-soft selection:text-super-primary [&amp;_.token.deleted]:!text-negative-primary [&amp;_.token.inserted]:!text-positive-primary text-sm"><figcaption class="flex min-h-8 items-center justify-between gap-2 border-b border-subtlest bg-soft px-2 py-1 text-xs text-secondary">text <div class="ml-auto flex items-center gap-2">  
</div></figcaption><code>&#91;sshd&#93;enabled &#61; trueport &#61; 2010action &#61; ufw&#91;application&#61;&#34;OpenSSH&#45;2010&#34;&#44; blocktype&#61;reject&#93;logpath &#61; &#37;&#40;sshd&#95;log&#41;sbackend &#61; &#37;&#40;sshd&#95;backend&#41;s</code></figure></div></div></div></div></div></div>**3. Reload** and verify:

<div class="group/final-text flex flex-col min-w-0 gap-4" data-workflow-final-text="" id="bkmrk-bash-sudo-ufw-allow-"><div class="contents" dir="auto" lang="en"><div class="break-words min-w-0 flex-1"><div><div class="prose dark:prose-invert inline leading-relaxed break-words min-w-0 [word-break:break-word] [&amp;_>*:first-child]:mt-0 [&amp;_>*:last-child]:mb-0" data-renderer="lm"><div class="w-full md:max-w-[90vw]"><figure class="relative flex w-full flex-col overflow-hidden rounded-lg bg-subtle font-mono font-medium text-primary selection:bg-super-soft selection:text-super-primary [&amp;_.token.deleted]:!text-negative-primary [&amp;_.token.inserted]:!text-positive-primary text-sm"><figcaption class="flex min-h-8 items-center justify-between gap-2 border-b border-subtlest bg-soft px-2 py-1 text-xs text-secondary">bash <div class="ml-auto flex items-center gap-2">  
</div></figcaption><code>&#60;span class&#61;&#34;token token&#34;&#62;sudo&#60;&#47;span&#62; ufw allow &#60;span class&#61;&#34;token token&#34;&#62;2010&#60;&#47;span&#62;&#47;tcp comment &#60;span class&#61;&#34;token token&#34;&#62;&#39;ssh&#39;&#60;&#47;span&#62;&#60;span class&#61;&#34;token token&#34;&#62;sudo&#60;&#47;span&#62; fail2ban&#45;client reload&#60;span class&#61;&#34;token token&#34;&#62;sudo&#60;&#47;span&#62; fail2ban&#45;client status sshd</code></figure></div></div></div></div></div></div>The <code>port &#61; 2010</code> line tells fail2ban the port to report/ban, and the <code>action &#61; ufw&#91;application&#61;&#34;OpenSSH&#45;2010&#34;&#93;</code> makes UFW insert a <code>deny from &#60;ip&#62; to any app OpenSSH&#45;2010</code> rule that actually drops traffic to 2010.

## Important caveat

There's a known failure mode: if you leave fail2ban's default `iptables`/`ufw` action pointing at 22 or forget to update it, `fail2ban-client status sshd` will show bans, but `ufw status` will have no matching deny rules and the IPs keep reaching 2010 — exactly the "fails to block ssh on custom port" problem.

Two checks to confirm it's working after changing the port:

<div class="group/final-text flex flex-col min-w-0 gap-4" data-workflow-final-text="" id="bkmrk-sudo-fail2ban-client"><div class="contents" dir="auto" lang="en"><div class="break-words min-w-0 flex-1"><div><div class="prose dark:prose-invert inline leading-relaxed break-words min-w-0 [word-break:break-word] [&amp;_>*:first-child]:mt-0 [&amp;_>*:last-child]:mb-0" data-renderer="lm">

- `sudo fail2ban-client status sshd` — shows the banned IP count.
- `sudo ufw status numbered` — verify a matching `DENY ... to 2010/tcp` (or `to ... app OpenSSH-2010`) rule exists for recently banned IPs.

</div></div></div></div></div>If you'd rather not maintain a custom profile, the alternative is the simpler stock line <code>action &#61; ufw</code> with <code>banaction &#61; ufw</code> under <code>&#91;DEFAULT&#93;</code> — but that still binds to the <code>port &#61; 2010</code> you set, so make sure that's updated.

One extra tip since you moved to 2010: add a second jail watching for activity on port 22 too — legacy scanners may still probe it, and banning them at 22 keeps noise out of your UFW logs.

<div id="bkmrk-%5Bopenssh-2010%5D-title"><div class="contents" dir="auto" lang="en"><div class="group group/user-bubble flex items-start justify-end gap-2"><div class="flex flex-col items-end gap-1 max-w-[600px]"><div class="inline-flex flex-col items-end relative"><div class="min-w-[48px] select-none p-3 bg-subtle rounded-2xl flex items-center justify-center"><span class="min-w-0 font-sans text-base text-primary font-normal select-text break-words selection:bg-super-selection selection:text-primary dark:selection:text-super-primary"><span class="max-w-full mb-1 border-subtlest ring-subtlest divide-subtlest inline-flex items-center"><span class="min-w-0 border-l-2 border-subtle pl-3 font-sans text-sm text-secondary  selection:bg-super-selection selection:text-primary dark:selection:text-super-primary line-clamp-1">\[OpenSSH-2010\] title=Secure shell server (custom) description=OpenSSH on custom port ports=2010/tcp</span></span><span class="flex min-w-0 max-w-full flex-col items-start"><span class="block min-w-0 max-w-full max-h-[144px] overflow-hidden"><span class="block max-w-full whitespace-pre-line break-words [word-break:break-word]">Just append this?</span></span></span></span></div></div></div></div></div></div>Yes — just append that block to the end of <code>&#47;etc&#47;ufw&#47;applications&#46;d&#47;openssh&#45;server</code>. Adding a second <code>&#91;OpenSSH&#45;2010&#93;</code> section alongside the existing <code>&#91;OpenSSH&#93;</code> (port 22) profile is fine; UFW supports multiple profiles in one file and each is referenced by its own bracketed name.

<div class="group/final-text flex flex-col min-w-0 gap-4" data-workflow-final-text="" id="bkmrk-bash-sudo-nano-%2Fetc%2F"><div><div class="contents" dir="auto" lang="en"><div class="break-words min-w-0 flex-1"><div><div class="prose dark:prose-invert inline leading-relaxed break-words min-w-0 [word-break:break-word] [&amp;_>*:first-child]:mt-0 [&amp;_>*:last-child]:mb-0" data-renderer="lm"><div class="w-full md:max-w-[90vw]"><figure class="relative flex w-full flex-col overflow-hidden rounded-lg bg-subtle font-mono font-medium text-primary selection:bg-super-soft selection:text-super-primary [&amp;_.token.deleted]:!text-negative-primary [&amp;_.token.inserted]:!text-positive-primary text-sm"><figcaption class="flex min-h-8 items-center justify-between gap-2 border-b border-subtlest bg-soft px-2 py-1 text-xs text-secondary">bash <div class="ml-auto flex items-center gap-2">  
</div></figcaption><code>&#60;span class&#61;&#34;token token&#34;&#62;sudo&#60;&#47;span&#62; &#60;span class&#61;&#34;token token&#34;&#62;nano&#60;&#47;span&#62; &#47;etc&#47;ufw&#47;applications&#46;d&#47;openssh&#45;server</code></figure></div></div></div></div></div></div></div>Append at the bottom:

<div class="group/final-text flex flex-col min-w-0 gap-4" data-workflow-final-text="" id="bkmrk-text-%5Bopenssh-2010%5D--1"><div><div class="contents" dir="auto" lang="en"><div class="break-words min-w-0 flex-1"><div><div class="prose dark:prose-invert inline leading-relaxed break-words min-w-0 [word-break:break-word] [&amp;_>*:first-child]:mt-0 [&amp;_>*:last-child]:mb-0" data-renderer="lm"><div class="w-full md:max-w-[90vw]"><figure class="relative flex w-full flex-col overflow-hidden rounded-lg bg-subtle font-mono font-medium text-primary selection:bg-super-soft selection:text-super-primary [&amp;_.token.deleted]:!text-negative-primary [&amp;_.token.inserted]:!text-positive-primary text-sm"><figcaption class="flex min-h-8 items-center justify-between gap-2 border-b border-subtlest bg-soft px-2 py-1 text-xs text-secondary">text <div class="ml-auto flex items-center gap-2">  
</div></figcaption><code>&#91;OpenSSH&#45;2010&#93;title&#61;Secure shell server &#40;custom&#41;description&#61;OpenSSH on custom portports&#61;2010&#47;tcp</code></figure></div></div></div></div></div></div></div>Two quick notes:

<div class="group/final-text flex flex-col min-w-0 gap-4" data-workflow-final-text="" id="bkmrk-don%27t-add-a-second%C2%A0%5B"><div><div class="contents" dir="auto" lang="en"><div class="break-words min-w-0 flex-1"><div><div class="prose dark:prose-invert inline leading-relaxed break-words min-w-0 [word-break:break-word] [&amp;_>*:first-child]:mt-0 [&amp;_>*:last-child]:mb-0" data-renderer="lm">

- Don't add a second `[OpenSSH]` with the same name, or UFW will complain about a duplicate app — a uniquely-named block is the correct approach.
- After saving, re-read the profile list and confirm it appears:

<div class="w-full md:max-w-[90vw]"><figure class="relative flex w-full flex-col overflow-hidden rounded-lg bg-subtle font-mono font-medium text-primary selection:bg-super-soft selection:text-super-primary [&amp;_.token.deleted]:!text-negative-primary [&amp;_.token.inserted]:!text-positive-primary text-sm"><figcaption class="flex min-h-8 items-center justify-between gap-2 border-b border-subtlest bg-soft px-2 py-1 text-xs text-secondary">bash <div class="ml-auto flex items-center gap-2">  
</div></figcaption><code>&#60;span class&#61;&#34;token token&#34;&#62;sudo&#60;&#47;span&#62; ufw app list&#60;span class&#61;&#34;token token&#34;&#62;&#35; You should now see both&#58;&#60;&#47;span&#62;&#60;span class&#61;&#34;token token&#34;&#62;&#35;   OpenSSH&#60;&#47;span&#62;&#60;span class&#61;&#34;token token&#34;&#62;&#35;   OpenSSH&#45;2010&#60;&#47;span&#62;</code></figure></div></div></div></div></div></div></div>Then you can use it in boththe firewall rule and the fail2ban action:

<div class="group/final-text flex flex-col min-w-0 gap-4" data-workflow-final-text="" id="bkmrk-bash-sudo-ufw-allow--1"><div><div class="contents" dir="auto" lang="en"><div class="break-words min-w-0 flex-1"><div><div class="prose dark:prose-invert inline leading-relaxed break-words min-w-0 [word-break:break-word] [&amp;_>*:first-child]:mt-0 [&amp;_>*:last-child]:mb-0" data-renderer="lm"><div class="w-full md:max-w-[90vw]"><figure class="relative flex w-full flex-col overflow-hidden rounded-lg bg-subtle font-mono font-medium text-primary selection:bg-super-soft selection:text-super-primary [&amp;_.token.deleted]:!text-negative-primary [&amp;_.token.inserted]:!text-positive-primary text-sm"><figcaption class="flex min-h-8 items-center justify-between gap-2 border-b border-subtlest bg-soft px-2 py-1 text-xs text-secondary">bash <div class="ml-auto flex items-center gap-2">  
</div></figcaption><code>&#60;span class&#61;&#34;token token&#34;&#62;sudo&#60;&#47;span&#62; ufw allow &#60;span class&#61;&#34;token token&#34;&#62;&#39;OpenSSH&#45;2010&#39;&#60;&#47;span&#62; comment &#60;span class&#61;&#34;token token&#34;&#62;&#39;ssh&#39;&#60;&#47;span&#62;&#60;span class&#61;&#34;token token&#34;&#62;sudo&#60;&#47;span&#62; fail2ban&#45;client reload</code></figure></div></div></div></div></div></div></div>If <code>ufw app list</code> doesn't show the new profile after saving, just re-run <code>sudo ufw app update OpenSSH&#45;2010</code> (or reload UFW) to refresh its parsed application definitions.