How To Use Firewalld Rich Rules And Zones For Filtering And NAT

Here we cover the RHCE exam objective “Use firewalld and associated mechanisms such as rich rules, zones and custom rules, to implement packet filtering and configure network address translation (NAT)” in Red Hat Enterprise Linux (RHEL) 7.

Red Hat Certified Engineer RHCE Video Course
Studying for your RHCE certification? Checkout our RHCE video course over at Udemy which is 20% off when you use the code ROOTUSER.

Firewalld Zones And Services

First it is important to understand the concept of zones. Zones are used to define a level of trust for network connections by separating incoming traffic based on the unique characteristics of that traffic. Different zones can then have different rules applied to them. The zone specifies the firewall options that are active within the zone in terms of predefined services, ports and protocols, masquerading/port forwarding and rich rules.

Firewalld filters incoming traffic into different zones depending on the particular rules applied to that zone. An incoming connection will use the following logic when determining which zone it will match. If the source IP address matches a source that has been defined for the zone, then the packet will be routed through that zone. If the source IP address has not matched any zones, next if the incoming interface for the packet matches a filter on that zone then this zone will be used. Otherwise if the incoming traffic does not specifically match any of the defined zones, the default zone will be used. By default the default zone is the public zone.

There are a number of different zones that come preconfigured which allow different services through by default, these are: trusted, internal, home, work, dmz, external, public, block, and drop. For full details on what these do, take a look at the ‘firewalld.zones’ man page. These are preconfigured to allow and deny different services through, however you can also create your own custom zones or modify the existing ones. The default public zone assumes that you do not trust the other computers on the network and only allows a select number of services in.

Zones may have custom or predefined services allowed into them, the predefined services are configured within the /usr/lib/firewalld/services directory.

For instance, here are the contents of the /usr/lib/firewalld/services/kerberos file.

[root@centos7 services]# cat kerberos.xml
<?xml version="1.0" encoding="utf-8"?>
  <description>Kerberos network authentication protocol server</description>
  <port protocol="tcp" port="88"/>
  <port protocol="udp" port="88"/>

We can create our own custom services to use with firewalld by creating a file with similar format to the above .xml file within the /etc/firewalld/services directory, the file name must also end in “.xml”. Take a look at the ‘’ manual page for detailed information on the syntax of these service files. Note that if you create your own .xml files within /etc/firewalld/services you will have to run “restorecon” against them so that the correct SELinux contexts are applied.

Managing Firewalld

In a default installation of RHEL 7, the firewalld service replaces iptables and should be configured to start automatically on boot.

[root@centos7 ~]# systemctl is-active firewalld
[root@centos7 ~]# systemctl is-enabled firewalld

Iptables and firewalld services conflict with each other so you can only use one or the other at any one time. Both are essentially a front end to the same netfilter kernel module and perform similar core tasks, however here we are specifically concerned with firewalld. We can disable iptables by masking it as shown below.

systemctl mask iptables

For further information on basic service management with systemctl, see our guide here.

Firewalld can be managed with the command line tool ‘firewall-cmd’, through the GUI by running ‘firewall-config’, or by manually editing the configuration files in the /etc/firewalld directory. The firewall-cmd command can do the same tasks as the GUI option, however not the other way around, so it’s therefore important to understand how to use the CLI tool for advanced features.

When making changes to firewalld it is important to understand the concepts of the runtime configuration and the permanent configuration. The runtime configuration is the firewall configuration that is actively in use, while the permanent configuration is the configuration that has actually been saved and will be loaded if you were to restart firewalld. Making changes to the runtime configuration only can be great for testing, however in the RHCE exam the configuration needs to be permanent so that it survives a reboot and can be marked.

When making any changes to firewalld with the firewall-cmd command, you can add in --permanent to modify the permanent configuration files stored on disk. If you do not add --permanent you will only modify the running configuration, and the changes will be lost on reboot. If you have put your changes in to the running configuration you can save these to the permanent configuration by using --runtime-to-permanent. If you make changes to the permanent configuration they will not be in use until you run firewall-cmd with --reload to pick up these changes.

Changes can be put in place temporarily with the --timeout option, here we can specify the amount of seconds that a rule should be in effect for prior to it being removed. This is incredibly useful when remote managing a system as you will not accidentally lock yourself out due to a bad rule as the change will revert after the set period defined. The --timeout option can not be used in combination with --permanent.

Here are some other commonly used and useful options that are worth knowing with firewall-cmd. Note that when making changes if you do not specify a zone, the default zone will be used. A zone can be specified with “--zone=ZONE_NAME”.

--get-default-zone will print the current zone that has been set as the default zone, by default the default zone is the public zone.

[root@centos7 ~]# firewall-cmd --get-default-zone

--new-zone=ZONE_NAME can be used to create your own custom zone, however note that this also requires --permanent to be used.

[root@centos7 ~]# firewall-cmd --permanent --new-zone=testing

--set-default can be used to change the default zone from public to what ever other zone you want. This will be the zone that will be used by default on all interfaces unless you specify otherwise.

[root@centos7 ~]# firewall-cmd --permanent --set-default-zone=testing

--get-zones can be used to print out a list of all zones that are available.

[root@centos7 ~]# firewall-cmd --get-zones
block dmz drop external home internal public trusted work

--list-all-zones will show even more information on each zone, for simplicity the above result has been truncacted to just show the result of one of the zones.

[root@centos7 ~]# firewall-cmd --list-all-zones
public (default, active)
  interfaces: eth0
  services: dhcpv6-client dns http https ssh
  masquerade: no
  rich rules:

--get-active-zones lists only those zones that are currently in use and have a binding to an interface.

[root@centos7 ~]# firewall-cmd --get-active-zones
  interfaces: eth0

--get-services can be used to list the predefined services that available for use in firewall rules, these services are tied to ports.

[root@centos7 ~]# firewall-cmd --get-services
RH-Satellite-6 amanda-client bacula bacula-client dhcp dhcpv6 dhcpv6-client dns freeipa-ldap freeipa-ldaps freeipa-replication ftp high-availability http https imaps ipp ipp-client ipsec iscsi-target kerberos kpasswd ldap ldaps libvirt libvirt-tls mdns mountd ms-wbt mysql nfs ntp openvpn pmcd pmproxy pmwebapi pmwebapis pop3s postgresql proxy-dhcp radius rpc-bind rsyncd samba samba-client smtp ssh telnet tftp tftp-client transmission-client vdsm vnc-server wbem-https

--list-services can be used to list the services that are allowed in the zone, the ones listed below are default for the public zone.

[root@centos7 ~]# firewall-cmd --list-services
dhcpv6-client dns http https ssh

--add-service can be used to add additional services into the specified zone, in this example we have added the predefined kerberos service to the public zone.

[root@centos7 ~]# firewall-cmd --add-service=kerberos

[root@centos7 ~]# firewall-cmd --list-services
dhcpv6-client dns http https kerberos ssh

--remove-service can be used to reverse this and remove access by a service into a zone.

[root@centos7 ~]# firewall-cmd --remove-service=kerberos

[root@centos7 ~]# firewall-cmd --list-services
dhcpv6-client dns http https ssh

--add-source=IP can be used to add an IP address or range of addresses to a zone. This will mean that if any source traffic enters the systems that matches this, the zone that we have set will be applied to that traffic. In this case we set the ‘testing’ zone to be associated with traffic from the range.

[root@centos7 ~]# firewall-cmd --permanent --zone=testing --add-source=

--list-sources can be used to list the source addresses that have been applied to a zone.

[root@centos7 ~]# firewall-cmd --permanent --zone=testing --list-sources

--remove-source=IP works in the same way as --add-source, except that it is used to remove a source IP address or address range that has been added to a zone.

[root@centos7 ~]# firewall-cmd --permanent --zone=testing --remove-source=

It should be noted that multiple IP addresses or ranges can be added as the source of a single zone.

[root@centos7 ~]# firewall-cmd --add-port=9000/tcp

--list-ports can be used to list the currently allowed ports into the specified zone, we just allowed TCP 9000 in through the default zone and we can see that this is listed here.

[root@centos7 ~]# firewall-cmd --list-ports

--remove-port can remove ports that have been added with --add-port.

[root@centos7 ~]# firewall-cmd --remove-port=9000/tcp

For full detailed information on all of these options and more, I suggest remembering that you can look these up in the manual page which will be available to you in the RHCE exam, however it is recommended that you get to know these options and practice using them beforehand.

man 1 firewall-cmd

There are also some examples at the bottom of this manual page, here are some more examples of putting these commands to use to do something practical.

The below example creates a new ‘test’ zone and applies to traffic from, we then allow the ssh service and TCP port 9000 into this zone.

[root@centos7 ~]# firewall-cmd --permanent --new-zone=test

[root@centos7 ~]# firewall-cmd --permanent --zone=test --add-source=

[root@centos7 ~]# firewall-cmd --permanent --zone=test --add-service=ssh

[root@centos7 ~]# firewall-cmd --permanent --add-port=9000/tcp --zone=test

[root@centos7 ~]# firewall-cmd --reload

As the changes have been made with --permanent, the runtime configuration needs to be reloaded with --reload.

Based on the ssh service name specified here, it is probably allowing TCP port 22 in, however we can confirm this by checking the configuration file for the service.

[root@centos7 ~]# cat /usr/lib/firewalld/services/ssh.xml
<?xml version="1.0" encoding="utf-8"?>
  <description>Secure Shell (SSH) is a protocol for logging into and executing commands on remote machines. It provides secure encrypted communications. If you plan on accessing your machine remotely via SSH over a firewalled interface, enable this option. You need the openssh-server package installed for this option to be useful.</description>
  <port protocol="tcp" port="22"/>

We can then view a summary of these changes by viewing all settings on the test zone.

[root@centos7 ~]# firewall-cmd --list-all --zone=test
  services: ssh
  ports: 9000/tcp
  masquerade: no
  rich rules:

As we can see, the source is, the ssh service is allowed in, as well as the port TCP 9000. Interestingly, we could also add port TCP 22 despite the service ssh already being in place which is essentially the same thing.

These type of rules are fairly basic, next up we go even deeper with rich rules which provide much more flexibility.

Firewalld Rich Rules

Rich rules provide a much greater level of control through more custom granular options. Rich rules can also be used to configure logging, masquerading, port forwarding, and rate limiting.

For further information on the syntax of rich rules and examples, see the manual page for firewalld.richlanguage.

man 5 firewalld.richlanguage

This will be a useful resource during the exam, however you should know how to write rich rules going in to save time so don’t rely purely on the manual page.

Once multiple rules are in place they will be processed in a certain order. Port forwarding and masquerading rules will be applied first, followed by any logging rules, then any allow rules, and finally any deny rules. A packet will use the first rule it applies to in this order, if it does not match a rule it will hit the default deny.

Here are some examples of working with rich rules.

--add-rich-rule=’RULE’ can be used to add a specified rule, here we are allowing traffic from the range into only through TCP ports 8080 through to 8090.

[root@centos7 ~]# firewall-cmd --permanent --zone=testing --add-rich-rule='rule family=ipv4 source address= destination address= port port=8080-8090 protocol=tcp accept'

--list-rich-rules can be used to show all rich rules for specified zone, here we see the rich rule that we just created.

[root@centos7 ~]# firewall-cmd --permanent --zone=testing --list-rich-rules
rule family="ipv4" source address="" destination address="" port port="8080-8090" protocol="tcp" accept

--remove-rich-rule can be used to remove the rule, essentially it’s the same syntax as --add-rich-rule but with removing to remove the already existing rule.

[root@centos7 ~]# firewall-cmd --permanent --zone=testing --remove-rich-rule='rule family=ipv4 source address= destination address= port port=8080-8090 protocol=tcp accept'

Here we create a rich rule to reject any traffic that comes from

[root@centos7 ~]# firewall-cmd --permanent --zone=testing --add-rich-rule='rule family=ipv4 source address= reject'

Reject will reply back with an ICMP packet noting the rejection, while a drop will just silently drop the traffic and do nothing else, so a drop may be preferable in terms of security as a reject response confirms the existence of the system as it is rejecting the request.

It’s also important to note that if source or destination addresses are used within a rich rule, the family must be specified as either ipv4 or ipv4, depending on the addressing in use for the rule.

Rich rules can also be used to rate limit traffic, here we limit incoming SSH connections to 10 per minute.

[root@centos7 ~]# firewall-cmd --permanent --add-rich-rule='rule service name=ssh limit value=10/m accept'

Rich rules can also be used to send messages to a log file, and this logging can also be rate limited. Here we log SSH connections from but at a rate of no more than 50 log entries per minute. Only logs of level ‘info’ or more important will be logged.

[root@centos7 ~]# firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="" service name="ssh" log prefix="ssh" level="info" limit value="50/m" accept'

Network Address Translation (NAT)

NAT can be done with firewalld with either masquerading or port forwarding, both of which can be configured with firewall-cmd. It is important to note that masquerading can only be done with IPv4 and not IPv6.

Masquerading With Firewalld

Masquerading will forward packets that are not directed to an IP address associated to the system itself onto the intended destination. The source IP address of the packets that are sent through our system will be changed to the IP address of our system, rather than the IP address of the original traffic source. Responses to these packets will then go through our system and the destination address will be modified so that the traffic will be sent back to the original host that initiated the traffic.

Masquerading can be enabled on a zone with the use of --add-masquerade

[root@centos7 ~]# firewall-cmd --permanent --zone=testing --add-masquerade

We can confirm that masquerading has been successfully enabled with --query-masquerade. In this case we want to query the permanent configuration as this is what we modified and we have not yet performed a reload.

[root@centos7 ~]# firewall-cmd --permanent --query-masquerade

In this example any packet sent to addresses defined in the zone ‘testing’ will be masqueraded. Rich rules can be used for more granular control.

[root@centos7 ~]# firewall-cmd --permanent --zone=testing --add-rich-rule='rule family=ipv4 source address= masquerade'

In this example anything from the source specifically will be masqueraded.

Port Forwarding With Firewalld

As the name implies, port forwarding will forward all traffic destined to a specific port to either a different port on the local system or to some port on an external system. Note that if you’re forwarding to an external system, you will also need to enable masquerading as covered above.

In the below example, the local system will forward all traffic sent to port 22 to, so any traffic sent to this server on port 22 will be forwarded to the external system on TCP 2222. In this instance the port forwarding rule will only apply to sources specified in the ‘test’ zone.

[root@centos7 ~]# firewall-cmd --permanent --zone=testing --add-forward-port=port=22:proto=tcp:toport=2222:toaddr=

We can confirm that masquerading has been successfully enabled with the use of --query-forward-port

[root@centos7 ~]# firewall-cmd --permanent --zone=testing --query-forward-port=port=22:proto=tcp:toport=2222:toaddr=

This doesn’t seem to be that useful as it only seems to match if you put in the full entry that was used when adding the port forwarder. A better option is to use --list-all as demonstrated previously, as this will show us any forwarded ports.

[root@centos7 ~]# firewall-cmd --permanent --list-all --zone=testing
  masquerade: yes
  forward-ports: port=22:proto=tcp:toport=2222:toaddr=
  rich rules:
        rule family="ipv4" source address="" masquerade

This is also another way of seeing if masquerading has been enabled.

Rich rules can again be used for more granular control. In this instance we can specify a specific source address within the test zone rather than the whole zone.

[root@centos7 ~]# firewall-cmd --permanent --zone=testing --add-rich-rule='rule family=ipv4 source address= forward-port port=22 protocol=tcp to-port=2222 to-addr='

Alternatively we can not use the optional ‘to-addr’ parameter, in which case the port forwarding will take place entirely on localhost. We can view the rich rules in place with --list-rich-rules.

[root@centos7 ~]# firewall-cmd --permanent --zone=testing --list-rich-rules
rule family="ipv4" source address="" masquerade
rule family="ipv4" source address="" forward-port port="22" protocol="tcp" to-port="2222" to-addr=""


By using the firewall-cmd command we have been able to create basic rules in firewalld as well as rich rules with very specific custom options. We have also been able to make use of masquerading and port forwarding in order to send traffic elsewhere by performing network address translation (NAT).

This post is part of our Red Hat Certified Engineer (RHCE) exam study guide series. For more RHCE related posts and information check out our full RHCE study guide.

Leave a comment ?


  1. There is a bug on RHEL 7.1 and RHEL 7.2 that prevents the iptables service from being masked if the package iptables-services is not installedwhen and SELinux is enforcing.

    The workaround is either to install iptables-services to be able to mask the service, or set SELinux to permissive.

  2. I have a zone with very restricted access but one port/service I want to allow all IPs access. What is the standard way to do the with firewalld?

    • If i understand you correctly, the below example should allow port 9000 in from any source address.

      firewall-cmd –permanent –add-port=9000/tcp
      firewall-cmd –reload

  3. Log prefix does not appear for SSH sessions:

    [root@system1 ~]# firewall-cmd –list-all
    public (default, active)
    interfaces: enp0s5
    services: dhcpv6-client
    masquerade: no
    rich rules:
    rule family=”ipv4″ service name=”ssh” log prefix=”SSH_” level=”debug” limit value=”2/m” accept

    [root@system1 ~]# tail -fn0 /var/log/messages
    Jan 9 05:25:36 system1 systemd-logind: New session 12 of user root.
    Jan 9 05:25:36 system1 systemd: Starting Session 12 of user root.
    Jan 9 05:25:36 system1 systemd: Started Session 12 of user root.

    • debug level needs to be enabled in syslog :
      I edited /etc/rsyslog.conf and restarted syslog service

      *.info;mail.none;authpriv.none;cron.none /var/log/messages

      *.debug;mail.none;authpriv.none;cron.none /var/log/messages

  4. I create a test using your example. The only difference is the to-addr=ip/mask

    # firewall-cmd –permanent –zone=testing –add-rich-rule=’rule family=ipv4 source address= forward-port port=22 protocol=tcp to-port=2222 to-addr=′

    Error: INVALID_ADDR=

    So the “to-address” field must be a specific address? Please advise.

  5. Correct error – typo!
    Error: INVALID_ADDR=

    • is an invalid ip address. /32 specifies only ONE host, but an ip ending with .0 is an invalid address for a host. Either use an allowed number (1 – 254), if you want to specify only one host, or use another net mask (for example, /24), if you want to specify a subnet.

  6. I want to limit the number of ping from a specific host ( So this is what i have used:
    firewall-cmd –add-rich-rule=’rule family=ipv4 source address= protocol value=icmp limit value=1/m accept’

    But it did not give the desired results. Any suggestions?

  7. I want to add range of ip addresses instead of only one in the firewall rich rule to allow service for example, from ip address to 150 how to do that in rhel7? Thanks in advance.

    • Not sure if specifying works? Had a quick search and didn’t see anything, if you want a range outside of a CIDR range perhaps you need to specify individual IPs, not ideal but will work.

  8. Hi Jarrod,
    Thanks for the post!

    I’m trying to do ssh tunnel into server behind NAT.
    using iptables it works, but using firewalld it doesn’t.

    Inside my host (that can ssh into guest

    $ firewall-cmd –permanent –zone=public –add-forward-port=port=222:proto=tcp:toport=22:toaddr=′
    $ firewall-cmd –permanent –zone=public –add-masquerade
    $ firewall-cmd –reload
    $ firewall-cmd –list-all

    public (active)
    target: default
    icmp-block-inversion: no
    interfaces: enp4s0f0
    services: ssh dhcpv6-client
    ports: 8139/tcp
    masquerade: yes
    forward-ports: port=222:proto=tcp:toport=22:toaddr=
    rich rules:


    From my laptop, trying to connect to the guest, via my host port 222 – ssh is refused:

    ssh -l stack my-host -p 222
    ssh: connect to host my-host port 222: Connection refused

  9. I think there is an error in „ipv4 or ipv4“:

    „It’s also important to note that if source or destination addresses are used within a rich rule, the family must be specified as either ipv4 or ipv4, depending on the addressing in use for the rule.“

    Thanks for this great manual.


Leave a Comment

NOTE - You can use these HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>