This article will discuss some basics of firewalld from the perspective of someone who is used to iptables for a long time.  We will be covering all the basics for firewalld and explaining how it differs from iptables.  I am old hat unix guy and the firewalld vs iptables battle rages strong between myself and the younger folks in my office.

Over the past couple of years there has been a lot of talk about firewalld, the "new" default firewall for the Red Hat / CentOS distros since version 7.  Here we will briefly discuss firewalld and then jump into some basic commands.

Firewalld is basically just a wrapper for iptables to allow a more intuitive and easier way to manage iptables rules.  For example, you can specify services by name rather than its ports and protocols.  Coming from an old, stubborn SysAdmin who has used iptables for a couple decades, I do not find this to be the case.  I often just remove firewalld and reinstall iptables on my systems.  In iptables, the rules were read in order, from top to bottom of the chain.  Firewalld removes this dependency on order, which also simplifies the management of rules.  All of this along with it's multi-zone configuration approach makes firewalld a flexible and accessible replacement for iptables.

In our iptables basics tutorial we discussed things like chains, rules and actions.  All of those concepts still exist although the names and syntax to manipulate them are much different.

Runtime VS Permanent Firewalld Configurations

There are two separate modes supported by firewalld, runtime and permanent.  When the firewall starts it loads the permanent configuration from a file into the runtime.  By default any changes you make are made to the runtime configuration, unless you specify --permanent parameter.  Alternatively, you can make changes to the runtime configuration for testing, then write them to the permanent configuration when you are sure they are correct by using:

# firewall-cmd --runtime-to-permanent

Changes made to the runtime configuration are instantly live.  If you make changes directly to the permanent configuration (using the --permanent parameter), you need to reload the service in order to make the changes live in runtime.

# firewall-cmd --reload

Some commands can ONLY be run against the permanent configuration.  Changing the zones target is one of them.  If you attempt to run a command that can only be run against the permanent configuration, you will see a message stating that:

# firewall-cmd --zone=dmz --set-target=REJECT
usage: see firewall-cmd man page
Option can be used only with --permanent.

Firewalld Zones

In iptables we had rules organized into chains, with firewalld a zone replaces the chain as the main container.  An incoming packet is linked to a zone by it's source.  The source can be an interface, IP address, or IP range.  By default all active interfaces will be assigned to the default zone.

This allows us to create two types of zones:

Interface Zone: This is a zone that is tied to an interface.

Source Zone: This is a zone that is tied to a source IP or IP/mask range.

An important thing to remember is that source zones take precedence over interface zones.

Firewalld Targets

When a packet arrives at a zone and there are no rules defined to match it, the zone uses it's target to determine what action to take.

ACCEPT - A zone configured with an ACCEPT target will accept every packet that does not match any rule.

REJECT - A zone configured with a REJECT target every packet not matching any rule.

DROP - A zone configured with a DROP target will drop every packet not matching a rule.

REJECT vs DROP ??

When using REJECT, the system will send a packet notifying the source of the packet that it is rejected.  If you use DROP, the system just dumps the packet and does not notify the sender.

DEFAULT - This is a target that has caused a lot of debate.  It is terribly documented and I have seen different people say it exists for different reasons.  If you see default, it is the same as reject according to everything I have read.  If you have a zone that has a target of default, I recommend changing it to one of the three above (ACCEPT, REJECT, or DROP).  We will cover how to do that later in this tutorial. I don't like confusion or vaguely defined options.  For the rest of this tutorial we will ignore the target-default and recommend you do as well.  At least until the firewalld documentation addresses it concisely.

Start, Stop, Firewalld & Enable Firewalld at Boot

Let's dive in and learn some basic firewalld commands.

Starting, stopping and restarting the service can be done with the systemctl command, just like any other service:

systemctl start firewalld
systemctl stop firewalld
systemctl restart firewalld

You can also enable and disable the service at boot with the systemctl command as well:

systemctl enable firewalld
systemctl disable firewald

When you are ready to interact with the firewall itself and change rules, you will use the firewall-cmd command.

First, let's find out what zones we have to work with.

Listing all zones in Firewalld

# firewall-cmd --get-zones
block dmz drop external home internal public trusted work

These are the predefined zones you can use to add rules to. You can add custom zones as well, we will discuss that further down the page. Let's find out which is the default zone.

List default Firewalld zone

# firewall-cmd --get-default-zone
public

By default firewalld makes public the default zone. 

List active Firewalld zone

# firewall-cmd --get-active-zone
public
  interfaces: em1

The default configuration will connect any and all interfaces with the public zone.  Sources are NOT defined in the default config to ensure security. 

The output above shows us that the public zone is active and tied to the em1 network interface.

Let's find out if there are any rules listed in the public zone, which is our active zone.

List Configuration for a Zone

# firewall-cmd --list-all --zone="public"
public (active)
  target: %%REJECT%%
  interfaces: em1
  sources: 
  services: ssh
  ports: 
  protocols: 
  masquerade: no
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules: 

This shows us the configuration for the public zone.  Let's read through it line by line so we understand what we are seeing:

  • public (active) - This means this is an active zone, an active zone is one that has a source attached to it like an interface or IP range.
  • target: %%REJECT%% - This means that any packet that does not match a rule with be rejected.
  • interfaces: em1 - Any packets arriving on the em1 network interface, will be inspected by rules in this zone.
  • source: - This is blank because we did not specify any IP sources. 
  • services: ssh - This is telling us that SSH packets will be allowed through the firewall.  
  • ports: - List of ports allowed to traverse the firewall.  We allowed ssh through using the name "ssh" but ports is useful if you want to allow a non-standard port for a custom application.
  • protocols: - List of allowed protocols which is currently empty.
  • masquerade: no - IP masquerading is disabled, if enabled it will allow packets to be forwarded.  This is useful when using your system as a router.
  • forward-ports: - List of ports that are being forwarded, currently empty.
  • source-ports: - List of source ports to identify packets coming into the interface on a specific port.
  • icmp-blocks: - A list of blocked ICMP types.
  • rich rules: - Rich rules are advanced configurations you can add to a zone.  We will cover these in a complete tutorial in the future.

Opening ports with firewall-cmd

You can open ports in two ways and a touched on before.  You can add them using the name of a known service, or you can add them using the port number.

Let's assume we have a web server and we want to open port 80 on the public zone.

# firewall-cmd --zone=public --add-service=http
success

We can also add this using the port/protocol statement if we wished.

# firewall-cmd --zone=public --add-port=80/tcp
success

Both of the commands above do the same thing.  

Remember, these commands will not survive a reboot. If we want to save them we have to either specify the permanent parameter and reload or copy the runtime to permanent configuration.

Closing ports

Closing ports is very similar:

# firewall-cmd --zone=public --remove-port=80/tcp
success

or by name:

# firewall-cmd --zone=public --remove-service=http
success

Timeout

Another cool feature in firewalld is the timeout function.  This allows you to quickly add a rule to the runtime configuration and remove it is a specified amount of time.  Let's say you wanted to allow your friend to upload a file to you via FTP.  You can open the ftp service on the firewall and have it automatically close after X amount of minutes.

# firewall-cmd --zone=public --add-service=ftp --timeout=5m
success

Timeout can be specified in seconds(s), minutes(m) or hours(h).

NOTE: I do not recommend ever using FTP. 🙂

Setting a default zone for Firewalld

If you would like to use a different zone as your default, you can do so easily:

# firewall-cmd --set-default-zone=home
success

Creating a custom zone

The predefined zones are usually sufficient for most configurations, but you may desire a custom zone.  It is easy to add a zone, but this must be done using the permanent configuration.

Here we will add a zone called Putorius, we will use it for the rest of the tutorial.

# firewall-cmd --permanent --new-zone=Putorius
success

Set a description for a zone

In large configurations it may be helpful to have a description for each zone.  

# firewall-cmd --permanent --zone=Putorius --set-description="This is our custom zone for testing"
success
# firewall-cmd --reload

In the future, you can read the description like so:

# firewall-cmd --permanent --zone=Putorius --get-description

This is our custom zone for testing

Set default target for a zone

We created a custom zone, let's get wild and set the default target to DROP.

# firewall-cmd --permanent --zone=Putorius --set-target=DROP
success
# firewall-cmd --reload

Let's take a look at our zone:

# firewall-cmd --zone=Putorius --list-all
Putorius
  target: DROP
  icmp-block-inversion: no
  interfaces: 
  sources: 
  services: 
  ports: 
  protocols: 
  masquerade: no
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules:

Notice that the target is set to DROP and also that it is not active.

Creating an IP source zone

Our local LAN ip scheme is 192.168.0.0/24.  Let's use this new zone as a source zone to control access from our local LAN.

firewall-cmd --zone=Putorius --add-source=192.168.0.0/24
success

NOTE: Did you notice we did this to the runtime config?  Also, remember that if a packet comes in on interface em1 from our local LAN, this SOURCE ZONE will take precedence over the INTERFACE ZONE (See Zones section above).

Let's take a look at our zone.

# firewall-cmd --zone=Putorius --list-all
Putorius (active)
  target: DROP
  icmp-block-inversion: no
  interfaces: 
  sources: 192.168.0.0/24
  services: 
  ports: 
  protocols: 
  masquerade: no
  forward-ports: 
  source-ports: 
  icmp-blocks: 
  rich rules:

Notice that the zone is active, that is because we added a source to it.  A zone will become active when a source or interface is associated with it.

Let's allow anyone in our local LAN to SSH into our system.

# firewall-cmd --zone=Putorius --add-service=ssh
success

Now anyone coming from a source IP inside of our local LAN (192.168.0.0/24) will be allowed SSH access.

Firewalld Rich Rules

Rich rules allow more granular control over the firewall.  For example, let's say you want to block all traffic from 192.168.0.7.  You could make a whole new source zone for just that IP address.  This would become cumbersome especially if you have a lot of specific rules.  Rich rules allow you to create very specific rules into a zone.

The Putorius zone we created allows anyone in our local LAN to SSH into our system.  But some jerk at 192.168.0.7 keeps sending broadcast messages to the terminal and being generally annoying.  Let's block him, but keep everyone else's access intact.

# firewall-cmd --zone=Putorius --add-rich-rule='rule family=ipv4 source address=192.168.0.7/32 port port=22 protocol=tcp drop'
success

Now let's list our rich rules for the Putorius zone.

firewall-cmd --zone=Putorius --list-rich-rules
rule family="ipv4" source address="192.168.0.7/32" port port="22" protocol="tcp" drop

You can remove a rich rule the same way you add it, but instead of using --add-rich-rule you would use --remove-rich-rule.

# firewall-cmd --zone=Putorius --remove-rich-rule='rule family=ipv4 source address=192.168.0.7/32 port port=22 protocol=tcp drop'
success

Rate Limiting with Rich Rules

You can also use rich rules to rate limit traffic.  Let's say you want to limit incoming ssh connections to 3 per minute.

# firewall-cmd --zone=Putorius --add-rich-rule='rule service name=ssh limit value=3/m accept'
success

Logging Packets with Rich Rules

Rich rules also allow you to send information to log files.  Let's keep an eye on our co-workers and log all incoming ssh connections:

# firewall-cmd --zone=Putorius --add-rich-rule='rule family="ipv4" source address="192.168.0.0/24" service name="ssh" log prefix="ssh" level="info" accept'
success

To learn more about rich rules, their syntax and additional example, check the man page:

man 5 firewalld.richlanguage

Feel free to leave questions or comments below.  Enjoy!