Introduction
Managing firewall rules is essential for server security. The ansible.posix.firewalld module provides full control over firewalld on RedHat-family systems — opening ports, enabling services, configuring zones, and applying rich rules, all idempotently.
This guide covers everything from basic port opening to advanced zone-based firewall configurations with Ansible.
The ansible.posix.firewalld Module
The full module name is ansible.posix.firewalld, part of the ansible.posix collection for POSIX platforms. It requires:
- Ansible 2.9+
- firewalld >= 0.2.11 on the target
- Python
firewallbindings on the target - Works on: RHEL, CentOS, CentOS Stream, Fedora, Rocky Linux, AlmaLinux, Oracle Linux, EuroLinux
Install the Collection
ansible-galaxy collection install ansible.posix
Parameters Reference
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
state | string | Yes | — | enabled, disabled, present, absent |
service | string | No | — | Service name (e.g., http, https) |
port | string | No | — | Port/protocol (e.g., 8080/tcp) |
permanent | boolean | No | false | Persist across reboots |
immediate | boolean | No | false* | Apply to running config now |
zone | string | No | system default | Target zone |
rich_rule | string | No | — | Rich rule string |
source | string | No | — | Source IP/network |
interface | string | No | — | Network interface |
masquerade | string | No | — | Enable masquerading |
icmp_block | string | No | — | ICMP type to block |
icmp_block_inversion | string | No | — | Invert ICMP block |
offline | boolean | No | false | Work with firewalld offline |
target | string | No | — | Zone target (default, ACCEPT, DROP, REJECT) |
*If permanent: false, immediate is assumed true.
Important: To make a change both permanent AND immediately active, set BOTH permanent: true and immediate: true.
Basic Examples
Open HTTP and HTTPS
---
- name: Open web ports
hosts: webservers
become: true
tasks:
- name: Enable HTTP and HTTPS
ansible.posix.firewalld:
service: "{{ item }}"
state: enabled
permanent: true
immediate: true
loop:
- http
- https
Open a Custom Port
---
- name: Open custom ports
hosts: appservers
become: true
tasks:
- name: Open application port 8080/tcp
ansible.posix.firewalld:
port: 8080/tcp
state: enabled
permanent: true
immediate: true
- name: Open port range 9000-9100/tcp
ansible.posix.firewalld:
port: 9000-9100/tcp
state: enabled
permanent: true
immediate: true
- name: Open UDP port for DNS
ansible.posix.firewalld:
port: 53/udp
state: enabled
permanent: true
immediate: true
Close a Port
---
- name: Close unused ports
hosts: all
become: true
tasks:
- name: Disable telnet service
ansible.posix.firewalld:
service: telnet
state: disabled
permanent: true
immediate: true
- name: Close port 3306 (MySQL not needed externally)
ansible.posix.firewalld:
port: 3306/tcp
state: disabled
permanent: true
immediate: true
Advanced Patterns
Zone Configuration
---
- name: Configure firewall zones
hosts: dmzservers
become: true
tasks:
- name: Move interface to DMZ zone
ansible.posix.firewalld:
zone: dmz
interface: eth0
state: enabled
permanent: true
immediate: true
- name: Allow HTTP in DMZ zone only
ansible.posix.firewalld:
zone: dmz
service: http
state: enabled
permanent: true
immediate: true
- name: Block all traffic in drop zone for interface eth1
ansible.posix.firewalld:
zone: drop
interface: eth1
state: enabled
permanent: true
immediate: true
Rich Rules (Fine-Grained Control)
---
- name: Apply rich rules
hosts: all
become: true
tasks:
- name: Allow SSH only from specific subnet
ansible.posix.firewalld:
rich_rule: 'rule family="ipv4" source address="10.0.0.0/24" service name="ssh" accept'
state: enabled
permanent: true
immediate: true
- name: Rate-limit SSH connections
ansible.posix.firewalld:
rich_rule: 'rule service name="ssh" accept limit value="3/m"'
state: enabled
permanent: true
immediate: true
- name: Log and drop traffic from malicious IP
ansible.posix.firewalld:
rich_rule: 'rule family="ipv4" source address="192.168.1.100" log prefix="BLOCKED: " level="warning" drop'
state: enabled
permanent: true
immediate: true
- name: Forward port 80 to 8080
ansible.posix.firewalld:
rich_rule: 'rule family="ipv4" forward-port port="80" protocol="tcp" to-port="8080"'
state: enabled
permanent: true
immediate: true
Source-Based Rules
---
- name: Source-based firewall rules
hosts: dbservers
become: true
tasks:
- name: Add trusted source to internal zone
ansible.posix.firewalld:
zone: internal
source: 10.0.1.0/24
state: enabled
permanent: true
immediate: true
- name: Allow PostgreSQL from app subnet only
ansible.posix.firewalld:
rich_rule: 'rule family="ipv4" source address="10.0.2.0/24" port port="5432" protocol="tcp" accept'
state: enabled
permanent: true
immediate: true
Enable Masquerading (NAT)
---
- name: Configure NAT gateway
hosts: gateways
become: true
tasks:
- name: Enable masquerading on external zone
ansible.posix.firewalld:
masquerade: "true"
zone: external
state: enabled
permanent: true
immediate: true
Complete Playbook: Web Server Firewall Setup
---
- name: Configure firewall for web server
hosts: webservers
become: true
vars:
allowed_services:
- http
- https
- ssh
custom_ports:
- 8443/tcp # Alternative HTTPS
- 9090/tcp # Prometheus metrics
admin_subnet: "10.0.0.0/24"
monitoring_subnet: "10.0.10.0/24"
tasks:
- name: Ensure firewalld is installed and running
block:
- name: Install firewalld
ansible.builtin.yum:
name: firewalld
state: present
- name: Start and enable firewalld
ansible.builtin.systemd:
name: firewalld
state: started
enabled: true
- name: Enable allowed services
ansible.posix.firewalld:
service: "{{ item }}"
state: enabled
permanent: true
immediate: true
loop: "{{ allowed_services }}"
- name: Open custom ports
ansible.posix.firewalld:
port: "{{ item }}"
state: enabled
permanent: true
immediate: true
loop: "{{ custom_ports }}"
- name: Restrict SSH to admin subnet
ansible.posix.firewalld:
rich_rule: 'rule family="ipv4" source address="{{ admin_subnet }}" service name="ssh" accept'
state: enabled
permanent: true
immediate: true
- name: Allow monitoring from monitoring subnet
ansible.posix.firewalld:
rich_rule: 'rule family="ipv4" source address="{{ monitoring_subnet }}" port port="9090" protocol="tcp" accept'
state: enabled
permanent: true
immediate: true
- name: Remove default SSH access (use rich rule instead)
ansible.posix.firewalld:
service: ssh
state: disabled
permanent: true
immediate: true
- name: Verify firewall configuration
ansible.builtin.command: firewall-cmd --list-all
register: fw_config
changed_when: false
- name: Display firewall status
ansible.builtin.debug:
var: fw_config.stdout_lines
Available Services
List all predefined services on a target:
firewall-cmd --get-services
Common services: ssh, http, https, dns, ntp, smtp, ftp, mysql, postgresql, redis, cockpit, kube-apiserver, prometheus, grafana
Common Mistakes
Mistake 1: Permanent but Not Immediate
# Rule saved to disk but NOT active until firewalld restart
- ansible.posix.firewalld:
service: http
state: enabled
permanent: true
# Missing: immediate: true
Mistake 2: Immediate but Not Permanent
# Rule active NOW but lost on reboot
- ansible.posix.firewalld:
service: http
state: enabled
permanent: false
immediate: true
Correct: Both
- ansible.posix.firewalld:
service: http
state: enabled
permanent: true
immediate: true
Troubleshooting
Module Not Found
ERROR! couldn't resolve module/action 'ansible.posix.firewalld'
Fix: Install the collection:
ansible-galaxy collection install ansible.posix
"firewalld is not running"
Fix: Start the service first in your playbook (see complete example above).
Changes Not Persisting
Ensure permanent: true is set. Check with:
firewall-cmd --permanent --list-all
Related Articles
- Ansible firewalld/ufw Module — Cross-platform firewall management
- Ansible systemd Module — Managing services
- Ansible Security Hardening — Server security automation
- Ansible Playbook Best Practices — Writing production playbooks
Conclusion
The ansible.posix.firewalld module provides complete control over firewalld on RedHat-family systems. Always use both permanent: true and immediate: true to ensure rules are active immediately AND survive reboots. Use services for standard ports, port for custom ports, rich rules for fine-grained source/destination control, and zones for interface-level segmentation.