Skip to content

firewalld Role Usage Guide

The firewalld role manages firewalld zones declaratively by writing zone XML files to /etc/firewalld/zones/. Any zone file on the host that is not represented in firewalld_zones is deleted on the next run, making zone configuration fully version-controlled.

Platform support

OS Packages installed
RHEL / Rocky firewalld, at (via dnf)
Ubuntu / Debian firewalld, at (via apt)

Calling the role

The role accepts a single variable: firewalld_zones. Pass it via include_role:

- name: Configure firewall
  ansible.builtin.include_role:
    name: firewalld
  vars:
    firewalld_zones:
      public:
        target: DROP
        services:
          - https
          - ssh
        ports:
          - "7327"
      monitoring:
        target: DROP
        sources:
          - 10.248.5.0/24
        services:
          - ssh
        ports:
          - "9113"
          - "9100"

Zone types

firewalld routes traffic to a zone based on the first match:

Source-based zones — a zone with one or more entries in sources receives traffic originating from those CIDRs. This is how a monitoring zone restricts Prometheus scrapes to a specific subnet without affecting other traffic.

Interface-based zones — a zone with no sources is assigned to a network interface (or is the default zone). The public zone is typically the OS default and receives all traffic not matched by a source-based zone.

Lockout protection

Every run of the role takes the following steps to prevent accidental SSH lockout:

  1. Backup — copies the current zone directory to /tmp/firewalld_zones_backup
  2. Schedule revert — schedules an at job to fire in 2 minutes that restores the backup and reloads firewalld
  3. Apply changes — writes new zone XML files, removes any extras, reloads firewalld
  4. Verify connectivity — runs wait_for_connection (60-second timeout) to confirm the controller can still reach the host via SSH
  5. Cancel revert — if connectivity is confirmed, cancels the at job and removes all artifacts

If step 4 fails (SSH is blocked), two recovery paths exist:

  • In-band rescue (ControlMaster socket available from prior connections): the rescue block runs immediately, restoring the backup and reloading firewalld before the at job fires
  • At-job revert (no existing connection): the at job fires after 2 minutes, restoring the backup. The revert script is idempotent — if the rescue block already ran and cleaned up the backup, the at job exits without making changes

In both cases, all temporary artifacts (/tmp/firewall_revert.sh, /tmp/firewall_revert_job_id, /tmp/firewalld_zones_backup) are removed before the run ends.

Verifying firewall state

# Show which zones are active (have an interface or source binding)
firewall-cmd --get-active-zones

# List configuration for a specific zone
firewall-cmd --zone=public --list-all
firewall-cmd --zone=monitoring --list-all

# Show all zones (including those with no active bindings)
firewall-cmd --list-all-zones

# Show the zone files on disk
ls -la /etc/firewalld/zones/