Home / Alt manpages / firewalld.richlanguage(5)

  • firewalld.richlanguage(5)
  • File format
  • linux

Add and Safely Test firewalld Rich Rules

By the end of this guide, you will have added a rich rule to a firewalld zone, checked the exact rule that is active, and removed it again when it is no longer needed. The examples use the syntax documented by the installed firewalld 2.1.1 package.

Allow about 10 minutes. You need a shell account with sudo access, a running firewalld service, and a service or port that you can test from a suitable client. The commands that change firewall state require elevated privileges. Listing and querying rules normally work as an ordinary user, but this depends on your local D-Bus policy.

1. Check the version and active zone

Start by confirming which client you are using and which zone is active. A rich rule belongs to a zone, so adding it without checking the zone is an easy way to protect the wrong interface.

firewall-cmd --version
sudo firewall-cmd --state
sudo firewall-cmd --get-active-zones

On the machine used for this guide, the installed manpage identifies firewalld as version 2.1.1. The state command should print running. The active-zone query should show one or more zone names, followed by the interfaces or source ranges assigned to them.

If firewalld is stopped, do not add a rule and assume it is protecting the host. Start or repair the service through your distribution's normal service manager, then repeat this check. If more than one zone is listed, choose the zone containing the interface that receives the traffic you are changing. In the examples below, replace public with that zone.

Checkpoint: the target is known

  • The client reports version 2.1.1 or another version you have accounted for.
  • firewalld reports running.
  • You have written down the zone that owns the relevant interface.

2. Inspect available service and ICMP names

Use a predefined service when one describes the traffic you need. Service names are maintained by firewalld and are less error-prone than copying a port number from memory.

sudo firewall-cmd --get-services
sudo firewall-cmd --get-icmptypes

For example, if ssh appears in the service list, a rule can refer to service name="ssh". Rich rules also accept a port number or range with tcp, udp, sctp or dccp. A port rule is more explicit when you are opening a private application rather than a standard service.

3. Add one narrowly scoped rule

This example allows SSH only from one IPv4 address. It is deliberately specific: the family matches the address, the source is a single host, and the action is accept.

ZONE=public
SOURCE_IP=198.51.100.24
RULE="rule family=\"ipv4\" source address=\"$SOURCE_IP\" service name=\"ssh\" accept"
sudo firewall-cmd --zone="$ZONE" --add-rich-rule="$RULE"

The command should return success. Rich language rules are single-line strings. Each element's options must immediately follow that element. For example, put service name="ssh" together, and put the service before the final action.

The change above is runtime configuration. It is useful for testing because it can be discarded by a reload or restart. If you intend to keep it across a reboot, save the working configuration only after testing it with your distribution's normal firewalld workflow. Saving untested firewall rules can lock out remote administration.

Checkpoint: the rule is loaded

sudo firewall-cmd --zone="$ZONE" --list-rich-rules
sudo firewall-cmd --zone="$ZONE" --query-rich-rule="$RULE"
echo "$?"

The list should contain a rule equivalent to the one you added, and the query should print yes followed by an exit status of 0. Do not rely on the list alone when troubleshooting: comparing the complete string catches a wrong address family, zone, service name or action.

Now test from 198.51.100.24, or substitute the real source address before running the test. A successful client connection proves that the service is reachable through the rule, but it does not prove that every other source is blocked. Test an unauthorised source as well, and inspect the service logs if the result is unexpected.

4. Add logging without creating a log flood

Logging is part of the rich rule and must appear before its action. This rule rejects SSH from a known unwanted IPv4 address and logs at most two matching attempts per day.

BLOCKED_IP=198.51.100.25
LOG_RULE="rule family=\"ipv4\" source address=\"$BLOCKED_IP\" service name=\"ssh\" log prefix=\"ssh-block\" level=\"info\" limit value=\"2/d\" reject"
sudo firewall-cmd --zone="$ZONE" --add-rich-rule="$LOG_RULE"
sudo firewall-cmd --zone="$ZONE" --query-rich-rule="$LOG_RULE"

A reject sends an ICMP or ICMPv6 rejection to the source. A drop discards packets without sending information back. Choose deliberately: rejection is easier to diagnose, while dropping reveals less to an unwanted client. The manpage limits values to a positive rate and a duration of seconds, minutes, hours or days, with a maximum of 2/d.

The default kernel log level for log is warning; this example selects info. The prefix can be up to 127 characters in the rich language, although the iptables backend truncates it to 29 characters. Check the host's journal or kernel log after a controlled test rather than generating repeated connection attempts.

5. Remove or undo the test rules

Remove a rule by passing the exact same string used to add it. Keep the variables in your shell until both removals have completed.

sudo firewall-cmd --zone="$ZONE" --remove-rich-rule="$RULE"
sudo firewall-cmd --zone="$ZONE" --remove-rich-rule="$LOG_RULE"
sudo firewall-cmd --zone="$ZONE" --list-rich-rules

Each successful removal should print success, and the final list should no longer contain either rule. If a removal says the rule is absent, first inspect the list and compare quoting, address family, zone and option order. Do not replace a rule with a broad allow rule just to make a test pass.

For a temporary experiment, another recovery path is to reload firewalld and then verify the zone again. That discards runtime-only changes, but it can also interrupt other uncommitted firewall changes, so use it only when you understand what else is active:

sudo firewall-cmd --reload
sudo firewall-cmd --zone="$ZONE" --list-rich-rules

Rules that need extra care

If a rule contains a source or destination address, provide family="ipv4" or family="ipv6". Without a family, a rule applies to both families when possible, but an address cannot match both. Forward-port rules and port forwarding also require a family. Forwarding to another address implicitly enables IP forwarding, and masquerading does the same, so treat those rules as routing changes rather than simple service exceptions.

Rule priority ranges from -32768 to 32767. Lower numbers have higher precedence, and equal priorities have undefined ordering. Negative priorities run before other firewalld primitives; positive priorities run afterwards. With priority zero, logging, deny actions and allow actions are placed into separate chains. Start without an explicit priority unless you have a documented ordering requirement, then add one and test the interaction with existing rules.

The first matching rule wins when rules interact or contradict. A broad allow rule before a narrower deny rule can therefore defeat the deny. Review the complete list after every change, particularly when combining source allowlists, denylists and logging.

Done means

  • You confirmed the installed firewalld version, running state and target zone.
  • Your rule uses the right family, source, service or port, and action.
  • --query-rich-rule confirmed the exact rule was present.
  • You tested an allowed and an unauthorised source where practical.
  • Temporary rules were removed, or you recorded and tested the persistent configuration deliberately.