DEV Community

Cover image for Guide to Secure Your Self-Hosted Stacks like Nginx, SSH, & Vaultwarden with Fail2ban
Nirjas Jakilim
Nirjas Jakilim

Posted on

Guide to Secure Your Self-Hosted Stacks like Nginx, SSH, & Vaultwarden with Fail2ban

If you are self-hosting services and expose them to public interfaces then you already know the anxiety of watching your server logs. The moment you expose port 22 (SSH) or 443 (HTTPS) to the internet, botnets and automated scripts begin knocking on your door. Whether they are brute-forcing your SSH credentials, probing Nginx for vulnerabilities, or trying to break into your Vaultwarden password manager, the noise is endless.
You can't sit there and manually block IPs all day. You need an automated bouncer.

Enter Fail2ban.

Fail2ban is an intrusion prevention software framework that protects computer servers from brute-force attacks. It works by scanning log files (like /var/log/auth.log or Nginx access logs) and dynamically updating your firewall rules to ban IPs that show malicious signs.

In this guide, we'll walk through setting up a robust Fail2ban configuration tailored for a modern self-hosted stack featuring SSH, Nginx, and Vaultwarden.

Step 1: Installation

First, let's get Fail2ban installed. On a Debian/Ubuntu-based system, you can install it via apt:

sudo apt update
sudo apt install fail2ban
sudo systemctl enable fail2ban
Enter fullscreen mode Exit fullscreen mode

Tip: Never edit the default /etc/fail2ban/jail.conf file directly. Package updates will overwrite it. Instead, we create a .local file, which Fail2ban reads to override the defaults.

Step 2: Defining the Jails (jail.local)

A "jail" in Fail2ban is essentially a rule. It tells the service which log file to watch, which filter (regex) to use, and what the punishment should be.
Create a new file at /etc/fail2ban/jail.local and paste in the following configuration:

[sshd]
# To use more aggressive sshd modes set filter parameter "mode" in jail.local:
# normal (default), ddos, extra or aggressive (combines all).
enabled  = true
port     = ssh
filter   = sshd
logpath  = %(sshd_log)s
backend  = %(sshd_backend)s

[vaultwarden]
enabled  = true
port     = 80,443,8081
backend  = pyinotify
filter   = vaultwarden
banaction = %(banaction_allports)s
logpath  = /opt/vaultwarden/vw-logs/access.log
maxretry = 3
bantime  = 1h
findtime = 10m

[nginx-http-auth]
enabled  = true
port     = http,https
logpath  = %(nginx_error_log)s
filter   = nginx-http-auth
maxretry = 3
bantime  = 1h
findtime = 10m

[nginx-bad-status]
enabled  = true
port     = http,https
logpath  = %(nginx_access_log)s
filter   = nginx-bad-status
maxretry = 15
bantime  = 10w
findtime = 10m

[nginx-botsearch]
enabled  = true
port     = http,https
logpath  = %(nginx_error_log)s
filter   = nginx-botsearch
maxretry = 2

[recidive]
enabled  = true
logpath  = /var/log/fail2ban.log
banaction = %(banaction_allports)s
bantime  = 4w
findtime = 1d
Enter fullscreen mode Exit fullscreen mode

Note: Don't forget to change the vaultwarden logpath /opt/vaultwarden/vw-logs/access.log according to your server's log path.

What's happening here?
[sshd]: The standard SSH protection. We are keeping it enabled to prevent basic brute-force ssh attacks.
[vaultwarden]: Protects our password manager. It watches all relevant web ports and uses banaction_allports to completely lock out an offending IP across all services. If someone fails a login 3 times in 10 minutes, they are banned for an hour.
[nginx-*]: These jails protect the web server. They handle failed HTTP authentications, aggressive bot searches and excessive "bad" HTTP status codes (like 404s or 403s).
[recidive]: The ultimate ban-hammer. This jail actually monitors Fail2ban's own log file. If an IP gets banned multiple times by other jails within a day, the recidive jail steps in and slaps them with a massive 4-week ban.

Step 3: Creating Custom Filters

Jails rely on filters to know what a "failed" attempt looks like. While Fail2ban comes with default filters for SSH and basic Nginx auth, we need to create custom filters for our specific Vaultwarden setup and our Nginx bad-status rules.
Filters are stored in /etc/fail2ban/filter.d/.

1. The Vaultwarden Filter

Create a file at /etc/fail2ban/filter.d/vaultwarden.local:

[INCLUDES]
before = common.conf

[Definition]
failregex = ^.*?Username or password is incorrect\. Try again\. IP: <ADDR>\. Username:.*$
ignoreregex =
Enter fullscreen mode Exit fullscreen mode

This simple regex specifically targets the exact string Vaultwarden outputs to its log file when a login fails, capturing the offender's IP address ().

2. The Nginx Bad Status Filter
Create a file at /etc/fail2ban/filter.d/nginx-bad-status.conf:

[Definition]
# Match standard log format - handles both normal HTTP requests and malformed requests (hex)
failregex = ^<HOST> .* "\S+ [^"]*" (?:400|401|403|404|405|444) \d+ ".*" ".*"$
            ^<HOST> .* ".*" (?:400|401|403|404|405|444) \d+ ".*" ".*"$

# Ignore common legitimate 404s
ignoreregex = ^<HOST> .* "GET (?:/favicon\.ico|/robots\.txt|/sitemap\.xml).* 404 \d+ ".*" ".*"$

# Define the timestamp pattern in your logs
datepattern = %%d/%%b/%%Y:%%H:%%M:%%S %%z
Enter fullscreen mode Exit fullscreen mode

This is an incredibly useful filter. Scanners constantly hit servers with malformed requests or search for hidden directories, generating 4xx errors. This catches them while safely ignoring harmless requests for missing favicons or robots.txt files.

Step 4: Start the Engine

With your configurations and filters in place, it's time to start Fail2ban and let it do its job.

Restart the service to apply the new configurations:

sudo systemctl restart fail2ban
Enter fullscreen mode Exit fullscreen mode

You can check the status of your jails at any time using:

sudo fail2ban-client status
Enter fullscreen mode Exit fullscreen mode

To see the details of a specific jail (like who is currently banned):

sudo fail2ban-client status vaultwarden
Enter fullscreen mode Exit fullscreen mode

Conclusion

Fail2ban is a lightweight, incredibly effective tool for keeping the noise out of your server. By combining service-specific jails like Vaultwarden with broad-stroke protections like Nginx bad-status and the long-term recidive ban, you drastically reduce the attack surface of your self-hosted infrastructure.
Set it, forget it and enjoy the peace of mind. There's also another great tool to use as a complete firewall solution for your servers and that is crowdsec. I will write a separate guideline for that one later.

Top comments (0)