On the VM that runs docker with our Vaultwarden container, we should install fail2ban and secure the docker container. In case you do not have it set up already, you can also secure SSH on your web servers.
Install fail2ban
# 1. Install Fail2ban sudo apt update sudo apt install fail2ban -y # 2. Create a local configuration file (never edit jail.conf directly) sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Protect SSH on a custom port
- In this case, SSH port is set to a custom port 2222 under
/etc/ssh/sshd_config.
sudo nano /etc/fail2ban/jail.local # Un-commment these: ignoreself = true # Add your own IP range (oneself + LAN + docker subnet) ignoreip = 127.0.0.1/8 ::1 192.168.0.0/16 172.20.0.0/16 # Comment out ignorecommand = # Find an existing section called [sshd] and modify it as follows: [sshd] enabled = true port = 2222 mode = aggressive # The more modern method instead of reading text files backend = systemd maxretry = 3 bantime = 1h
- If you have not started the fail2ban client yet,
run sudo fail2ban-client start. Otherwise runsudo fail2ban-client reloadfor the changes above to kick in.
Protect Vaultwarden’s docker container
- In your docker-compose.yml file, ensure that you have the following parameters set:
# Ensure these three are somewhere in your 'environment':
environment:
- LOG_FILE=/data/vaultwarden.log
- LOG_LEVEL=info
- EXTENDED_LOGGING=true
- After changing the values below, run
docker compose up -dto apply changes. - Let’s create a config that instructs
fail2banon what a failed login to Vaultwarden looks like:
sudo nano /etc/fail2ban/filter.d/vaultwarden.conf [Definition] # Matches "Admin login attempt failed" and "Invalid password for user" failregex = .*Username or password is incorrect.*IP: <HOST>
- We will need to define a deny action:
sudo nano /etc/fail2ban/action.d/nginx-deny.conf [Definition] actionstart = actionstop = actioncheck = actionban = printf "deny <ip>;\\n" >> /etc/nginx/blocklist.conf; systemctl reload nginx actionunban = sed -i "/deny <ip>;/d" /etc/nginx/blocklist.conf; systemctl reload nginx
- This path does not yet exist, let’s create it and set correct permissions for it:
sudo touch /etc/nginx/blocklist.conf sudo chmod 664 /etc/nginx/blocklist.conf
- Then we should configure nginx’s config to read from it:
sudo nano /etc/nginx/conf.d/vaultwarden.conf # Block Banned IPs from fail2ban include /etc/nginx/blocklist.conf;
- Now we can finally add a new jail for Vaultwarden.
sudo nano /etc/fail2ban/jail.local [vaultwarden] enabled = true # Read logs directly from Docker backend = systemd # Which container to watch journalmatch = CONTAINER_NAME=vaultwarden # The attacker's port, not the internal port port = 80,443,8081 filter = vaultwarden # The 'chain=DOCKER-USER' blocks the IP before Docker forwards it action = nginx-deny maxretry = 3 bantime = 300m # 5min findtime = 300m # 5 min
- Reload the fail2ban service –
sudo fail2ban-client reload. - Then let’s run a real test: Attempt some failed login attempts from your phone while on mobile network. Attempt some bad logins and refresh the page. You should see your custom 403 page. Then check:
sudo fail2ban-client status vaultwarden. Then you can remove your banned IP by runningsudo fail2ban-client set vaultwarden unbanip 1.2.3.4on the affected web server.
Troubleshooting fail2ban for Vaultwarden on Docker
- Ensure that the log is seeing repeated failed login attempts:
sudo docker logs --tail 20 vaultwarden - Compare the errors with the filter we created earlier – check
sudo nano /etc/fail2ban/filter.d/vaultwarden.conf.- In case you make modifications, restart the fail2ban service with
sudo fail2ban-client restart.
- In case you make modifications, restart the fail2ban service with
- Run a dry-run using
sudo fail2ban-regex systemd-journal /etc/fail2ban/filter.d/vaultwarden.conf - Is the count increasing but the connection with the device is not being blocked?
- Ensure that in your nginx config, you have this line included:
include /etc/nginx/blocklist.conf;, as otherwise, whatever is there to be blocked will be ignored. - Confirm that your nginx-deny config exists, such as by running
sudo nano /etc/fail2ban/action.d/nginx-deny.conf.
- Ensure that in your nginx config, you have this line included:
Ensure local + Cloudflare traffic does not get blocked
If you are using Cloudflare or another proxy-like traffic management that provides you with CDN and traffic filtering and an attacker tries to log into your instance of Vaultwarden, nginx will end up blocking the IP address of the proxy server from Cloudflare rather than their actual one. Such behavior is undesirable, as it would then block legitimate traffic coming from the proxy to your nginx server.
The solution is to whitelist the local and Cloudflare servers as shown on their website. Yet the list changes (not often but from time to time) and it would be tedious to keep it up to date manually. So let’s automate it!
- Create a folder and script:
sudo mkdir /opt/cloudflare-proxies sudo nano /opt/cloudflare-proxies/update-cloudflare-ips.sh
#!/bin/bash
# Download Cloudflare IPs
echo "# Cloudflare IPs - Auto Updated" > /etc/nginx/snippets/trusted-proxies.conf
# Internal IPs - modify this to reflect your local subnet
echo "set_real_ip_from 192.168.0.0/16;" >> /etc/nginx/snippets/trusted-proxies.conf
# IPv4
for ip in $(curl -s <https://www.cloudflare.com/ips-v4>); do
echo "set_real_ip_from $ip;" >> /etc/nginx/snippets/trusted-proxies.conf
done
# IPv6
for ip in $(curl -s <https://www.cloudflare.com/ips-v6>); do
echo "set_real_ip_from $ip;" >> /etc/nginx/snippets/trusted-proxies.conf
done
# Headers
echo "real_ip_header X-Forwarded-For;" >> /etc/nginx/snippets/trusted-proxies.conf
echo "real_ip_recursive on;" >> /etc/nginx/snippets/trusted-proxies.conf
# Reload Nginx
sudo systemctl reload nginx
- Run this script to verify it works as expected, then make it executable:
# Run it sudo bash /opt/cloudflare-proxies/update-cloudflare-ips.sh # You should see the IPs from the script in there cat /etc/nginx/snippets/trusted-proxies.conf # Make the script executable sudo chmod +x /opt/cloudflare-proxies/update-cloudflare-ips.sh # Add it to run weekly via a cron job sudo crontab -e # Add the following entry in there 0 4 * * 1 /bin/bash /opt/cloudflare-proxies/update-cloudflare-ips.sh
- Add it into your virtual host on nginx:
sudo nano /etc/nginx/conf.d/vaultwarden.conf
# Add the text below under the server { directive:
# Whitelist Cloudflare proxies & internal subnet:
include /etc/nginx/snippets/trusted-proxies.conf;
- Double check the syntax and reload nginx:
sudo nginx -t sudo systemctl reload nginx