Write a firewalld Policy XML File and Verify It

Zones alone cannot express "internal may talk out to external, but only on HTTPS and DNS", and that gap is what firewalld policies fill. This guide builds a small permanent policy file, checks it, reloads firewalld and confirms the policy is visible. The example uses firewalld 2.1.1, installed here as package version 2.1.1-1, and takes about 20 minutes.

1. Check the installed contract

Read the local manual page and identify the command paths before editing anything. These are ordinary, read-only commands:

$ man firewalld.policy
$ firewall-cmd --version
FirewallD is not running
$ dpkg-query -W -f='${Package} ${Version}\n' firewalld
firewalld 2.1.1-1

The version command can print a warning when the daemon is stopped. That is not a version mismatch. The package query is the useful version check on this Debian-style system; on another distribution, use its package tool instead.

A few facts about policy files:

Confirm the directory and the zones you intend to connect:

$ sudo install -d -m 0755 /etc/firewalld/policies
$ firewall-cmd --get-zones
block dmz drop external home internal public trusted work

Checkpoint: the directory exists and the zone list contains the two zones you plan to use. The zone output is host-specific, so replace internal and external below only if your host uses different existing zone names. A policy is directional: ingress internal to egress external is not the reverse direction.

2. Save the current policy state

Before a privileged change, check whether this policy already exists and keep any file with the same name. The commands below read the configuration and create a recoverable backup:

$ sudo test -e /etc/firewalld/policies/lab-egress.xml && \
    sudo cp --preserve=all /etc/firewalld/policies/lab-egress.xml \
    /etc/firewalld/policies/lab-egress.xml.bak || true
$ sudo ls -l /etc/firewalld/policies

If the file already exists, inspect the backup before you carry on. Do not overwrite an unfamiliar production policy just to make this example fit. If you decide not to continue, remove only the backup you just made after confirming its path, or leave it for later recovery.

3. Write the policy XML

Now create the policy as root. This is the first state-changing step. The XML has one policy element, two zone bindings and two port entries. CONTINUE is the default target, so packets that do not match these entries are left for the rest of firewalld's policy evaluation.

$ sudo tee /etc/firewalld/policies/lab-egress.xml >/dev/null <<'XML'
<?xml version="1.0" encoding="utf-8"?>
<policy target="CONTINUE">
  <short>Lab egress</short>
  <description>Allow web and DNS traffic from the internal lab to the external zone.</description>
  <ingress-zone name="internal"/>
  <egress-zone name="external"/>
  <port port="443" protocol="tcp"/>
  <port port="53" protocol="udp"/>
</policy>
XML
$ sudo sed -n '1,80p' /etc/firewalld/policies/lab-egress.xml

Checkpoint: the sed output is the same XML you supplied. If the file is empty or truncated, stop and restore the backup before doing anything that reloads firewalld.

4. Check before reloading

Ask firewalld to check its permanent configuration. This is read-only with respect to the running rules, but it may need root access to read all the configuration files:

$ sudo firewall-cmd --check-config
success

If it reports an XML or policy error, do not reload. Check these:

A valid XML document can still describe a policy that matches no traffic if its zone bindings are wrong.

Checkpoint: continue only after you see success. A non-zero exit status means the configuration is not ready for activation.

5. Reload and inspect the active policy

Warning: reloading is the second state-changing step and can briefly disrupt connections. Run it during a suitable maintenance window.

$ sudo firewall-cmd --reload
success
$ firewall-cmd --get-policies
lab-egress
$ firewall-cmd --policy=lab-egress --list-ingress-zones
internal
$ firewall-cmd --policy=lab-egress --list-egress-zones
external
$ firewall-cmd --policy=lab-egress --list-ports
443:tcp 53:udp

The exact ordering and whitespace can differ. What matters is that lab-egress is listed, both zone bindings are present, and both ports appear. If a command reports that the daemon is not running, start or enable firewalld according to your distribution's service policy before treating that as a policy-file error.

These checks confirm that firewalld loaded the policy. They do not prove that an application can reach a particular host. Test from a machine in the ingress zone, using a destination and route that really belong to the egress zone. A connection test can also be affected by the destination service, routing, DNS or another policy.

6. Roll back safely

Warning: warn users or operators before rolling back, because the reload can again interrupt connections.

If this example replaced an existing file, restore its backup. If it was a new file, remove only this policy file:

$ if sudo test -e /etc/firewalld/policies/lab-egress.xml.bak; then
    sudo cp --preserve=all /etc/firewalld/policies/lab-egress.xml.bak \
      /etc/firewalld/policies/lab-egress.xml
  else
    sudo rm -- /etc/firewalld/policies/lab-egress.xml
  fi
$ sudo firewall-cmd --check-config
$ sudo firewall-cmd --reload

Do not use a broad recursive removal under /etc/firewalld. The backup copy is the undo path for an existing policy. Once the restored configuration has been checked and reloaded, verify that firewall-cmd --get-policies no longer shows the temporary policy if it was newly created.

Common traps

Done means