Linux firewall and SSH protection configuration

The Linux kernel's built-in firewall function (netfilter packet filtering framework) is administered by the iptables Linux command.

General information about the concept of a firewall.

The documentation on this page includes:

RHEL7/CentOS7 and Fedora firewalld

RHEL7/CentOS7 and Fedora have a completely new firewall interface, rather than the well-known iptables service in RHEL6/CentOS6.

A nice introduction is RHEL7: How to get started with Firewalld.

The default firewall service is now firewalld, which is a front-end service on top of the iptables service. The dynamic firewall daemon firewalld provides a dynamically managed firewall with support for network “zones” to assign a level of trust to a network and its associated connections and interfaces. See Introduction to firewalld.

A graphical configuration tool:


is used to configure firewalld, which in turn uses iptables tool to communicate with netfilter in the kernel which implements packet filtering.

A command line (CLI) configuration tool:


may also used to configure firewalld. To make any new rule permanent, run the command with the --permanent flag. See the manual firewall-cmd for further information.

The firewalld configuration files are in the directory /etc/firewalld/zones/ where XML files contain the firewall rules.

See firewalld log messages in:


Whitelisting IP addresses with firewalld

It is important to be able to whitelist internal IP addresses. Insert a firewalld direct_rule so that any incoming source IP packet (src) from a specific IP subnet gets accepted, for example:

firewall-cmd --direct --add-rule ipv4 filter INPUT_direct 0 -s -j ACCEPT

To make the rule permanent, run the command with the --permanent flag.

To open a specific port NNNN:

firewall-cmd --zone=public --add-port=NNNN/tcp --permanent

Then remember to reload the firewall for changes to take effect:

firewall-cmd --reload

Here the value 0 is the priority of the rule (smaller numbers have higher priority, so 0 is the highest priority). Make sure that other direct rules (which might interfere with the whitelist) have a lower priority.

List the rules by:

firewall-cmd  --permanent --direct --get-all-rules

Iptables IP sets and country blocking

IP_sets are a framework inside the Linux 2.4.x and 2.6.x kernel which can be used efficiently to create firewall rules for large numbers of IP subnets.

Depending on the type, currently an IP set may store IP addresses, (TCP/UDP) port numbers or IP addresses with MAC addresses in a way, which ensures lightning speed when matching an entry against a set. The IP_sets can be administered by the ipset utility.

Install IP_sets by:

yum install ipset                # RHEL/CentOS 6 and 7

Read the man ipset manual page.

Country IP blocks

There are providers of country IP blocks available for download and eventual inclusion into IP_sets block lists:

For example, the IP blocks for China may start with:

Country IPv6 blocks may be found at IPdeny or

For discussions see:

Create a country geoblock ipset

We use the IPdeny all countries zone files and create an IP set which we name geoblock. We offer a simple script which automatically creates an ipset geoblock data file. Download this Makefile to a new directory:

Edit the COUNTRYLIST list variable according to your needs, then run:


to create the IPSET_DATA file /etc/sysconfig/ipset.

Tor exit node blocking

If you wish to block port-scanning from Tor exit nodes then you may download the dynamically updated torbulkexitlist file.

The torbulkexitlist file contains a list of IP-addresses which may be considered as a "country named Tor" and simply added as another zone file like in the above country blocks, for example

Using ipsets in firewalld on RHEL/CentOS 7.3 and above

According to the RHEL 7.3 Release Notes, firewalld has been upgraded from version 0.3.9 to 0.4.3:

The ipset must be configured directly in firewalld. Do not try to run the Systemd service ipset service together with the firewalld 0.4 firewalld service (as we did previously). See manual pages for firewalld.ipset, and firewall-cmd.

The procedure for RHEL/CentOS 7.3 is:

  • As a prerequisite create the data file /etc/sysconfig/ipset as shown above.

  • Create a new ipset called geoblock, optionally specifying a larger maximum number of elements in the list:

    firewall-cmd --permanent --new-ipset=geoblock --type=hash:net [ --option=hashsize=65536 --option=maxelem=524288 ]
See firewall-cmd for the --option flags.
  • Add ipset geoblock to the zone named drop

    firewall-cmd --permanent --zone=drop --add-source=ipset:geoblock
  • Now load ipset data:

    firewall-cmd --permanent --ipset=geoblock --add-entries-from-file=/etc/sysconfig/ipset
  • Reload the firewalld service:

    firewall-cmd --reload

See the manual firewall-cmd section IPSet Options for a list of ipset subcommands.

To list all ipset entries:

firewall-cmd --permanent --ipset=geoblock --get-entries

Using ipsets in firewalld on RHEL/CentOS 8

In RHEL/CentOS 8 the ipset setup works similarly to the above description for 7.3 (and above).

There is an important problem, however, in CentOS 8.3:

  • Overlapping network ranges in the IPsets causes firewalld failures.

This is described in Bugzilla_1836571 firewalld startup failure: COMMAND_FAILED: 'python-nftables' failed: internal:0:0-0: Error: Could not process rule: File exists (reported on Fedora FC32). See error messages in Comment 12.

The current status (Dec. 2020) is:

  1. firewalld needs to use the "auto-merge" feature of sets to a allow element coalescing.
  2. The nftables "auto-merge" feature was introduced in release 0.8.2.

Conclusion: Do not (accidentally) use overlapping IPsets in CentOS 8.3.

Aggregate CIDR addresses

To circumvent the CentOS 8 Firewalld failures in case of overlapping IP ranges, there exist several tools:

  1. Python tool aggregate6.

    First install prerequisites:

    dnf install gcc platform-python-devel

    Then install by:

    pip3 install aggregate6 --user


    ~/.local/bin/aggregate6 --help
  2. Perl script aggregate-cidr-addresses which takes a list of CIDR address blocks and combine them without overlaps. We have a copy of the file, download it to /usr/local/bin/.

    Install prerequisite Perl modules:

    dnf install perl-Net-IP


    cat file1 file2 ... fileN |

Enabling the ipset service on RHEL6/CentOS6

The latest RHEL6/CentOS6 RPM ipset-6.11-3.el6.x86_64 now includes a new init script /etc/rc.d/init.d/ipset for starting and stopping the ipset service. See also Bug 888571 - provide an init script that loads the ipsets for information. On the Internet you may find all sorts of information about starting ipset - such advice may no longer be relevant.

The ipset service will only start if this IPSET_DATA file exists and contains ipset input data:

/etc/sysconfig/ipset    # RHEL6/CentOS6

See above for how to populate the IPSET_DATA file with country block data.

Now start the ipset service, for RHEL6/CentOS6 by:

chkconfig ipset on
service ipset start

Using ipset with the iptables firewall (RHEL6/CentOS6)

Make sure the IP_sets kernel modules are loaded when the iptables service is started by editing this line in /etc/sysconfig/iptables-config:

IPTABLES_MODULES="ip_set ip_set_hash_netport ip_set_hash_net"

Verify that these kernel modules have been loaded:

# lsmod | grep ip_set
ip_set_hash_net        27500  1
ip_set_hash_netport    29076  0
ip_set                 30977  3 xt_set,ip_set_hash_net,ip_set_hash_netport
nfnetlink               4200  1 ip_set

Insert an iptables rule so that any incoming source IP packet (src) gets matched against the set of IP addresses in geoblock:

iptables -I INPUT -m set --match-set geoblock src -j DROP

Verify the presence of the geoblock line number 1:

# service iptables status
Table: filter
Chain INPUT (policy ACCEPT)
num  target     prot opt source               destination
1    DROP       all  --             match-set geoblock src

Test that you can still connect from any allowed external hosts.

If your tests are OK, add this line to /etc/sysconfig/iptables as the top-most rule line:

-I INPUT -m set --match-set geoblock src -j DROP

Testing the ipset service

Testing the ipset:

  • List all the sets in ipset:

    ipset save
  • Test whether an IP address (here: is in a given IPset geoblock:

    ipset test geoblock

Flush the ipset kernel table for this IPset:

ipset flush geoblock

This may be required if you delete ipset entries - subsequently you should restart the IPset service.

Enabling the ipset service on RHEL/CentOS 7.0, 7.1 and 7.2

RHEL/CentOS 7.0, 7.1 and 7.2 (which use Systemd) do not have a method for starting an ipset service. This has been fixed in later releases.

A workaround exists for enabling the ipset service on RHEL7/CentOS7 using scripts from Fedora:

See below for how to start the ipset service.

Using ipset with firewalld (RHEL7/CentOS7 and Fedora)

Insert an firewalld direct_rule so that any incoming source IP packet (src) gets matched against the set of IP addresses in geoblock:

firewall-cmd --direct --add-rule ipv4 filter INPUT_direct 1 -m set --match-set geoblock src -j DROP

Here the value 1 is the priority of the rule (smaller numbers have higher priority, so 0 is the highest priority).

Verify the new rule:

firewall-cmd --direct --get-all-rules | grep geoblock

If testing is OK, you can make this rule permanent:

firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT_direct 1  -m set --match-set geoblock src -j DROP

The file /etc/firewalld/direct.xml will contain this direct_rule.

Blocking SSH brute force attacks with iptables (RHEL6/CentOS6)

Hackers often attack any server which offers an open SSH port (port 22) to the Internet. They will try to log in as system users (root etc.) with a list of passwords, and/or try a dictionary of usernames and passwords. This may run into thousands of SSH login attempts within a few minutes.

There are a number of different methods which may be used to block such SSH brute force attacks, see for example this article Defending against brute force ssh attacks.

On our gateway machine exposed to the Internet we use the Linux firewall iptables rules to block brute force attacks on the SSH port. There exists a lot of advice on various mailing lists about how to accomplish this, as is shown by a Google search. However, detailed recipes/HowTos seem to be mostly missing.

We use a script by Brian Gaynor modified slightly to become:


# Set up iptables so that SSH dictionary attacks get rejected for some minutes

# Modprobe the extra modules we need
modprobe ipt_recent
modprobe ip_conntrack

# Remove any old rules
iptables -F
iptables -X
iptables -Z

# Some variables - REPLACE WITH YOUR IP
IPADDR="`ifconfig $IFACE | grep addr: | awk '{print $2}' | sed s/addr://`"

# Kill ssh hackers - watch for more than 5 connection attempts in under
# 60 seconds and reject for 60 minutes

echo "Setting up iptables rules for interface $IFACE at IP=${IPADDR}:"
echo -n "SSH maximum number of logins=$MAXLOGINS within $PERIOD seconds, "
echo rejecting further connections for $REJECTPERIOD seconds

# Set up iptables rules

iptables -N SSH-EVIL
iptables -A SSH-EVIL -m recent --name badSSH --set -j LOG --log-level $LOGLEVEL --log-prefix "Evil SSH user: "
iptables -A SSH-EVIL -j REJECT

iptables -N SSH
iptables -A SSH -p tcp ! --syn -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A SSH -p tcp --syn -m recent --name badSSH --rcheck --seconds $REJECTPERIOD -j REJECT
iptables -A SSH -p tcp --syn -m recent --name sshconn --rcheck --seconds $PERIOD --hitcount $MAXLOGINS -j SSH-EVIL
iptables -A SSH -p tcp --syn -m recent --name sshconn --set
iptables -A SSH -p tcp --syn -j ACCEPT

# Allow unlimited traffic on the loopback interface
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# Send ssh down our user-defined chain, allow ftp ...
iptables -A INPUT -i $IFACE -p tcp --dport 22 -j SSH
# iptables -A INPUT -i $IFACE -p tcp --dport 21 -j ACCEPT

After running this script, and testing the iptables setup, you can save the configuration by:

iptables-save > /etc/sysconfig/iptables

Now the configuration will be used whenever the iptables service is restarted.

Another way is to configure this directly in /etc/sysconfig/iptables:

-A INPUT -i eth0 -p tcp -m tcp --dport 22 -j SSH
-A SSH -p tcp -m tcp ! --tcp-flags SYN,RST,ACK SYN -m state --state RELATED,ESTABLISHED -j ACCEPT
-A SSH -p tcp -m tcp --tcp-flags SYN,RST,ACK SYN -m recent --rcheck --seconds 3600 --name badSSH --rsource -j REJECT --reject-with icmp-port-unreachable
-A SSH -p tcp -m tcp --tcp-flags SYN,RST,ACK SYN -m recent --rcheck --seconds 60 --hitcount 5 --name sshconn --rsource -j SSH-EVIL
-A SSH -p tcp -m tcp --tcp-flags SYN,RST,ACK SYN -m recent --set --name sshconn --rsource
-A SSH -p tcp -m tcp --tcp-flags SYN,RST,ACK SYN -j ACCEPT
-A SSH-EVIL -m recent --set --name badSSH --rsource -j LOG --log-prefix "Evil SSH user: " --log-level 4
-A SSH-EVIL -j REJECT --reject-with icmp-port-unreachable

Blocking SSH brute force attacks with firewalld (RHEL7/CentOS7 and Fedora)

See these pages:

These commands may be tried:

firewall-cmd --direct --add-rule ipv4 filter INPUT_direct 2 -p tcp --dport 22 -m state --state NEW -m recent --set
firewall-cmd --direct --add-rule ipv4 filter INPUT_direct 3 -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 30 --hitcount 4 -j REJECT --reject-with tcp-reset

Note: Logging to syslog is missing from this setup. Apparently firewalld is incapable of logging rejected or accepted packets, see:

When tested OK, add the --permanent flag to the above commands.

sshblack -- Automatically BLACKLIST SSH attackers

The sshblack script is a real-time security tool for secure shell (ssh). It monitors -nix log files for suspicious activity and reacts appropriately to aggressive attackers by adding them to a "blacklist" created using various firewalling tools -- such as iptables -- available in most modern versions of Unix and Linux. The blacklist is simply a list of source IP addresses that are prohibited from making ssh connections to the protected host. Once a predetermined amount of time has passed, the offending IP address is removed from the blacklist.

See also this page Further Securing OpenSuSE 11.1 Against SSH Script Attacks.

Installing and configuring sshblack

Unpack the sshblack tar-ball. Consult the sshblack_install instructions.

Copy the executable files to a standard executable directory:

cp bl unbl list unlist /usr/local/sbin/

Configure the script, at a minimum define the sshblack_Whitelist for your local network by tailoring this line:

my($LOCALNET) = '^(?:127\.0\.0\.1|192\.168\.0)';

For security reasons the sshblack CACHE should be in a private directory rather than the world-writable volatile directory /var/tmp, for example:

my($CACHE) = '/var/lib/sshblack/ssh-blacklist-pending';

Note: The same CACHE variable must also be defined in the helper scripts list and unlist.

Create the private directory (RHEL/CentOS conventional location):

mkdir -v -p /var/lib/sshblack

Other configurable parameters include:

my($REASONS) = '(Failed password|Failed none|Invalid user)';
my($AGEOUT) = 600;
my($CHECK) = 300;
my($MAXHITS) = 4;
my($DOSBAIL) = 200;
my($CHATTY) = 1;
my($EMAILME) = 1;
my($NOTIFY) = 'root';

Some malformed SSH attacks generate log entries like:

sshd[30179]: Received disconnect from 11: Bye Bye [preauth]

To blacklist such IPs add an additional rule:

my($REASONS) = '(Failed password|Failed none|Invalid user|Bye Bye \[preauth\])';

See also the sshblack_config page for additional advice.

sshblack logfiles

The sshblack logs to this file:


It is a good idea to rotate this logfile on a weekly basis, so create the file /etc/logrotate.d/sshblacklisting with the contents:

# Log rotation configuration for SSH blacklisting
/var/log/sshblacklisting {

CentOS 6/RHEL 6 and sshblack

On CentOS 6/RHEL 6 Linux you should set up an init startup script (and not run the command manually). An example is found in the sshblack_config page. However, a more modern init startup script for RHEL6/CentOS6 is:

Now add the service and create the private sshblack directory:

cp init-sshblack /etc/init.d/sshblack
chkconfig --add sshblack
mkdir -v -p /var/lib/sshblack
Configure CentOS 6/RHEL 6 iptables for sshblack

Add a new iptables chain named BLACKLIST and make all port 22 connection go to that chain:

iptables -N BLACKLIST
iptables -A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j BLACKLIST

In /etc/sysconfig/iptables (CentOS 6) the entire file may look something like:

:OUTPUT ACCEPT [276:53553]
:BLACKLIST - [0:0]
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j BLACKLIST
-A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited

This means that SSH (port 22) connections first go through the BLACKLIST chain and, if not hit by any DROP rules here, will return and be permitted by the INPUT chain ACCEPT line. The daemon will be updating the BLACKLIST chain in the background.

Restart iptables to activate the above configuration (RHEL/CentOS):

service iptables restart

If you also use IP_sets (see above), make sure that the geoblock rule in iptables is still active (see the IP_sets section above).

CentOS7/RHEL7/Fedora and sshblack

WARNING: Since CentOS7/RHEL7/Fedora use firewalld in stead of iptables, the sshblack version 2.8.1 from the web-site does not work. All iptables commands in the sshblack scripts need to be replaced by similar firewall-cmd commands. Some preliminary work on this is under way.

On CentOS7/RHEL7/Fedora Linux you should set up a Systemd startup script (and not run the command manually).

An EL7-specific startup script must be used for RHEL7/CentOS7/Fedora with Systemd and firewalld:

Download also this service file:

Now add the service and create the private sshblack directory:

mkdir /usr/libexec/sshblack
cp init-sshblack-el7 /usr/libexec/sshblack/init-sshblack
cp sshblack.service /usr/lib/systemd/system/
chmod 755 /usr/libexec/sshblack/init-sshblack /usr/lib/systemd/system/sshblack.service
systemctl enable sshblack.service

Create a SSHBLACK iptables chain:

firewall-cmd --permanent --direct --add-chain ipv4 filter BLACKLIST

Then make all new connections to port 22 (SSH) jump to the BLACKLIST chain:

firewall-cmd --direct --add-rule ipv4 filter INPUT_direct 7 -p tcp --dport 22 -m state --state NEW  -j BLACKLIST

It is possible to drop packets from specific IP-addresses and subnets using rich rules like:

firewall-cmd --permanent --add-rich-rule="rule family='ipv4' source address='' drop"

but we don't use this yet.

Using sshblack

The sshblack daemon must be started:

  • RHEL6/CentOS6:

    service sshblack start
  • RHEL7/CentOS7/Fedora:

    systemctl start sshblack.service
  • Manual method: Start the sshblack daemon as the root user:


There are some useful sshblack_notes explaining some additional useful commands:

  • list -- manually adds an IP address to the blacklist and modifies the $CACHE file accordingly
  • unlist -- manually remove an IP address from the blacklist and the $CACHE file
  • bl -- a manual blacklisting tool (one liner that modifies the iptables configuration only)
  • unbl -- a manual UNblacklisting tool (one liner that modifies the iptables configuration only)
  • iptables-setup -- a few shell commands to set up the iptables chains if you don't want to do it manually.

If you want a list of blacklisted IP-addresses, display the BLACKLIST chain:

iptables -S BLACKLIST

Checkpoint and restart of sshblack

The script doesn't have any checkpoint/restart feature, so preservation of BLACKLIST state across restarts must be done manually. See the Checkpoint and Restart discussion.

The following script should be downloaded to /usr/local/sbin/:

and a new crontab rule should be added to run it every 5 minutes:

# Save the iptables chain BLACKLIST DROP lines for restarting sshblack
*/5 * * * * /usr/local/sbin/sshblack-save-state

This will create a restart script /var/lib/sshblack/ which will be executed by the above init-script init-sshblack at system boot time.

This command prints iptables commands to recreate the BLACKLIST from the sshblack CACHE in case it is lost by a restart of iptables:

awk -F, '{if ($3 > 4) printf("/sbin/iptables -I BLACKLIST -s %s -j DROP\n", $1)}' < /var/lib/sshblack/ssh-blacklist-pending

Firewall and NFS

Open up the NFS client's firewall to all traffic from the specific NFS-server(s). In general this is accomplished by this command:

iptables -A <rule-name> -s <NFS-server-hostname> -j ACCEPT

This may be accomplished permanently by adding this line by manually appending this rule to /etc/sysconfig/iptables:

-A RH-Firewall-1-INPUT -s <NFS-server-IP> -j ACCEPT    # RHEL 5
-A INPUT -s <NFS-server-IP> -j ACCEPT                  # RHEL 6

Summary of RHEL7/CentOS7 and Fedora firewalld settings

Summarizing the direct_rule commands in the above will result in permanent firewalld settings in the file /etc/firewalld/direct.xml:

<?xml version="1.0" encoding="utf-8"?>
  <chain table="filter" ipv="ipv4" chain="BLACKLIST"/>
  <rule priority="0" table="filter" ipv="ipv4" chain="INPUT_direct">-s -j ACCEPT</rule>
  <rule priority="1" table="filter" ipv="ipv4" chain="INPUT_direct">-m set --match-set geoblock src -j DROP</rule>
  <rule priority="2" table="filter" ipv="ipv4" chain="INPUT_direct">-p tcp --dport 22 -m state --state NEW -m recent --set</rule>
  <rule priority="3" table="filter" ipv="ipv4" chain="INPUT_direct">-p tcp --dport 22 -m state --state NEW -m recent --update --seconds 30 --hitcount 4 -j REJECT --reject-with tcp-reset</rule>

Here we have adjusted the priority values so that the most important rules have the highest priority (smaller numbers have higher priority, so 0 is the highest priority).

IT-wiki: Linux_firewall_configuration (last edited 2022-02-16 08:46:47 by OleHolmNielsen)