Skip to content

Practical Guide to firewalld Zones, Services, and Rich Rules

firewalld is a standard firewall management mechanism for controlling permitted communications on RHEL-compatible Linux systems. Rather than simply opening and closing ports, it lets you combine zones, services, sources, IP sets, and rich rules to build policies based on traffic sources and intended use.

However, an incorrect configuration change during remote administration can also block SSH. In this article, we use HTTP traffic as an example and work through runtime settings, the difference between allowing traffic by service and by port number, source assignments, IP sets, and rich rules step by step while keeping the SSH management path available.

Difference between runtime and permanent settings

Section titled “Difference between runtime and permanent settings”

firewalld has runtime settings that represent the rules currently in effect and permanent settings that are used again after the configuration is reloaded.

Runtime settings take effect immediately, making them suitable for temporary changes and preliminary testing. However, when you run firewall-cmd --reload, the runtime configuration is rebuilt from the permanent configuration.

Changes made with --permanent, on the other hand, are saved to the persistent configuration. To apply those changes to the current runtime configuration, you normally need to run a command such as firewall-cmd --reload.

For safer changes, the basic approach is to keep the management path available, test the impact using runtime settings first, and then apply the change to the permanent configuration if no problems are found.

Choosing between a service and a direct port number

Section titled “Choosing between a service and a direct port number”

With firewalld, you can allow traffic by service name, such as --add-service=http, or specify a port number and protocol directly, such as --add-port=80/tcp.

For standard services, using a service makes the purpose of the rule easier to understand. For custom applications or listening ports that do not have a standard service definition, you can specify the port number directly.

Values that differ between environments are represented by the following variables.

Variable nameExample valueDescription
<<SERVER_IP>>192.0.2.10IP address of the destination Web server
<<OBSERVER_IP>>192.0.2.20IPv4 address of the client used to verify HTTP connectivity

Step 1: Create a baseline state for firewalld and the HTTP service

Section titled “Step 1: Create a baseline state for firewalld and the HTTP service”

First, install firewalld and Apache HTTP Server on the server and place a simple page for HTTP connectivity checks.

Terminal window
sudo dnf install -y firewalld httpd
printf '%s\n' 'example web server' | sudo tee /var/www/html/index.html >/dev/null
sudo systemctl enable --now firewalld
sudo systemctl enable --now httpd

Next, use the standard public zone as the baseline and allow SSH and HTTP in the permanent configuration. By explicitly allowing SSH first, you can separate the management path from the HTTP-related changes that follow.

Terminal window
sudo firewall-cmd --set-default-zone=public
sudo firewall-cmd --permanent --zone=public --add-service=ssh
sudo firewall-cmd --permanent --zone=public --add-service=http
sudo firewall-cmd --reload

Connect over HTTP from the verification client. If the Web page can be retrieved, the rule based on the HTTP service definition is working.

Terminal window
curl --fail --silent --show-error --connect-timeout 5 --max-time 10 'http://<<SERVER_IP>>/'

From the same verification client, also confirm that TCP/22 is reachable.

Terminal window
nc -vz -w 5 '<<SERVER_IP>>' 22

From the management terminal, confirm that the SSH server returns a protocol response. Use ssh-keyscan to verify only that the SSH service is reachable, without performing authentication or updating the known-hosts file.

Terminal window
test -n "$(ssh-keyscan -T 5 '<<SERVER_IP>>' 2>/dev/null)"

This check does not confirm whether SSH user authentication succeeds. It only confirms that the management terminal can communicate with the SSH service and reach the SSH host-key exchange stage.

Step 2: Temporarily block HTTP using runtime settings only

Section titled “Step 2: Temporarily block HTTP using runtime settings only”

Remove the HTTP service from the public zone without using --permanent. This change is applied only to the runtime configuration.

Terminal window
sudo firewall-cmd --zone=public --remove-service=http

Try connecting over HTTP again from the verification client. At this point, a connection failure is the expected result.

Terminal window
curl --fail --silent --show-error --connect-timeout 5 --max-time 10 'http://<<SERVER_IP>>/'

Now run firewall-cmd --reload. The HTTP removal made only in the runtime configuration is discarded, and the permanent configuration is loaded back into the runtime configuration.

Terminal window
sudo firewall-cmd --reload

After the reload, connect over HTTP again. If connectivity is restored, this confirms that the runtime-only change was lost after the reload and that the HTTP permission in the permanent configuration was preserved.

Terminal window
curl --fail --silent --show-error --connect-timeout 5 --max-time 10 'http://<<SERVER_IP>>/'

This behavior lets you test the communication impact using only the runtime configuration before changing the permanent configuration.

Step 3: Allow traffic by specifying a port number directly

Section titled “Step 3: Allow traffic by specifying a port number directly”

Next, verify how to allow traffic by specifying the port number and protocol directly instead of using a service name.

First, remove only the HTTP service permission from the runtime configuration and allow TCP/80 directly by port number instead. Because the permanent configuration is left unchanged, this modification can be reverted with a reload.

Terminal window
sudo firewall-cmd --zone=public --remove-service=http
sudo firewall-cmd --zone=public --add-port=80/tcp

Connect over HTTP from the verification client. Even though the HTTP service has been removed from the runtime configuration, communication still succeeds because TCP/80 is allowed directly.

Terminal window
curl --fail --silent --show-error --connect-timeout 5 --max-time 10 'http://<<SERVER_IP>>/'

Discard the runtime change made with the direct port rule and return to the HTTP service permission in the permanent configuration.

Terminal window
sudo firewall-cmd --reload

The advantage of --add-service is that the purpose of a rule can be managed by service name. --add-port lets you control any port directly, but the rule’s purpose is harder to understand from the rule alone. When a standard service exists, using that service usually makes the configuration easier to manage.

Step 4: Allow HTTP only for a specific client with a source-specific zone

Section titled “Step 4: Allow HTTP only for a specific client with a source-specific zone”

Next, create a zone that is selected based on the source address. In this example, we create a custom zone named web-client.

Terminal window
sudo firewall-cmd --permanent --new-zone=web-client
sudo firewall-cmd --reload

Allow HTTP in the web-client zone and assign the verification client’s IP address as a source. At the same time, remove HTTP from the public zone.

This results in a configuration where HTTP is not allowed in the normal public zone and only the specified source can connect to HTTP through the web-client zone.

Terminal window
sudo firewall-cmd --permanent --zone=web-client --add-service=http
sudo firewall-cmd --permanent --zone=web-client --add-source='<<OBSERVER_IP>>'
sudo firewall-cmd --permanent --zone=public --remove-service=http
sudo firewall-cmd --reload

Check which zone the source is assigned to.

Terminal window
sudo firewall-cmd --get-zone-of-source='<<OBSERVER_IP>>'

Connect over HTTP from the verification client. HTTP is not allowed in the public zone, but this client is classified into the web-client zone by its source assignment, so it can connect over HTTP.

Terminal window
curl --fail --silent --show-error --connect-timeout 5 --max-time 10 'http://<<SERVER_IP>>/'

After changing the HTTP-related zone configuration, also confirm that the SSH service remains reachable from the management terminal. This is a read-only connectivity check and does not modify system state.

Terminal window
test -n "$(ssh-keyscan -T 5 '<<SERVER_IP>>' 2>/dev/null)"

Step 5: Manage multiple traffic sources together with an IP set

Section titled “Step 5: Manage multiple traffic sources together with an IP set”

As the number of traffic sources grows, you can group them into a firewalld IP set instead of listing each IP address individually in zones or rich rules.

In this example, create an IP set named web-clients of type hash:ip and add the verification client to it. Then replace the direct source assignment in the web-client zone with a reference to the IP set.

Terminal window
sudo firewall-cmd --permanent --new-ipset=web-clients --type=hash:ip
sudo firewall-cmd --permanent --ipset=web-clients --add-entry='<<OBSERVER_IP>>'
sudo firewall-cmd --permanent --zone=web-client --add-source=ipset:web-clients
sudo firewall-cmd --permanent --zone=web-client --remove-source='<<OBSERVER_IP>>'
sudo firewall-cmd --reload

Confirm that the verification client is included in the IP set and that the web-client zone references the IP set as a source.

Terminal window
sudo firewall-cmd --ipset=web-clients --query-entry='<<OBSERVER_IP>>'
sudo firewall-cmd --zone=web-client --list-sources

Connect over HTTP from the verification client. Even though the IP address is no longer registered directly in the zone, it is a member of the web-clients IP set, so the HTTP permission in the web-client zone still applies.

Terminal window
curl --fail --silent --show-error --connect-timeout 5 --max-time 10 'http://<<SERVER_IP>>/'

An IP set lets you manage multiple sources that share the same policy as one collection. Sources can be added to or removed from the IP set itself, helping keep the zone’s rule structure simple.

Step 6: Explicitly reject specific traffic with a rich rule

Section titled “Step 6: Explicitly reject specific traffic with a rich rule”

Use rich rules when you need conditions that are more specific than service-based permissions.

In this example, the HTTP service remains allowed in the web-client zone, while only TCP/80 traffic from the verification client is rejected with a runtime rich rule. Because the rule is not stored in the permanent configuration, its impact can be tested temporarily.

Terminal window
sudo firewall-cmd --zone=web-client --add-rich-rule='rule family="ipv4" source address="<<OBSERVER_IP>>" port port="80" protocol="tcp" reject'

Try connecting over HTTP from the verification client. Even though HTTP is allowed as a service, the more specific rejection condition applies, so a connection failure is the expected result.

Terminal window
curl --fail --silent --show-error --connect-timeout 5 --max-time 10 'http://<<SERVER_IP>>/'

Even while HTTP is rejected, confirm that the SSH service is still reachable from the management terminal. This check is also read-only.

Terminal window
test -n "$(ssh-keyscan -T 5 '<<SERVER_IP>>' 2>/dev/null)"

Remove the tested rich rule from the runtime configuration.

Terminal window
sudo firewall-cmd --zone=web-client --remove-rich-rule='rule family="ipv4" source address="<<OBSERVER_IP>>" port port="80" protocol="tcp" reject'

Confirm that HTTP connectivity is restored after removing the rule.

Terminal window
curl --fail --silent --show-error --connect-timeout 5 --max-time 10 'http://<<SERVER_IP>>/'

Rich rules can combine sources, destinations, ports, and protocols to provide more detailed control than services alone. However, as the number of rules increases, their evaluation relationships become harder to understand. Requirements that can already be expressed with services or zones should not be moved to rich rules unnecessarily.

Step 7: Remove the test zone and IP set and return to the baseline configuration

Section titled “Step 7: Remove the test zone and IP set and return to the baseline configuration”

Finally, restore the HTTP permission in the public zone and remove the zone and IP set used for testing.

Terminal window
sudo firewall-cmd --permanent --zone=public --add-service=http
sudo firewall-cmd --permanent --zone=web-client --remove-source=ipset:web-clients
sudo firewall-cmd --permanent --delete-zone=web-client
sudo firewall-cmd --permanent --delete-ipset=web-clients
sudo firewall-cmd --reload

From the verification client, confirm that HTTP is reachable again.

Terminal window
curl --fail --silent --show-error --connect-timeout 5 --max-time 10 'http://<<SERVER_IP>>/'

Finally, confirm that the SSH service remains reachable from the management terminal. This final check also does not modify any state.

Terminal window
test -n "$(ssh-keyscan -T 5 '<<SERVER_IP>>' 2>/dev/null)"

This restores the baseline configuration in which SSH and HTTP are allowed as services in the public zone.

When modifying firewalld on a remote server, do not focus only on the rules for the target service, such as HTTP. It is equally important to keep management paths such as SSH independently available.

If you cannot fully predict the impact of a change, do not modify the permanent configuration immediately. Test the communication impact with the runtime configuration first, then make the change persistent.

Use services for standard services and direct port specifications for custom listening ports. If there are only a few traffic sources, you can specify sources directly. As the number of sources grows, grouping them into an IP set makes them easier to manage.

Add rich rules only when you need to combine multiple conditions. This helps avoid making the rule structure unnecessarily complex.

It is also important to treat firewall connectivity checks and SSH authentication checks separately. ssh-keyscan can verify that the SSH service is reachable and returns a protocol response, but it does not prove that user authentication succeeds. In normal operations, verify the host key separately through a trusted method and use an appropriate authentication method for management connections.

Category: Linux