Write a firewalld Zone XML File and Apply It Safely
A zone file that does nothing is harmless, but one that does the wrong thing on a remote box means a trip to the data centre. This guide builds a small persistent firewalld zone that binds a source network and permits one named service, then checks and reloads it without guessing what the XML means. The examples match firewalld 2.1.1, the installed version used for this guide, and take about 20 minutes.
The route
Jump straight to the step you need, or tick off Done means at the end.
- You need root access, firewalld installed, and a maintenance path to the host if the machine is remote.
- Risk. This changes firewall policy.
- Keep a way back in. Hold an existing SSH session open and have console or out-of-band access before you reload a new zone.
1. Check the installed version and current state
Start with read-only checks. These do not need elevated privileges, although the daemon may refuse some information when it is stopped:
$ firewall-cmd --version
2.1.1
$ firewall-cmd --state
running
$ firewall-cmd --get-active-zones
public
interfaces: enp1s0
The exact active zone and interface are host-specific. On a stopped daemon, this installation reports FirewallD is not running. Do not treat that as a reason to edit files blindly. First decide whether firewalld is meant to be the firewall on this host.
Write down the active zone, interfaces and any current services before changing anything:
$ firewall-cmd --get-active-zones
$ firewall-cmd --zone=public --list-all
Checkpoint
You have the current zone, interfaces and services noted. Replace public with the zone that is actually active on your machine. A zone file that no interface or source selects may be perfectly valid and still have no effect on traffic.
2. Choose the file location and zone boundary
- Your zones go in
/etc/firewalld/zones/. That is the intended override location for administrator-created zones. - Packaged defaults live in
/usr/lib/firewalld/zones/. Do not edit them, because an update can replace them. - The filename is the zone name plus
.xml. The manpage limits the zone name to 17 characters.
This example creates office.xml for IPv4 addresses in 192.0.2.0/24, a documentation network. Replace that network with one you control:
# install -d -m 0755 /etc/firewalld/zones
# install -m 0644 /dev/null /etc/firewalld/zones/office.xml
# cp --preserve=mode,ownership,timestamps /etc/firewalld/zones/office.xml /etc/firewalld/zones/office.xml.bak
The last command is a recovery copy. If office.xml already contains useful policy, inspect it before replacing it.
Warning
The install commands need root, and the empty-file step destroys the contents of an existing file. It is only safe for a newly named zone, so do not use it on an existing one.
3. Write a minimal zone
A zone has one zone root element. A source binds traffic to an address or network, and a service enables a predefined service. Service names come from firewalld's service definitions, so check the spelling rather than inventing a port label:
$ firewall-cmd --get-services | tr ' ' '\n' | grep -x ssh
ssh
Now write the policy as root. The example allows the predefined SSH service from the documentation network:
<?xml version="1.0" encoding="utf-8"?>
<zone>
<short>Office</short>
<description>Controlled administration network</description>
<source address="192.0.2.0/24"/>
<service name="ssh"/>
</zone>
Tip
The original text of this guide described a forward element for intra-zone forwarding, but the example XML above does not contain one. Add it only if you specifically need forwarding between interfaces or sources in this zone, and leave it out otherwise.
Use an editor with root privileges, such as sudoedit /etc/firewalld/zones/office.xml. Do not paste a source network you have not verified.
- No host names. A zone
sourcetakes an IP address or a network with a mask. - One kind of source per entry. Use one of
address,macoripset, not several at once.
4. Add only traffic you can explain
Use services for named application protocols. Use port when no suitable service exists, and specify tcp, udp, sctp or dccp. For example, this permits TCP port 8443:
<port port="8443" protocol="tcp"/>
Do not add both a service and its individual ports for reassurance. That makes later reviews harder without changing the result. A protocol element is different: it permits a system protocol from /etc/protocols, not a TCP or UDP port.
Security warning
Masquerading and forwarding are separate, security-sensitive choices. <masquerade/> enables address masquerading for the zone. A forward-port maps a local port to another port or address: use to-port for local forwarding, and to-addr with an optional to-port for remote forwarding. Do not add either element until you can name the destination and the return path.
For rules that depend on source, destination, logging, rate limits, or accept, reject and drop actions, use a rich rule. The zone manpage points to firewalld.richlanguage(5) for the full grammar. Keep this first zone simple and add rich rules one at a time.
5. Validate before reloading
Check the permanent configuration while the current runtime policy stays in place:
# firewall-cmd --check-config
success
A parse or schema error is a stop sign. Check XML spelling, closing tags, the zone filename, the source mask, service names and protocol values. Do not reload until this command succeeds. It validates configuration only: it does not prove that the intended interface or source will select the zone.
Confirm that firewalld can see the zone and its permanent contents:
$ firewall-cmd --get-zones
block dmz drop external home internal office public trusted work
$ firewall-cmd --permanent --zone=office --list-all
office
target: default
sources: 192.0.2.0/24
services: ssh
Checkpoint
office is listed, the source is correct and the expected service appears. Output varies by version and existing configuration. According to the installed zone manpage, the default target rejects packets that match no rule.
6. Reload and verify the runtime policy
Warning
Reloading is service-disrupting. Existing connections can be affected, and a bad policy can remove your access. Keep your second session or console ready.
When validation and the backup are done, reload as root:
# firewall-cmd --reload
success
$ firewall-cmd --zone=office --list-all
office
target: default
sources: 192.0.2.0/24
services: ssh
Test from a client in the declared source network, not only from the firewall host. Confirm the intended service works, and confirm that an unrelated source is not accidentally matching the new zone.
Tip
If NetworkManager manages an interface, it normally binds that interface to a zone automatically. An interface element in XML is mainly a fallback for interfaces it cannot manage.
A zone source and an interface are alternative ways to classify traffic. If both are present in overlapping zones, selection can surprise you. Use --get-active-zones and --zone=ZONE --list-all to inspect the actual arrangement.
7. Recover without guessing
If the reload fails validation, the old runtime policy should remain in place, but still read the error before retrying. If the new policy blocks your administration path, use the console or your second session. Restore the backup, validate and reload:
# cp --preserve=mode,ownership,timestamps /etc/firewalld/zones/office.xml.bak /etc/firewalld/zones/office.xml
# firewall-cmd --check-config
# firewall-cmd --reload
Recovery
Do not delete a zone file as a first response. If the zone is no longer required, first remove its interface or source association and confirm the replacement policy. Keep the backup until a connection test has succeeded. Removing a backup is irreversible, so make it a deliberate housekeeping action rather than part of the recovery command.
Done means
- You know the starting point. The installed firewalld version and active zones are recorded.
- The file is in place. The administrator-owned XML is under
/etc/firewalld/zones/, with a tested backup. - The zone is tight. It has one clear source or interface boundary and only the required services or ports.
- Validation came first.
firewall-cmd --check-configreportssuccessbefore every reload. - Runtime matches permanent. The runtime zone listing matches the intended permanent configuration.
- You can still get in. A client test succeeded, and console or backup recovery remains available.