Introduction

Ansible manages Linux firewalls through dedicated modules: ansible.posix.firewalld for RHEL/CentOS/Fedora and community.general.ufw for Ubuntu/Debian. Open ports, allow services, configure zones, and ensure firewall rules persist across reboots.

firewalld (RHEL/CentOS/Fedora)

Open a Port

# Open HTTP port
- name: Allow HTTP
  ansible.posix.firewalld:
    port: 80/tcp
    permanent: true
    state: enabled
    immediate: true

# Open HTTPS
- name: Allow HTTPS
  ansible.posix.firewalld:
    port: 443/tcp
    permanent: true
    state: enabled
    immediate: true

# Open custom port
- name: Allow app port
  ansible.posix.firewalld:
    port: 8080/tcp
    permanent: true
    state: enabled
    immediate: true

Allow a Service

# Allow by service name
- name: Allow SSH
  ansible.posix.firewalld:
    service: ssh
    permanent: true
    state: enabled
    immediate: true

# Allow multiple services
- name: Allow web services
  ansible.posix.firewalld:
    service: "{{ item }}"
    permanent: true
    state: enabled
    immediate: true
  loop:
    - http
    - https
    - ssh

Zone Management

# Set default zone
- name: Set default zone to public
  ansible.builtin.command: firewall-cmd --set-default-zone=public

# Add interface to zone
- name: Add eth0 to public zone
  ansible.posix.firewalld:
    zone: public
    interface: eth0
    permanent: true
    state: enabled
    immediate: true

# Open port in specific zone
- name: Allow port in internal zone
  ansible.posix.firewalld:
    zone: internal
    port: 5432/tcp
    permanent: true
    state: enabled
    immediate: true

# Allow source IP range in zone
- name: Trust internal network
  ansible.posix.firewalld:
    zone: trusted
    source: 10.0.0.0/24
    permanent: true
    state: enabled
    immediate: true

Rich Rules

# Allow specific IP to a port
- name: Allow monitoring server
  ansible.posix.firewalld:
    rich_rule: 'rule family="ipv4" source address="10.0.0.50" port port="9090" protocol="tcp" accept'
    permanent: true
    state: enabled
    immediate: true

# Rate limit SSH
- name: Rate limit SSH connections
  ansible.posix.firewalld:
    rich_rule: 'rule service name="ssh" accept limit value="10/m"'
    permanent: true
    state: enabled
    immediate: true

Close Ports / Remove Rules

- name: Close port
  ansible.posix.firewalld:
    port: 8080/tcp
    permanent: true
    state: disabled
    immediate: true

- name: Remove service
  ansible.posix.firewalld:
    service: http
    permanent: true
    state: disabled
    immediate: true

UFW (Ubuntu/Debian)

Open a Port

# Enable UFW
- name: Enable UFW
  community.general.ufw:
    state: enabled
    policy: deny    # Default deny incoming

# Allow SSH (always do this before enabling!)
- name: Allow SSH
  community.general.ufw:
    rule: allow
    port: "22"
    proto: tcp

# Allow HTTP/HTTPS
- name: Allow web traffic
  community.general.ufw:
    rule: allow
    port: "{{ item }}"
    proto: tcp
  loop:
    - "80"
    - "443"

# Allow custom port
- name: Allow app port
  community.general.ufw:
    rule: allow
    port: "8080"
    proto: tcp

Allow from Specific IPs

# Allow from specific IP
- name: Allow monitoring server
  community.general.ufw:
    rule: allow
    src: 10.0.0.50
    port: "9090"
    proto: tcp

# Allow from subnet
- name: Allow internal network
  community.general.ufw:
    rule: allow
    src: 10.0.0.0/24

# Allow specific IP to specific port
- name: Allow DB access from app server
  community.general.ufw:
    rule: allow
    src: 10.0.0.10
    port: "5432"
    proto: tcp

Rate Limiting

# Rate limit SSH (UFW built-in)
- name: Rate limit SSH
  community.general.ufw:
    rule: limit
    port: "22"
    proto: tcp

Deny Traffic

# Deny specific port
- name: Block port 23 (telnet)
  community.general.ufw:
    rule: deny
    port: "23"
    proto: tcp

# Deny from IP
- name: Block attacker IP
  community.general.ufw:
    rule: deny
    src: 203.0.113.100

Delete Rules

- name: Remove rule
  community.general.ufw:
    rule: allow
    port: "8080"
    proto: tcp
    delete: true

Practical Patterns

Web Server Firewall (firewalld)

---
- name: Configure web server firewall
  hosts: webservers
  become: true
  tasks:
    - name: Ensure firewalld is running
      ansible.builtin.service:
        name: firewalld
        state: started
        enabled: true

    - name: Allow web services
      ansible.posix.firewalld:
        service: "{{ item }}"
        permanent: true
        state: enabled
        immediate: true
      loop:
        - http
        - https
        - ssh

    - name: Allow app port from internal only
      ansible.posix.firewalld:
        rich_rule: 'rule family="ipv4" source address="10.0.0.0/24" port port="{{ app_port }}" protocol="tcp" accept'
        permanent: true
        state: enabled
        immediate: true

Web Server Firewall (UFW)

---
- name: Configure web server firewall
  hosts: webservers
  become: true
  tasks:
    - name: Install UFW
      ansible.builtin.apt:
        name: ufw
        state: present

    - name: Allow SSH (before enabling!)
      community.general.ufw:
        rule: allow
        port: "22"
        proto: tcp

    - name: Allow web ports
      community.general.ufw:
        rule: allow
        port: "{{ item }}"
        proto: tcp
      loop: ["80", "443"]

    - name: Allow app port from internal
      community.general.ufw:
        rule: allow
        src: 10.0.0.0/24
        port: "{{ app_port }}"
        proto: tcp

    - name: Enable UFW with default deny
      community.general.ufw:
        state: enabled
        policy: deny

Database Server Lockdown

- name: Only allow app servers to reach DB
  ansible.posix.firewalld:
    rich_rule: 'rule family="ipv4" source address="{{ item }}" port port="5432" protocol="tcp" accept'
    permanent: true
    state: enabled
    immediate: true
  loop: "{{ groups['appservers'] | map('extract', hostvars, 'ansible_host') }}"

firewalld Parameters

ParameterDescription
portPort/protocol (e.g., 80/tcp)
serviceService name (e.g., http)
zoneFirewall zone
sourceSource IP/CIDR
interfaceNetwork interface
rich_ruleRich rule string
permanentPersist across reboots
immediateApply immediately
stateenabled or disabled

Troubleshooting

"Locked out of SSH"

Always allow SSH before enabling the firewall:

# Order matters!
- ufw: { rule: allow, port: "22", proto: tcp }
- ufw: { state: enabled, policy: deny }

Rules Not Persisting

For firewalld, set permanent: true AND immediate: true:

- firewalld:
    port: 80/tcp
    permanent: true    # Survives reboot
    immediate: true    # Applies now
    state: enabled

Conclusion

Use ansible.posix.firewalld on RHEL/CentOS and community.general.ufw on Ubuntu/Debian. Always allow SSH before enabling a deny-all policy. Use permanent: true + immediate: true with firewalld. Use rich rules or source-based rules to restrict access to specific IPs. For database servers, only allow connections from application servers. Firewall configuration is a critical security baseline — automate it to ensure consistency.