firewalld and fail2ban are packaged by the distribution, native to nftables and maintained on the same schedule as the operating system. That combination covers everything most hosting servers used CSF for: a port allow list, connection rate limiting, and automatic banning of brute-force sources. This tutorial performs the swap on AlmaLinux 9 or 10, keeping the server reachable throughout. It assumes you have console access as a safety net; a firewall change over SSH with no console is how servers get lost.
Applies to AlmaLinux 9 and 10
Table of Contents
Short answer: Export the CSF port list and csf.allow, uninstall CSF with csf -x and the package or uninstall.sh, then enable firewalld and add the same services and ports to the public zone with --permanent before reloading. Install fail2ban and fail2ban-firewalld from EPEL, define sshd, mail and FTP jails in /etc/fail2ban/jail.local with banaction = firewallcmd-rich-rules, and confirm a test ban appears in firewall-cmd --list-rich-rules.
Record what CSF is doing today
Before removing anything, capture the open ports and the permanent allow list so nothing is forgotten:
grep -E '^(TCP_IN|TCP_OUT|UDP_IN|UDP_OUT) ' /etc/csf/csf.conf
grep -v '^#' /etc/csf/csf.allow | grep -v '^$' > /root/csf-allow-export.txt
csf -l > /root/csf-live-rules.txt
Keep those files. The allow list becomes a firewalld trusted source list, and the port list becomes your zone services.
Remove CSF cleanly
CSF ships its own uninstaller, which stops the daemons, flushes the chains and removes the cron entries. On cPanel it is an RPM, so use dnf; elsewhere run the script:
csf -x
rpm -q cpanel-csf && dnf remove -y cpanel-csf || sh /etc/csf/uninstall.sh
pgrep -a lfd
nft list ruleset | head
pgrep should return nothing and the ruleset should be essentially empty. If you are on a panel server, also remove the panel plugin so no one can re-enable CSF from the UI later. There is now no firewall; move directly to the next step.
Install and configure firewalld
dnf install -y firewalld
systemctl enable --now firewalld
firewall-cmd --get-default-zone
The default zone is public. Add the services and ports you exported, using named services where they exist and raw ports where they do not. A cPanel-style server looks like this:
firewall-cmd --permanent --zone=public --add-service={ssh,http,https,smtp,smtps,submission,imap,imaps,pop3,pop3s,dns,ftp}
firewall-cmd --permanent --zone=public --add-port={2083/tcp,2087/tcp,2096/tcp}
firewall-cmd --permanent --zone=public --add-port=30000-35000/tcp
firewall-cmd --reload
firewall-cmd --list-all
The high port range is for passive FTP; match it to your FTP daemon’s configuration. If SSH runs on a non-standard port, add that port and remove the ssh service. Any addresses from csf.allow that need unrestricted access go into the trusted zone:
firewall-cmd --permanent --zone=trusted --add-source=198.51.100.0/24
firewall-cmd --reload
For rate limiting comparable to CSF’s CT_LIMIT and PORTFLOOD, use a rich rule:
firewall-cmd --permanent --add-rich-rule='rule service name="ssh" accept limit value="10/m"'
firewall-cmd --reload
Install fail2ban and build the jails
fail2ban comes from EPEL. Install it with the firewalld action package so bans are applied as firewalld rich rules rather than raw iptables calls:
dnf install -y epel-release
dnf install -y fail2ban fail2ban-firewalld
systemctl enable --now fail2ban
Never edit jail.conf; create /etc/fail2ban/jail.local with the jails you need. On AlmaLinux 9 and 10, sshd logs to the journal, so use the systemd backend:
[DEFAULT]
backend = systemd
banaction = firewallcmd-rich-rules
bantime = 1h
findtime = 10m
maxretry = 5
ignoreip = 127.0.0.1/8 198.51.100.0/24
[sshd]
enabled = true
[postfix]
enabled = true
[dovecot]
enabled = true
[proftpd]
enabled = true
logpath = /var/log/proftpd/proftpd.log
On an Exim server replace the postfix jail with exim and point logpath at /var/log/exim_mainlog. cPanel and DirectAdmin login failures need a custom filter; cPanel logs failed logins to /usr/local/cpanel/logs/login_log with the string FAILED LOGIN, which a short regex in /etc/fail2ban/filter.d/cpanel.conf can match:
[Definition]
failregex = ^.*\[.*\] .* FAILED LOGIN .* from <HOST>.*$
Then add a [cpanel] jail with that logpath and backend = auto. Reload and confirm every jail started:
fail2ban-client reload
fail2ban-client status
Permanent bans and manual blocks
CSF’s csf -d has a direct equivalent in firewalld:
firewall-cmd --permanent --zone=drop --add-source=203.0.113.10
firewall-cmd --reload
For a fail2ban ban that persists across restarts, set bantime = -1 in a dedicated recidive jail, which bans addresses that keep getting banned by other jails. That reproduces LF_PERMBLOCK without a separate mechanism.
Common pitfall
Forgetting --permanent is the classic one: the rule works until the next reload and then vanishes. The second is mismatched backends: with backend = systemd fail2ban ignores logpath, so any jail that reads a plain file must set backend = auto explicitly. If a jail shows zero failures for a service you know is under attack, this is almost always why.
Verify
Test each layer independently. From another host, try a port that should be closed and one that should be open:
nc -zv server.example.com 3306
nc -zv server.example.com 443
Then generate a few failed SSH logins from a test address and watch the ban appear:
fail2ban-client status sshd
firewall-cmd --list-rich-rules
The test address should appear in both. Finally, reboot the server once during a maintenance window and repeat the checks; a firewall that only works until the first reboot is worse than none because it stops anyone worrying about it. Our SSH hardening guide pairs well with this setup, since key-only authentication removes most of what fail2ban would otherwise be banning.
Replace CSF with firewalld at a glance

Official documentation: AlmaLinux wiki, Linux man pages.
Related guides: Copy Fail, Dirty Frag and Fragnesia: mitigating the 2026 Linux privilege-escalation trio without a reboot · Installing Sentinel Firewall as a drop-in CSF replacement on Ubuntu 24.04 and Debian 13 · CVE-2026-65638, 65639 and 67402 explained: patching the CSF Messenger and URLGET remote-code flaws.
See also: fail2ban vs CSF in 2026: Which Firewall for a Hosting Server?
Frequently asked questions
Is firewalld with fail2ban as good as CSF for a hosting server?
For port filtering, allow lists, rate limiting and brute-force banning, yes; both use nftables underneath. What it lacks is CSF’s single configuration file, the WHM plugin and integrated checks such as process tracking and the security report, which need separate tooling.
Can I run CSF and firewalld at the same time?
No. Both manage the nftables ruleset and they overwrite each other’s chains on every reload, so the server ends up with whichever loaded last. Remove CSF completely before enabling firewalld.
Does fail2ban work with cPanel or DirectAdmin logins?
Yes, with a custom filter. cPanel writes failed logins to /usr/local/cpanel/logs/login_log and DirectAdmin to /var/log/directadmin/login.log; a short failregex in /etc/fail2ban/filter.d/ plus a jail using backend = auto bans the source address like any other service.
Maintenance record
This guide changes servers, data or security settings, so we re-check it against current versions on a fixed schedule. Take a backup or snapshot before you start.
- Maintained by
- srvScripts editorial team
- Supported versions
- AlmaLinux 9 and 10
- Last full review
- Next review
- Sources
- wiki.almalinux.org