Exploring firewalld: A Simple and Powerful Firewall for Linux to Replace iptables

Date: 07/08/2025

There have been numerous articles dedicated to configuring a firewall using iptables in Linux, and given the topic’s popularity, many more are likely to be written. For simple configurations, things are quite straightforward, but when dealing with complex networks and numerous computers, it becomes far more challenging. Additionally, porting rules to different systems is not always seamless.

Iptables and Beyond

The iptables project, developed by Rusty Russell in 1999 for managing netfilter, replaced tools like ipchains and ipnatctl in the 2.4 Linux kernel, offering a more expandable way to filter packets, giving system administrators greater control and simplifying rule creation. With ipchains, an administrator had to create a rule in each chain to trace a packet’s entire route, whereas now, a single rule is sufficient. The introduction of modules allowed for easy expansion of capabilities. Over time, iptables was ported to IPv6 (in 2011, as ip6tables), and additional modules like ULOG and nf_conntrack were added, enabling various packet manipulations, traffic classification up to the OSI model’s seventh layer, load balancing, and more. As the number of features grew, the configurations became more complex. Despite some unification, each extension has its own syntax; some support ranges, negation, and prefixes, while others do not. Initially, any rule changes required a complete firewall restart, including unloading modules, which led to disconnections of established connections. This issue no longer exists.

In simple cases, configuring with iptables is straightforward, but in complex networks, managing a large number of rules becomes challenging. Understanding all the settings and how it operates requires time. It’s difficult to immediately grasp what each chain does, and rules start to repeat, making them hard to maintain, update, and transfer to other systems.

It’s not surprising that various tools have been developed to address these issues. In Ubuntu, for instance, ufw (Uncomplicated Firewall) is used for straightforward configuration of firewall rules. For example, to allow access to the SSH port, you simply need to enter

$ sudo ufw allow 22

Application developers can create pre-configured profiles that activate when the package is installed, saving users from having to create and input rules themselves.

Another well-known project that simplifies the maintenance of complex rules is FERM (for Easy Rule Making). FERM stores all rules in a single file that is easy to read, edit, and load with a single command. This file can be effortlessly transferred between computers. The rules themselves are organized into blocks and can include variables and lists, allowing for the same configurations to be more concise and understandable. The final size of FERM rules is about three times smaller than equivalent configurations for iptables. For example, you can block all connections except for HTTP, SSH, and FTP.

chain INPUT {
policy DROP;
mod state state (RELATED ESTABLISHED) ACCEPT;
proto tcp dport (http ssh ftp) ACCEPT;
}

Under the hood, FERM is essentially a Perl script that converts configuration files into iptables rules.

In Fedora 18, the firewalld daemon was announced, which became the official tool for managing netfilter settings in RHEL 7 / CentOS 7. These systems are becoming increasingly popular on VDS, so it’s important to be aware of their specific features.

Features of Firewalld

Firewalld operates as a daemon, allowing new rules to be added without restarting or resetting the existing firewall. Configuration changes can be made at any time and are applied instantly, with no need to save or manually apply them. It supports IPv4, IPv6, automatic kernel module loading, and network zones to define the trust level of connections. Firewalld offers an easy interface for adding rules for services and applications, as well as a whitelist for applications authorized to change rules. Currently, it is supported by libvirt, Docker, fail2ban, Puppet, the Virtuozzo installation script, and many other projects. YUM repositories already include packages like fail2ban-firewalld and puppet-firewalld, making it possible to enable them with a single command.

Firewalld provides information about the current firewall settings via a D-Bus API and accepts changes through D-Bus using PolicyKit authentication methods. It uses iptables, ip6tables, ebtables, and ipset as backends, with plans to integrate nftables. However, firewalld cannot interpret rules created directly by these utilities, so both methods cannot be used simultaneously.

Management is done using command-line utilities like firewall-cmd or the graphical firewall-config, which allows you to configure all the rules in a user-friendly environment. The firewall-offline-cmd utility is used to assist in migrating current iptables rules to firewalld, and by default, it reads from /etc/sysconfig/system-config-firewall. Recent releases include the firewallctl utility, which features a simple syntax and enables you to get information about the service’s status, firewall configuration, and to modify rules.

The graphic interface firewall-config supports firewalld
The graphic interface firewall-config supports firewalld
firewall-cmd options
firewall-cmd options

Checking the status:

# systemctl status firewalld
# firewall-cmd --state
running

Allowing a connection on a specific port is quite straightforward:

# firewall-cmd --permanent --add-port=22/tcp

For any changes to take effect, you must always run a command after making edits.

# firewall-cmd --reload

The parameter --remove-port is used to delete a port from the rules:

# firewall-cmd --remove-port=22/tcp

In general, many --add-* commands have corresponding --query-* for status checks, --list-* for lists, --change-* for modifications, or --remove for deleting the respective values. For brevity, we’ll not dwell further on these details. After reloading the rules, let’s proceed to check:

# firewall-cmd --list-ports

Firewalld includes a mode that allows you to block all connections with a single command:

# firewall-cmd --panic-on

To check which mode the firewall is in, there is a special command:

# firewall-cmd --query-panic

Disabling panic mode:

# firewall-cmd --panic-off

With firewalld, you don’t need to know which port is associated with a service; you just need to specify the service name. The utility will handle the rest.

Once firewalld is installed, it recognizes the configurations of over 50 services. Let’s get the list of these services.

# firewall-cmd --get-services

Let’s enable the connection to HTTP:

# firewall-cmd --add-service=http

By using curly braces, you can configure multiple services simultaneously. Information about service settings is available through

# firewall-cmd --info-service=http

Firewalld stores all its configurations in XML files located in directories under /usr/lib/firewalld. Specifically, the services are found in the services directory. Inside each file, you’ll find details such as the name, protocol, and port.

<?xml version="1.0" encoding="utf-8"?>
<service>
<short>MySQL</short>
<description>MySQL Database Server</description>
<port protocol="tcp" port="3600"/>
</service>

This is a system directory, and nothing should be changed there. If you need to override settings or create your own service, copy any file as a template into /etc/firewalld/services, modify it according to your needs, and apply the settings.

A separate set of rules is used for configuring ICMP. Here’s how to get a list of supported ICMP types:

# firewall-cmd --get-icmptypes

Checking the status:

# firewall-cmd --zone=public --query-icmp-block=echo-reply
# firewall-cmd --zone=public --add-icmp-block=echo-reply
Firewalld stores all its configurations in XML files
Firewalld stores all its configurations in XML files
Firewalld recognizes nearly 50 services
Firewalld recognizes nearly 50 services

Zone Management

In firewalld, zones are used to determine the level of trust for a network connection. A zone can contain multiple network connections, but each network connection can only belong to one zone. You can get a list of all zones using the command firewall-cmd --get-zones.

Retrieving the list of zones
Retrieving the list of zones

Upon installation, nine zones are created. Depending on the purpose, one or more of these zones can be utilized:

  • Trusted: All network connections are allowed.
  • Work/Home/Internal: These zones have similar settings with different purposes. Maximum trust is given to computers on the network, allowing only specific incoming connections (by default SSH and DHCPv6 client; with home and internal also allowing MDNS and Samba client).
  • DMZ: For computers in a demilitarized zone, accessible from the Internet with limited access to the internal network. Only specified incoming connections are allowed (by default SSH).
  • External: A rule suitable for routers to be used in external networks with masquerading enabled, characterized by maximum distrust and clearly defined allowed incoming connections (by default SSH).
  • Public: For use in public places, with maximum distrust towards other computers, allowing only specific incoming connections (by default SSH and DHCPv6 client).
  • Block: Incoming network connections are rejected with an icmp-host-prohibited message, only connections initiated from this system are allowed.
  • Drop: Only outgoing connections are allowed, all incoming connections are blocked.

Zone descriptions are also provided in XML files located in /usr/lib/firewalld/zones.

After installing the system, the ‘public’ zone is typically used. If the existing zones are not sufficient, you can create new zones using the appropriate tools.

# firewall-cmd --permanent --new-zone=zone_name
Default Zone Settings
Default Zone Settings

All packets that do not fall within specified zones are processed in the default zone.

# firewall-cmd --get-default-zone

Now, let’s look at which zones are currently active and which interfaces are associated with them.

# firewall-cmd --get-active-zones

We can also obtain feedback on which zone the interface is associated with.

# firewall-cmd --get-zone-of-interface=eno1

Let’s review the zone settings (services, ports, protocols, etc.).

# firewall-cmd --zone=public --list-all
# firewall-cmd --zone=public --list-services

If the parameter is empty, it means that the settings have not been configured. If needed, reassign the interface to the zone:

# firewall-cmd --zone=home --add-interface=eno1 --permanent

If you check the output of firewall-cmd --zone=public --list-all now, you’ll notice that the network interface is missing from the setup list. Let’s allow the service connection:

# firewall-cmd --zone=home --add-service=openvpn --permanent

Here’s how you can remove it:

# firewall-cmd --zone=home --remove-service=openvpn --permanent

Zones can also be associated with other sources, identified by MAC address, individual IP, or network address. Any packet arriving from such a source will be processed according to the zone’s rules.

# firewall-cmd --permanent --zone=trusted --add-source=192.168.1.0/24

You can view the list of all sources using --zone=trusted --list-sources. NAT, which allows multiple computers to connect to the network, can be enabled in firewalld with a single command. To check the current masquerade settings:

# firewall-cmd --zone=external --query-masquerade

If the response we receive is no, then activate:

# firewall-cmd --zone=external --add-masquerade

That’s all. To enable external access, we’ll set up port forwarding to one of the computers. For example, if we need SSH access to an internal server:

# firewall-cmd --zone=external --add-forward-port=port=22:proto=tcp:toport=22:toaddr=192.168.1.100

Let’s check:

# firewall-cmd --zone=external --list-all

The forwarding rule can be deleted using --remove-forward-port.

Advanced Rules

For individual PCs or small networks, the basic features are usually sufficient. To configure complex rules in firewalld, a so-called direct syntax was initially proposed, and a Rich Language was introduced later. The first option requires knowledge of iptables syntax and is recommended only as a last resort since rules do not persist after a reboot.

The syntax for a direct rule is as follows:

# firewall-cmd [--permanent] --direct --add-rule { ipv4 | ipv6 | eb } <table> <chain> <priority> <args>

The syntax for <args> is exactly the same as that of iptables. Here’s how to retrieve the current settings:

# firewall-cmd --direct --get-chains ipv4 filter
# firewall-cmd --direct --get-rules ipv4 filter input

Add a rule that allows connections through port 25:

# firewall-cmd --direct --add-rule ipv4 filter INPUT 0 -m tcp -p tcp --dport 25 -j ACCEPT

Let’s forward the connection on port 22 to another server:

# firewall-cmd --permanent --direct --add-rule ipv4 filter FORWARD 0 -i eno1 -o eno2 -p tcp --dport 22 -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT

Let’s check:

# firewall-cmd --direct --get-all-rules

Rich Language allows you to write complex rules in a more comprehensible format. In a rule, you can specify any parameters that define a packet, such as source, destination, service, port, protocol, masquerading, logging, auditing, and action. For instance, you can permit a subnet to connect via HTTP and add auditing:

# firewall-cmd --permanent --zone=public --add-rich-rule="rule family="ipv4" source address="192.168.0.0/24" service name="http" audit limit value="1/m" accept

A significant advantage of Rich Language is that all parameters can be described in XML within the zone file. The file format is very straightforward and mirrors the parameter names:

<rule>
<service name="ssh"/>
<accept/>
</rule>

PANEL

Configuring a firewall is often a matter of habit. Many people find it more convenient to use a command they’ve relied on for years rather than learning a new utility. That’s why sometimes there’s a desire to bring back the classic tool. This isn’t an issue. Iptables isn’t included in CentOS 7 by default, so you’ll need to reinstall it:

# yum install -y iptables-services

To avoid having to configure everything from scratch, it’s better to save the current rules generated by firewalld.

# iptables-save > /etc/sysconfig/iptables
# ip6tables-save > /etc/sysconfig/ip6tables

Stopping firewalld and starting iptables:

# systemctl stop firewalld && systemctl disable firewalld
# systemctl start iptables && systemctl enable iptables
# systemctl start ip6tables && systemctl enable ip6tables

Reviewing the current rules:

# iptables -L
# iptables -S

Prevent firewalld from automatically starting when the OS boots:

# systemctl disable firewalld

Conclusions

As you can see, it’s nothing complicated! Firewalld simplifies the setup process, especially considering that configurations can easily be transferred.

Related posts:
2022.02.16 — Timeline of everything. Collecting system events with Plaso

As you are likely aware, forensic analysis tools quickly become obsolete, while hackers continuously invent new techniques enabling them to cover tracks! As…

Full article →
2023.04.04 — Serpent pyramid. Run malware from the EDR blind spots!

In this article, I'll show how to modify a standalone Python interpreter so that you can load malicious dependencies directly into memory using the Pyramid…

Full article →
2023.02.21 — Pivoting District: GRE Pivoting over network equipment

Too bad, security admins often don't pay due attention to network equipment, which enables malefactors to hack such devices and gain control over them. What…

Full article →
2022.06.01 — Log4HELL! Everything you must know about Log4Shell

Up until recently, just a few people (aside from specialists) were aware of the Log4j logging utility. However, a vulnerability found in this library attracted to it…

Full article →
2022.01.11 — Pentest in your own way. How to create a new testing methodology using OSCP and Hack The Box machines

Each aspiring pentester or information security enthusiast wants to advance at some point from reading exciting write-ups to practical tasks. How to do this in the best way…

Full article →
2023.07.29 — Invisible device. Penetrating into a local network with an 'undetectable' hacker gadget

Unauthorized access to someone else's device can be gained not only through a USB port, but also via an Ethernet connection - after all, Ethernet sockets…

Full article →
2023.03.03 — Infiltration and exfiltration. Data transmission techniques used in pentesting

Imagine a situation: you managed to penetrate the network perimeter and gained access to a server. This server is part of the company's internal network, and, in theory, you could…

Full article →
2023.06.08 — Croc-in-the-middle. Using crocodile clips do dump traffic from twisted pair cable

Some people say that eavesdropping is bad. But for many security specialists, traffic sniffing is a profession, not a hobby. For some reason, it's believed…

Full article →
2023.04.20 — Sad Guard. Identifying and exploiting vulnerability in AdGuard driver for Windows

Last year, I discovered a binary bug in the AdGuard driver. Its ID in the National Vulnerability Database is CVE-2022-45770. I was disassembling the ad blocker and found…

Full article →
2022.02.09 — F#ck da Antivirus! How to bypass antiviruses during pentest

Antiviruses are extremely useful tools - but not in situations when you need to remain unnoticed on an attacked network. Today, I will explain how…

Full article →