Firewalld
is a firewall management tool that comes pre-installed on most RHEL-based distributions. In this article, we will look at setting up a firewall with firewalld
on a CentOS server using the firewall-cmd
utility.
To follow this guide, you will need:
A cloud server running CentOS
A root
user or a user with sudo
privileges
A firewall is a program for access control at the network level. Based on the set up rules, the firewall determines which devices can access this or that part of the network, which traffic is allowed and which is blocked. In Linux, these functions are performed by the netfilter
program built into the kernel.
So, netfilter
works at the kernel level. To configure the firewall, you need tools in user space. ip_tables
/nf_tables
, implemented as kernel modules, are used as frameworks for managing netfilter. And on top of them, at the user level, either iptables
or nft
are used.
Experienced system administrators know that manually writing rules for iptables
or nft
is not so easy, especially when you need to set up a large network or transfer rules to another system. That's where more user-friendly and feature-rich add-ons come into play. For example, in Ubuntu such an add-on is a tool with the self-explanatory name ufw
(Uncomplicated Firewall). And in RedHat distributions (CentOS, Alma, Fedora, RHEL, OpenSUSE, SUSE Linux Enterprise), it is firewalld
.
Our cloud-based firewall provides cutting-edge defense tailored for businesses of all sizes.
There are two programs to manage firewalld
: firewall-cmd
for working in the terminal and firewall-config
GUI.
In this article, we will work with firewall-cmd
.
Connect to your server and check the service status:
sudo systemctl status firewalld
As mentioned before, in RHEL-based distributions, firewalld
is pre-installed. If you wish to use it on Ubuntu/Debian, you can easily install it manually:
sudo apt install firewalld
sudo systemctl enable firewalld --now
Don't forget to disable ufw
, as using two utilities for iptables
/nftables
may cause issues.
Check the existing active rules by using the command:
sudo firewall-cmd --list-all
Note that you need superuser privileges for this, so log in as root
or use sudo
/su
.
In the command output you can see many different parameters. Let's look at them in order.
The ports
field shows the ports on which connections are allowed.
To better understand how it works, let's change the listening port of the OpenSSH daemon from 22 to 3333 in the /etc/ssh/sshd_config
file:
Port 3333
#AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::
sudo systemctl restart sshd
List the ports listened by sshd
:
sudo ss -nl4p | grep sshd
Output:
tcp LISTEN 0 128 *:3333 *:* users:(("sshd",pid=7602,fd=3))
As you can see, the port 3333 is now listening. The current session has not been interrupted, but if we try to connect again using the standard or new port, we won't be successful:
ssh [email protected]
ssh: connect to host 166.1.227.252 port 22: Connection refused
ssh [email protected] -p 3333
ssh: connect to host 166.1.227.252 port 3333: No route to host
You need to add an allowing rule to firewalld
that allows you to connect via 3333/tcp:
sudo firewall-cmd --add-port=3333/tcp
Or:
sudo firewall-cmd --add-port=3333/tcp --permanent
sudo firewall-cmd --reload
In the first case, the changes are active only in the current runtime, and when the machine or service is restarted, everything is reset. In the second case, the --permanent
flag indicates that we are changing the settings permanently, so you need to restart the firewall (the --reload
option).
Let's check which ports are now allowed:
sudo firewall-cmd --list-ports
Output:
3333/tcp
Now everything works:
ssh [email protected] -p 3333
[email protected]'s password:
You might wonder how we managed to connect via SSH earlier, as at first the ports field in the policy output was empty. The fact is that the settings were specified using service descriptions.
Services in firewalld are service descriptions that make configuration more convenient and centralized. Let's look up the current services and information about them:
sudo firewall-cmd --list-services
sudo firewall-cmd --info-service=ssh
Note that SSH still has port 22. The fact is that the daemons' own parameters are in no way related to their descriptions in firewalld
. Let's try to remove the standard port and add 3333 to the ssh
service description (don't forget to remove port 3333/tcp from the policies):
sudo firewall-cmd --service=ssh --remove-port=22/tcp --permanent
sudo firewall-cmd --remove-port=3333/tcp --permanent
sudo firewall-cmd --service=ssh --add-port=3333/tcp --permanent
sudo firewall-cmd --reload
Now let's check again:
sudo firewall-cmd --info-service=ssh
Let's try to remove and add a service:
sudo firewall-cmd --remove-service=dhcpv6-client
sudo firewall-cmd --add-service=mysql
sudo firewall-cmd --runtime-to-permanent
Check the list of services:
sudo firewall-cmd --list-services
The --runtime-to-permanent
option makes the runtime permanent. You don't have to specify --permanent
after each command, but execute a set of commands and only after that make the settings permanent (please note that it does not always work since some commands require the --permanent
option).
Files with predefined services are located at /lib/firewalld/services
. Let's list them:
sudo firewall-cmd --get-services
You can create your own services. To do this, create an .xml
file in the /etc/firewalld/services
directory. For convenience, you can copy a configuration from /etc/firewalld/services
and make the necessary changes. Then restart the firewall with the --reload
flag.
The output of --list-all
has the following fields:
sudo firewall-cmd --list-all | grep -E '(target|icmp|protocols)'
In firewalld
, you can configure not only services and ports, but also protocols. Let's say we set up a firewall on a router. It makes sense to indicate a list of allowed protocols. To illustrate, let's enable the OSPF dynamic routing protocol:
sudo firewall-cmd --add-protocol=ospf
sudo firewall-cmd --list-protocols
The icmp-blocks
and icmp-blocks-inversion
fields contain parameters for the ICMP protocol used for network testing and error notifications. It is used by ping
and traceroute
utilities.
Using icmp-blocks
you can block specific ICMP messages. The icmp-blocks-inversion
inverts the logic, as in the listed message types become allowed and the rest are blocked. This functionality can be used to hide network information since many scanning engines use ICMP. Let's test the blocking using the ping
utility.
Prohibit all types of ICMP messages (since nothing is listed in icmp-block
, all messages become prohibited):
sudo firewall-cmd --add-icmp-block-inversion
Now let's try to ping the server:
ping -c 3 166.1.227.252
PING 166.1.227.252 (166.1.227.252) 56(84) bytes of data.
From 166.1.227.252 icmp_seq=1 Destination Host Prohibited
From 166.1.227.252 icmp_seq=2 Destination Host Prohibited
From 166.1.227.252 icmp_seq=3 Destination Host Prohibited
--- 166.1.227.252 ping statistics ---
3 packets transmitted, 0 received, +3 errors, 100% packet loss, time 2004ms
The machine does not respond to ping. This method is often used by providers to hide router addresses. However, it is clear from the errors that the packets on the target devices are being filtered by the firewall. To hide your computer, you need to use targets.
A target is an action that needs to be done with the package if any of the rules are triggered. The most used are DROP
, ACCEPT
, REJECT
. In the example above, the target was REJECT
, i.e. returning the reason for the error. To drop a packet without sending anything, you need to use DROP
:
sudo firewall-cmd --set-target=DROP --permanent
sudo firewall-cmd --reload
Let's ping the server again:
ping -c 3 166.1.227.252
PING 166.1.227.252 (166.1.227.252) 56(84) bytes of data.
--- 166.1.227.252 ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time 2050ms
When working with firewalld
, zones are often used. A machine may have multiple interfaces: one for Internet, another for LAN, and third for dmz. Different networks have different levels of trust, and traffic coming from them must be controlled by different firewall rules. To achieve this, firewalld
uses zones, sets of rules that apply to different parts of the network.
All the firewall rules we configured above were applied for the public zone (by default):
sudo firewall-cmd --get-default-zone
Firewalld
initially contains several zones. However, you can create your own zones. Let's display a list of available zones:
sudo firewall-cmd --get-zones
The --zone
option allows you to set and view rules for a specific zone (without it, everything is applied to the default zone). Let's look at the policies for the drop
zone:
sudo firewall-cmd --list-all --zone=drop
In our case, the interface eth0, which provides access to the Internet, is in the public zone. This means that the policies of the public zone are applied to incoming traffic. To add the interface to another zone, use the --change-interface
switch. Let's move our eth0
to the home
zone:
sudo firewall-cmd --change-interface=eth0 --zone=home
sudo firewall-cmd --list-interfaces --zone=home
Sometimes, you may need to configure traffic rules for a specific address. It's done like this:
sudo firewall-cmd --zone=drop --add-source=192.168.3.1
Now check:
sudo firewall-cmd --zone=drop --list-all
Now, even if packets from the address 192.168.3.1 arrive on the eth0
interface, the rules will be applied not for the zone in which the interface is located, but for the zone we manually specified (drop
).
NAT stands for Network Address Translation. There are two types of NAT: source and destination. In the first case, the router replaces home IP addresses with its external ones, i.e. changing the sender's (source) address. In Linux, this is called masquerade. In the second case, the recipient address is replaced (port forwarding). For example, a packet arrives at the router port, and the router forwards the packet to some other port.
Let's say you need to configure a firewall on a router. Enable masquerading:
sudo firewall-cmd --add-masquerade
Run --list-all
:
sudo firewall-cmd --list-all
Enable port forwarding:
sudo firewall-cmd --add-forward-port=port=3333:proto=tcp:toport=22
sudo firewall-cmd --list-all
You may also notice the source-ports option. Firewalld can allow specific outgoing ports, but this is rarely used since dynamic 5-digit ports are usually used as source ports.
We looked at how to use different firewalld rules for different network areas (interfaces, IP addresses). But sometimes you need to create very distinctive rules, like allowing connections from a specific address to a specific port, using such and such protocols, etc. Of course, you can create your own zone, but it may be better to use rich-rules
.
Rich-rules
in firewalld
have a different syntax which you can find in the manual:
man firewalld.richlanguage
It also provides many examples.
And that's how you add a rich-rule
manually:
sudo firewall-cmd --add-rich-rule=’rule'
A properly functioning firewall greatly improves network security. In this article, we looked at the basics of working in firewalld
, which are necessary for proper configuration. Firewalld
allows you to easily divide your network into sections and set your own security policies for each section.