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.
Variable notation
Section titled “Variable notation”Values that differ between environments are represented by the following variables.
| Variable name | Example value | Description |
|---|---|---|
<<SERVER_IP>> | 192.0.2.10 | IP address of the destination Web server |
<<OBSERVER_IP>> | 192.0.2.20 | IPv4 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.
sudo dnf install -y firewalld httpdprintf '%s\n' 'example web server' | sudo tee /var/www/html/index.html >/dev/nullsudo systemctl enable --now firewalldsudo systemctl enable --now httpdNext, 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.
sudo firewall-cmd --set-default-zone=publicsudo firewall-cmd --permanent --zone=public --add-service=sshsudo firewall-cmd --permanent --zone=public --add-service=httpsudo firewall-cmd --reloadConnect over HTTP from the verification client. If the Web page can be retrieved, the rule based on the HTTP service definition is working.
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.
nc -vz -w 5 '<<SERVER_IP>>' 22From 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.
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.
sudo firewall-cmd --zone=public --remove-service=httpTry connecting over HTTP again from the verification client. At this point, a connection failure is the expected result.
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.
sudo firewall-cmd --reloadAfter 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.
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.
sudo firewall-cmd --zone=public --remove-service=httpsudo firewall-cmd --zone=public --add-port=80/tcpConnect 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.
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.
sudo firewall-cmd --reloadThe 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.
sudo firewall-cmd --permanent --new-zone=web-clientsudo firewall-cmd --reloadAllow 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.
sudo firewall-cmd --permanent --zone=web-client --add-service=httpsudo firewall-cmd --permanent --zone=web-client --add-source='<<OBSERVER_IP>>'sudo firewall-cmd --permanent --zone=public --remove-service=httpsudo firewall-cmd --reloadCheck which zone the source is assigned to.
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.
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.
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.
sudo firewall-cmd --permanent --new-ipset=web-clients --type=hash:ipsudo firewall-cmd --permanent --ipset=web-clients --add-entry='<<OBSERVER_IP>>'sudo firewall-cmd --permanent --zone=web-client --add-source=ipset:web-clientssudo firewall-cmd --permanent --zone=web-client --remove-source='<<OBSERVER_IP>>'sudo firewall-cmd --reloadConfirm that the verification client is included in the IP set and that the web-client zone references the IP set as a source.
sudo firewall-cmd --ipset=web-clients --query-entry='<<OBSERVER_IP>>'sudo firewall-cmd --zone=web-client --list-sourcesConnect 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.
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.
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.
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.
test -n "$(ssh-keyscan -T 5 '<<SERVER_IP>>' 2>/dev/null)"Remove the tested rich rule from the runtime configuration.
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.
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.
sudo firewall-cmd --permanent --zone=public --add-service=httpsudo firewall-cmd --permanent --zone=web-client --remove-source=ipset:web-clientssudo firewall-cmd --permanent --delete-zone=web-clientsudo firewall-cmd --permanent --delete-ipset=web-clientssudo firewall-cmd --reloadFrom the verification client, confirm that HTTP is reachable again.
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.
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.
Key points for changing firewalld safely
Section titled “Key points for changing firewalld safely”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.