//Server Security

Automate Fail2ban & UFW Updates on Multiple Servers

Automate Fail2ban and UFW rules across your dedicated servers with Ansible. Learn how to ensure consistency, speed, and security in your fleet.

6 min read
Automate Fail2ban & UFW Updates on Multiple Servers

Managing a fleet of dedicated servers for websites, APIs, or applications requires constant attention to security, availability, and consistency. Two tools commonly used by Indian administrators are Fail2ban—which blocks IPs showing malicious activity—and UFW (Uncomplicated Firewall)—a user‑friendly front‑end for iptables.

On a single server, updating Fail2ban filters or UFW rules is simple: edit a file, reload the service, and you’re done. When the number of servers grows, manual updates become error‑prone and time‑consuming. This guide shows how to automate Fail2ban and UFW rule updates across multiple dedicated servers using Ansible. The examples target Ubuntu 22.04 LTS, the most common OS for AtoZNode VPS and dedicated servers, but the concepts apply to any Debian‑based distribution.

Why automate?

  • Consistency: Every server runs the same set of filters and firewall policies.
  • Speed: A new rule propagates to all nodes in seconds, not minutes per host.
  • Auditability: Changes are version‑controlled, making roll‑backs simple.
  • Scalability: Adding a new server only requires adding it to the inventory.

Prerequisites

  1. Root or sudo access on all target servers.
  2. Ansible installed on a control machine (your laptop or a jump host). The steps below assume the control node also runs Ubuntu 22.04.
  3. SSH key‑based authentication set up between the control node and each server.
  4. Fail2ban and UFW installed on the target servers. If they are missing, the playbook will install them.

Setting up the Ansible control node

Install Ansible on the control machine:

# Update package index
sudo apt update

# Install Ansible and required Python packages
sudo apt install -y ansible python3-apt

Verify the installation:

ansible --version

Creating an inventory and variables

Ansible uses an inventory file to know which hosts to manage. Create a project directory and add the inventory:

mkdir -p ~/fail2ban-ufw
cd ~/fail2ban-ufw

# inventory file (hosts.ini)
cat > hosts.ini <<EOF
[dedicated]
server1.example.com
server2.example.com
server3.example.com

[dedicated:vars]
ansible_user=ubuntu
ansible_ssh_private_key_file=~/.ssh/id_rsa
EOF

Replace the hostnames with your actual server FQDNs or IP addresses. Adjust ansible_user if you use a different SSH user.

Writing the playbook

The playbook performs three main tasks on every host in the dedicated group:

  1. Ensures Fail2ban and UFW are installed.
  2. Copies a shared jail.local (Fail2ban) and ufw.rules (UFW) from the control node.
  3. Reloads the services so the new rules take effect.
# site.yml
---
- name: Synchronise Fail2ban and UFW across dedicated servers
  hosts: dedicated
  become: yes
  vars:
    fail2ban_config_src: files/jail.local
    ufw_rules_src: files/ufw.rules
  tasks:

    - name: Install Fail2ban and UFW
      apt:
        name:
          - fail2ban
          - ufw
        state: present
        update_cache: yes

    - name: Ensure UFW is enabled
      ufw:
        state: enabled
        policy: deny

    - name: Deploy shared Fail2ban configuration
      copy:
        src: "{{ fail2ban_config_src }}"
        dest: /etc/fail2ban/jail.local
        owner: root
        group: root
        mode: '0644'
      notify: Restart Fail2ban

    - name: Deploy shared UFW rules
      copy:
        src: "{{ ufw_rules_src }}"
        dest: /etc/ufw/ufw.rules
        owner: root
        group: root
        mode: '0644'
      notify: Reload UFW

  handlers:
    - name: Restart Fail2ban
      service:
        name: fail2ban
        state: restarted

    - name: Reload UFW
      command: ufw reload

Explanation of key modules:

  • apt – ensures the required packages are installed, updating the APT cache first.
  • ufw – enables the firewall and sets the default policy to deny for incoming traffic.
  • copy – transfers the central configuration files to each server, guaranteeing identical rules everywhere.
  • Handlers – run only when the associated task reports a change, so services restart only when the configuration actually differs.

Preparing the shared configuration files

Place the files in a files/ directory inside the project folder.

Fail2ban jail.local

# files/jail.local
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
backend = systemd

[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s

This example protects SSH with a one‑hour ban after five failed attempts. Add additional sections (e.g., nginx-http-auth, apache-modsecurity) as needed.

UFW ufw.rules

# files/ufw.rules
# Allow SSH
ufw allow OpenSSH

# Allow HTTP/HTTPS
ufw allow 80/tcp
ufw allow 443/tcp

# Allow custom application port (example 3000)
ufw allow 3000/tcp

# Rate‑limit SSH to mitigate brute‑force attacks
ufw limit OpenSSH

These rules are a starting point and can be expanded with ufw insert or ufw deny lines for your specific services.

Running the playbook

Execute the playbook from the project directory:

ansible-playbook -i hosts.ini site.yml

During the run Ansible will:

  • Connect to each server via SSH using the specified key.
  • Install any missing packages.
  • Copy the latest jail.local and ufw.rules files.
  • If the files differ from the remote versions, trigger the handlers to restart Fail2ban and reload UFW.

Check the output for any FAILED tasks. If a host cannot be reached, verify network connectivity and SSH key permissions.

Keeping configurations in version control

Store the entire ~/fail2ban-ufw directory in a Git repository. This provides:

  • History of every change to firewall or ban policies.
  • Branching for testing new rules before they go live.
  • Collaboration with pull‑request reviews.

Typical workflow:

# Make a change locally
git edit files/jail.local
git commit -am "Tighten SSH ban time to 2h"
git push origin main

# Deploy to all servers
ansible-playbook -i hosts.ini site.yml

Handling edge cases

  • Server‑specific overrides: Create a host‑specific file (e.g., host_vars/server2.example.com.yml) and add a when condition in the playbook.
  • Windows servers: Fail2ban and UFW are Linux‑only. For Windows you would use Windows Defender Firewall and a different intrusion‑prevention tool. Automation can be achieved with PowerShell DSC or Ansible’s win_* modules.
  • Service interruptions: Reloading UFW does not drop existing connections, but restarting Fail2ban may briefly pause its monitoring. Schedule updates during low‑traffic windows if needed.

Monitoring and verification

After deployment, confirm that the rules are active on each server:

# Verify Fail2ban status
sudo fail2ban-client status

# List active jails (example SSH)
sudo fail2ban-client status sshd

# Verify UFW rules
sudo ufw status numbered

You can also add a simple Ansible task to gather these outputs and write them to a log file for later review.

Conclusion

Automating Fail2ban and UFW rule updates with Ansible turns a repetitive, error‑prone process into a repeatable, auditable workflow. By maintaining a single source of truth for firewall and ban policies, every dedicated server under AtoZNode follows the same security posture, reduces manual effort, and scales effortlessly as your infrastructure grows.

Start by cloning the example repository, customizing the jail.local and ufw.rules files for your environment, and running the playbook. From there, integrate the repository into your CI/CD pipeline to push security updates automatically whenever a change is merged. Consistency, speed, and confidence—delivered with just a few lines of Ansible.

fail2banufwansiblefirewalllinuxsecurityautomationserver management

Try it on your own server

Follow along on a Cloud VPS with full root access, or read the step-by-step knowledge base guides.