| |

LFCS 108 ๐Ÿง Firewalld โ€” Rich Rules and Services

โฑ๏ธ Estimated read: 13 min | |

The previous chapter introduced services and rich rules as the mechanisms for controlling traffic within a zone. This chapter goes deeper into both. A service is not just a name that opens a portโ€”it is an XML definition that can specify multiple ports, protocols, kernel modules, and destination addresses. A rich rule is not just a syntax variant of --add-serviceโ€”it is a language with priorities, logging, rate limiting, and source restrictions that the simple service and port commands cannot express. The LFCS exam tests both layers: the ability to read and create service definitions, and the ability to write rich rules that solve problems the basic commands cannot.

Key point: A service definition is an XML file under /usr/lib/firewalld/services/ (default) or /etc/firewalld/services/ (custom). A rich rule is a single string passed to --add-rich-rule that can combine source, destination, service or port, logging, rate limiting, and an action. The syntax is order-sensitive and quote-sensitive.


Why service definitions and rich rules exist

The multi-port service problem. A single application often requires more than one port. An FTP server needs TCP port 21 for control and a range of ports for data transfer. A SIP server needs UDP 5060 and TCP 5061. Opening each port individually with --add-port is error-prone and undocumented. A service definition groups all the required ports and protocols under a single name. Adding the service to a zone opens every port it defines, in one command .

The kernel module problem. Some services require kernel modules to be loaded before they function correctly. A NetBIOS service needs nf_conntrack_netbios_ns. A service definition can specify the module name, and firewalld loads it when the service is added to a zone . The --add-port command cannot do this. The service definition is the only mechanism that ties a kernel module to a firewall rule.

The source restriction problem. --add-service=ssh opens SSH to the entire world. If the requirement is “allow SSH only from the 10.0.0.0/8 subnet,” the simple service command cannot express it. A rich rule with a source address condition is the only way to restrict a service to a specific source .

The rate limiting problem. A public-facing SSH server will be scanned and brute-forced. --add-service=ssh accepts every connection attempt. A rich rule with limit value="4/m" accepts only four connection attempts per minute from any given source, which dramatically slows brute-force attacks .

The logging problem. When a packet is dropped, the administrator may need to know why. The drop action in a simple rule is silent. A rich rule can add a log clause before the action, writing a message to the kernel log with a custom prefix .

The trade-off. Service definitions are static XML files. They must be created before they can be used, and they are not modified by firewall-cmd commands. Rich rules are dynamic but fragile: a single syntax error causes the command to fail, and the error message is often unhelpful. The exam tests both, and the correct choice depends on whether the requirement can be expressed with a service or requires the flexibility of a rich rule.


a. Service definitions: XML structure and custom services

A service definition is an XML file that describes the ports, protocols, modules, and destination addresses required by a service. The file lives in /usr/lib/firewalld/services/ for default services and /etc/firewalld/services/ for custom services. A custom service with the same name as a default service overrides the default .

The structure of a service file is straightforward. The <service> root element contains one or more <port> elements, optional <protocol> elements, optional <module> elements, and an optional <destination> element.

<?xml version="1.0" encoding="utf-8"?>
<service>
  <short>My Custom App</short>
  <description>Custom application on ports 8080 and 8443</description>
  <port port="8080" protocol="tcp"/>
  <port port="8443" protocol="tcp"/>
  <module name="nf_conntrack_myapp"/>
</service>

The <port> element is the most common. The port attribute accepts a single port number or a range (8080-8090). The protocol attribute accepts tcp, udp, sctp, or dccp . The <module> element specifies a kernel module that must be loaded for the service to function. The <destination> element restricts the service to a specific destination address, which is used for multicast services .

To create a custom service, write the XML file to /etc/firewalld/services/ and run firewall-cmd --reload. The service name is the filename without the .xml extension. It can then be added to a zone with --add-service=myapp .

The --get-services command lists every service definition available on the system, including custom services. The --info-service=<name> command shows the details of a specific service, including its ports, protocols, and modules.


b. Rich rules: syntax, priorities, and actions

A rich rule is a structured string that combines conditions and an action. The general syntax is defined in the firewalld.richlanguage man page . The order of elements matters: rule, optional family and priority, optional source and destination, the match condition (service or port), optional log, and the action (accept, reject, or drop).

# Allow SSH from a specific subnet
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="10.0.1.0/24" service name="ssh" accept' --permanent

The family attribute must be specified when a source or destination address is used. The source address condition restricts the rule to packets originating from that address or subnet. The service name condition matches the service definition. The accept action allows the packet .

The priority attribute controls the order of evaluation. Lower numbers are evaluated first. A rule with a negative priority is evaluated before the zone’s default services and ports. A rule with a positive priority is evaluated after them. This allows a drop rule for a specific source to be evaluated before a general accept rule for the same service .

# Drop all traffic from a specific IP before any accept rules
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" priority="-10" source address="192.168.1.100" drop' --permanent

The log element writes a message to the kernel log when the rule matches. The prefix attribute sets the log message prefix, and the level attribute sets the syslog level. The limit attribute restricts how often the log is written, which prevents log flooding .

# Log and accept HTTP from a specific subnet
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="10.0.1.0/24" service name="http" log prefix="HTTP-ALLOWED: " level="info" accept' --permanent

The limit value="N/m" syntax sets a rate limit. The N is the number of matches, and m is the time unit (s, m, h, or d). A rule with limit value="4/m" matches at most four times per minute. This is used for rate limiting SSH connections or for preventing log flooding .


c. Combining services and rich rules for common scenarios

The exam may present a scenario that requires both a service definition and a rich rule. For example: “Allow MySQL (port 3306) only from the application server at 10.0.1.50.” The port is defined in the mysql service, but the source restriction requires a rich rule.

# Allow MySQL only from the app server
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="10.0.1.50" port port="3306" protocol="tcp" accept' --permanent

The rich rule uses port port="3306" protocol="tcp" instead of service name="mysql". Both forms are valid, but the port form is more explicit when the service definition does not exist or when a different port is required.

Another common scenario: “Allow SSH from the internal subnet, but log and reject SSH from everywhere else.” This requires two rich rules with different priorities.

# Allow SSH from internal subnet
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" priority="-100" source address="10.0.1.0/24" service name="ssh" accept' --permanent

# Log and reject SSH from everywhere else
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" priority="-99" service name="ssh" log prefix="SSH-BLOCKED: " level="warning" limit value="5/m" reject' --permanent

The priority="-100" rule is evaluated first. If the source is in the internal subnet, the packet is accepted. If not, the priority="-99" rule is evaluated, which logs and rejects the packet. Without the priority attribute, the order would be determined by the action, and the reject rule might be evaluated before the accept rule, blocking legitimate traffic .


Complete Example Session

# ============================================
# PART 1: LIST AVAILABLE SERVICES
# ============================================

firewall-cmd --get-services
# Output includes ssh, http, https, mysql, ...
# ============================================
# PART 2: INSPECT A SERVICE DEFINITION
# ============================================

firewall-cmd --info-service=ssh
# Output: ssh
#         ports: 22/tcp
#         protocols:
#         source-ports:
#         modules:
# ============================================
# PART 3: CREATE A CUSTOM SERVICE
# ============================================

sudo tee /etc/firewalld/services/myapp.xml <<'EOF'
<?xml version="1.0" encoding="utf-8"?>
<service>
  <short>My Custom App</short>
  <port port="8080" protocol="tcp"/>
  <port port="8443" protocol="tcp"/>
</service>
EOF

sudo firewall-cmd --reload
firewall-cmd --get-services | grep myapp
# Output: myapp
# ============================================
# PART 4: ADD THE CUSTOM SERVICE
# ============================================

sudo firewall-cmd --zone=public --add-service=myapp --permanent
sudo firewall-cmd --reload
firewall-cmd --zone=public --list-services
# Output includes myapp
# ============================================
# PART 5: ADD A SOURCE-RESTRICTED RICH RULE
# ============================================

sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="10.0.1.0/24" service name="ssh" accept' --permanent
sudo firewall-cmd --reload
# ============================================
# PART 6: ADD A RATE-LIMITING RICH RULE
# ============================================

sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" service name="ssh" limit value="4/m" accept' --permanent
sudo firewall-cmd --reload
# ============================================
# PART 7: ADD A LOGGING RICH RULE
# ============================================

sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="192.168.1.200" log prefix="DROPPED: " level="warning" limit value="5/m" drop' --permanent
sudo firewall-cmd --reload
# ============================================
# PART 8: ADD A PRIORITY-BASED RICH RULE
# ============================================

sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" priority="-10" source address="203.0.113.0/24" drop' --permanent
sudo firewall-cmd --reload
# ============================================
# PART 9: LIST RICH RULES
# ============================================

firewall-cmd --zone=public --list-rich-rules
# Output shows all rich rules with their conditions and actions.

The nine parts trace the workflow: listing services, inspecting a definition, creating a custom service, adding it to a zone, adding source-restricted, rate-limiting, logging, and priority-based rich rules, and listing them.


Quick Reference

Service Definition Elements

ElementAttributesPurpose
<port>port, protocolOpen a port or range
<protocol>valueAllow a protocol (e.g., igmp)
<module>nameLoad kernel module
<destination>ipv4, ipv6Restrict to destination
<short>(text)Human-readable name
<description>(text)Detailed description

Rich Rule Structure

ElementExamplePurpose
ruleruleStart of rule
familyfamily="ipv4"IPv4 or IPv6
prioritypriority="-10"Evaluation order (lower first)
source addresssource address="10.0.0.0/8"Source restriction
service nameservice name="ssh"Match a service
port port protocolport port="3306" protocol="tcp"Match a port
loglog prefix="X: " level="info"Log matched packets
limit valuelimit value="4/m"Rate limit
accept / reject / dropacceptAction

Commands

CommandPurpose
firewall-cmd --get-servicesList all service definitions
firewall-cmd --info-service=<name>Show service details
firewall-cmd --add-rich-rule='...'Add a rich rule
firewall-cmd --remove-rich-rule='...'Remove a rich rule
firewall-cmd --list-rich-rulesList rich rules in zone

Best Practices

โœ… Do This:

sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="10.0.0.0/8" service name="ssh" accept' --permanent
<!-- Use source restriction for services -->          # โœ…
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" service name="ssh" limit value="4/m" accept' --permanent
<!-- Rate limit to slow brute force -->              # โœ…
sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" priority="-10" source address="203.0.113.0/24" drop' --permanent
<!-- Use priority to ensure drop before accept -->   # โœ…

โŒ Don’t Do This:

# Use --add-service=ssh for a public-facing server
<!-- Opens SSH to the entire internet -->            # โŒ
# Forget --permanent on rich rules
<!-- Rule is lost on reload -->                      # โŒ
# Assume rich rule syntax is forgiving
<!-- A single quote error breaks the rule -->         # โŒ

Common Pitfalls

PitfallWhy It HappensFix
Rich rule rejectedSyntax errorVerify quoting and keyword order
Rule not evaluated firstNo priority attributeUse priority with negative value
Rate limit not workingExisting accept ruleRemove broader accept or adjust priority
Custom service not recognizedWrong directory or no reloadPlace in /etc/firewalld/services/, reload
Source address ignoredMissing familyAdd family="ipv4" or "ipv6"

Real-World Examples

1. List Services

firewall-cmd --get-services

2. Inspect Service

firewall-cmd --info-service=http

3. Create Custom Service

sudo tee /etc/firewalld/services/myapp.xml <<'EOF'
<service>
  <port port="8080" protocol="tcp"/>
</service>
EOF

4. Source-Restricted SSH

sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="10.0.0.0/8" service name="ssh" accept' --permanent

5. Rate-Limited SSH

sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" service name="ssh" limit value="4/m" accept' --permanent

6. Log and Drop

sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="192.168.1.200" log prefix="DROPPED: " limit value="5/m" drop' --permanent

7. Priority Drop

sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" priority="-10" source address="203.0.113.0/24" drop' --permanent

8. Allow MySQL from App Server

sudo firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="10.0.1.50" port port="3306" protocol="tcp" accept' --permanent

9. List Rich Rules

firewall-cmd --zone=public --list-rich-rules

10. Remove Rich Rule

sudo firewall-cmd --zone=public --remove-rich-rule='rule family="ipv4" source address="10.0.0.0/8" service name="ssh" accept' --permanent

Visual

Service Definition Structure

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  /etc/firewalld/services/myapp.xml           โ”‚
โ”‚                                              โ”‚
โ”‚  <service>                                   โ”‚
โ”‚    <short>My App</short>                     โ”‚
โ”‚    <port port="8080" protocol="tcp"/>        โ”‚
โ”‚    <port port="8443" protocol="tcp"/>        โ”‚
โ”‚    <module name="nf_conntrack_myapp"/>       โ”‚
โ”‚  </service>                                  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                   โ”‚
                   โ”‚ firewall-cmd --reload
                   โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Service "myapp" available                   โ”‚
โ”‚  --add-service=myapp opens both ports        โ”‚
โ”‚  and loads the kernel module.                โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Rich Rule Evaluation Order

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Incoming packet                             โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                   โ”‚
                   โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Priority < 0 rules (pre-chain)              โ”‚
โ”‚  e.g., priority="-10" drop 203.0.113.0/24    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                   โ”‚
                   โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Zone default rules (services, ports)        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                   โ”‚
                   โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Priority > 0 rules (post-chain)             โ”‚
โ”‚  e.g., priority="32767" log unexpected       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Lower priority numbers are evaluated first. The pre-chain is the correct place for blocking rules.


Summary

ItemValue
Service definition location/etc/firewalld/services/
Service file extension.xml
Port element<port port="8080" protocol="tcp"/>
Rich rule syntaxrule [family="..."] [priority="..."] [source ...] [service/port ...] [log ...] [action]
Source restrictionsource address="10.0.0.0/8"
Rate limitinglimit value="4/m"
Logginglog prefix="X: " level="info"
PriorityNegative = before defaults, positive = after
Actionaccept, reject, drop

Key takeaways:

  • A service definition groups ports, protocols, and modules. The XML file is placed in /etc/firewalld/services/ and the service is available after a reload. This is the only way to tie a kernel module to a firewall rule .
  • Rich rules express what services cannot. Source restrictions, rate limiting, and logging are only possible with rich rules. The syntax is order-sensitive and quote-sensitive .
  • Priority controls evaluation order. A rule with a negative priority is evaluated before the zone’s default services and ports. This is how a drop rule is placed before an accept rule .
  • Rate limiting slows brute force. The limit value="4/m" syntax accepts at most four connection attempts per minute. Without a broader accept rule, the excess attempts fall through to the zone’s default policy .
  • Logging requires a limit. An unlimited log rule on a scanned port fills the disk. The limit attribute must be included in any logging rule .

Remember: The exam tests the syntax, not just the concept. A rich rule with a missing quote or a misspelled keyword fails. A service definition with a malformed XML fails on reload. The safe workflow is to test in runtime, verify with --list-all, then save with --runtime-to-permanent. The syntax is strict, but the diagnostic tools are available.

Was this helpful?


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!