Hardening an AlmaLinux 8 Server Against SSH Brute-Force Attacks and Bots
Hardening an AlmaLinux 8 Server Against SSH Brute-Force Attacks and Bots
This guide documents the security hardening performed on an AlmaLinux 8 server running GitLab. The server was receiving a large number of automated SSH login attempts from external IP addresses.
The goal was to improve security without accidentally locking legitimate administrators out of the server.
1. Initial Security Problem
During SSH login, the server reported a large number of failed authentication attempts:
There were xxx failed login attempts since the last successful login.We checked the effective SSH configuration:
sudo sshd -T | egrep 'permitrootlogin|passwordauthentication|pubkeyauthentication'The server was configured as:
permitrootlogin yes
pubkeyauthentication yes
passwordauthentication yesThis meant that automated bots could continuously attempt username/password authentication, including directly against the root account.
2. Check Whether Fail2ban Is Installed
We checked for Fail2ban:
rpm -q fail2banIt was not installed.
3. Fix the EPEL Repository Before Installing Fail2ban
Fail2ban is available through EPEL. However, attempting to install it produced dependency errors because the AlmaLinux 8 server was incorrectly using an EPEL 7 repository.
The problem was identified with:
sudo dnf repoinfo epelThe result showed:
Repo-name : Extra Packages for Enterprise Linux 7 - x86_64This was incorrect because the operating system was AlmaLinux 8.
We inspected the EPEL files:
ls -la /etc/yum.repos.d/epel*
rpm -ql epel-release | grep '\.repo'
rpm -V epel-releaseThe verification showed that the active EPEL files had been modified, while correct EL8 versions were available as .rpmnew files.
Before changing anything, we backed up the existing repository configuration:
sudo mkdir -p /root/epel-repo-backup
sudo cp -a /etc/yum.repos.d/epel*.repo /root/epel-repo-backup/We then replaced the incorrect repository files with the newer EL8 versions:
sudo mv /etc/yum.repos.d/epel.repo.rpmnew /etc/yum.repos.d/epel.repo
sudo mv /etc/yum.repos.d/epel-testing.repo.rpmnew /etc/yum.repos.d/epel-testing.repoThe DNF cache was then rebuilt:
sudo dnf clean all
sudo rm -rf /var/cache/dnf
sudo dnf makecacheVerification:
sudo dnf repoinfo epelThe repository now correctly reported:
Repo-name : Extra Packages for Enterprise Linux 8 - x86_644. Install Fail2ban
We first confirmed that the packages were now EL8 packages:
sudo dnf --showduplicates list fail2ban fail2ban-server fail2ban-firewalldThe correct versions were:
fail2ban.noarch 1.0.2-3.el8
fail2ban-firewalld.noarch 1.0.2-3.el8
fail2ban-server.noarch 1.0.2-3.el8Fail2ban was then installed:
sudo dnf install fail2ban fail2ban-firewalld -yThe installation also installed the appropriate SELinux integration.
5. Verify Firewalld
We confirmed that firewalld was already running:
sudo systemctl status firewalld --no-pagerThe service was active and enabled.
6. Configure Fail2ban for SSH
We created a dedicated SSH jail:
/etc/fail2ban/jail.d/sshd.localConfiguration:
[sshd]
enabled = true
port = ssh
backend = systemd
maxretry = 5
findtime = 10m
bantime = 1h
banaction = firewallcmd-rich-rulesThis means an IP that generates five failed SSH authentication attempts within ten minutes will be banned for one hour.
The configuration was validated before starting Fail2ban:
sudo fail2ban-client -tThe expected result is:
OK: configuration test is successful7. Enable and Start Fail2ban
sudo systemctl enable --now fail2banVerify:
sudo systemctl status fail2ban --no-pagerThen check the SSH jail:
sudo fail2ban-client status sshdOn our server, Fail2ban immediately began detecting attacks and banning IP addresses:
Status for the jail: sshd
|- Filter
| |- Currently failed: 6
| |- Total failed: 23
`- Actions
|- Currently banned: 1
|- Total banned: 1
`- Banned IP list: 152.32.182.8This confirmed that Fail2ban was actively processing SSH authentication failures.
8. Verify Fail2ban Integration With Firewalld
Fail2ban was configured to use firewallcmd-rich-rules, which dynamically creates firewalld rules for banned IP addresses.
Check them with:
sudo firewall-cmd --list-rich-rulesExample:
rule family="ipv4" source address="77.90.185.20" port port="ssh" protocol="tcp" reject type="icmp-port-unreachable"Multiple attacking IP addresses were automatically blocked after Fail2ban was enabled.
9. Create a Dedicated Administrative User
Direct administration using the root SSH account is undesirable. We created a dedicated administrative account:
sudo useradd -m -s /bin/bash ssh-user
sudo passwd ssh-user
sudo usermod -aG wheel ssh-userThe wheel group provides sudo privileges on AlmaLinux.
Verify membership:
id ssh-userWe then opened a completely separate SSH session and logged in as:
ssh ssh-user@SERVER_IPSudo access was tested:
sudo whoamiThe expected result:
rootThis test is extremely important. Do not disable root SSH access until another administrative account has been successfully tested from a separate SSH session.
10. Disable Direct Root SSH Login
Before changing SSH, we backed up the configuration:
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup-20260821Direct root SSH login was disabled:
sudo sed -i 's/^[#[:space:]]*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_configBecause password authentication is still being used for ssh-user, password authentication was intentionally kept enabled:
sudo sed -i 's/^[#[:space:]]*PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_configThe configuration was validated:
sudo sshd -tThen SSH was reloaded without terminating existing sessions:
sudo systemctl reload sshdThe effective configuration was checked:
sudo sshd -T | egrep 'permitrootlogin|passwordauthentication|pubkeyauthentication'The resulting configuration was:
permitrootlogin no
pubkeyauthentication yes
passwordauthentication yesThis means:
- Direct root SSH login is disabled.
- Password authentication remains available for the administrative user.
- Public-key authentication is available for future use.
- Fail2ban protects password authentication against repeated attempts.
11. Test SSH Again Before Closing Existing Sessions
A new SSH session was opened again using ssh-user.
ssh ssh-user@SERVER_IPThen:
sudo whoamireturned:
rootOnly after this test succeeded was it considered safe to close older root SSH sessions.
12. Disable Firewalld AllowZoneDrifting
Firewalld generated the following warning:
WARNING: AllowZoneDrifting is enabled.
This is considered an insecure configuration option.The current setting was checked:
sudo grep -i '^AllowZoneDrifting' /etc/firewalld/firewalld.confIt returned:
AllowZoneDrifting=yesWe changed it to:
sudo sed -i 's/^AllowZoneDrifting=.*/AllowZoneDrifting=no/' /etc/firewalld/firewalld.confVerification:
sudo grep -i '^AllowZoneDrifting' /etc/firewalld/firewalld.confResult:
AllowZoneDrifting=no13. Verify the Active Firewall Zone
The active firewalld zone was checked:
sudo firewall-cmd --get-active-zonesThe server uses:
public
interfaces: ens192The complete configuration was checked:
sudo firewall-cmd --list-allThe important portion was:
public (active)
interfaces: ens192
services: dhcpv6-client http https sshThis means the server currently exposes the services required for GitLab:
- SSH
- HTTP
- HTTPS
No unnecessary custom ports were listed in the active public zone.
14. Reload Firewalld
After verifying the configuration, reload firewalld so the AllowZoneDrifting=no configuration takes effect:
sudo firewall-cmd --reloadThen verify:
sudo firewall-cmd --list-all
sudo firewall-cmd --list-rich-rules
sudo fail2ban-client status sshdIf Fail2ban's runtime rules disappear after a firewall reload, restart Fail2ban:
sudo systemctl restart fail2banThen verify again:
sudo firewall-cmd --list-rich-rules
sudo fail2ban-client status sshd15. Current Security Architecture
Internet
|
v
firewalld
|
+-- HTTP (GitLab)
+-- HTTPS (GitLab)
+-- SSH
|
v
Fail2ban
|
+-- Detect repeated authentication failures
+-- Automatically ban attacking IPs
+-- Add firewalld rich rules
SSH
|
+-- root login: DISABLED
|
+-- ssh-user
|
+-- Password authentication
+-- Protected by Fail2ban
+-- sudo via wheel
16. Useful Fail2ban Commands
Check Fail2ban:
sudo systemctl status fail2banCheck enabled jails:
sudo fail2ban-client statusCheck SSH protection:
sudo fail2ban-client status sshdCheck banned IP addresses:
sudo fail2ban-client status sshdCheck firewall rules:
sudo firewall-cmd --list-rich-rulesManually ban an IP if required:
sudo fail2ban-client set sshd banip IP_ADDRESSManually unban an IP:
sudo fail2ban-client set sshd unbanip IP_ADDRESSRestart Fail2ban:
sudo systemctl restart fail2ban17. Current Fail2ban Policy
The current SSH policy is:
maxretry = 5
findtime = 10m
bantime = 1hThis means:
5 failed SSH attempts
within 10 minutes
|
v
IP banned for 1 hour
For a public production server, the ban duration can later be increased to 12 or 24 hours if desired.
18. Recommended Future Improvement: SSH Keys
The server is significantly safer now, but password authentication is still enabled because the administrator currently uses normal username/password SSH login.
The recommended future configuration is to configure an SSH public key for ssh-user, verify key authentication, and then disable SSH password authentication entirely.
The final desired configuration would be:
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yesThis would prevent password brute-force attacks from succeeding even if an attacker knows a valid SSH username.
19. Important Operational Rules
- Never disable root/password SSH access until another administrative login has been tested.
- Keep an existing SSH session open while modifying SSH or firewall configuration.
- Always run
sshd -tbefore reloading SSH. - Do not expose database, Redis, PostgreSQL, Elasticsearch/OpenSearch, or internal GitLab services directly to the Internet.
- Use Fail2ban as protection against brute-force attacks, not as a replacement for upstream DDoS protection.
- Keep AlmaLinux, GitLab, and security packages patched.
- Review Fail2ban and SSH logs periodically for unusual activity.
20. DDoS Protection Consideration
Fail2ban and firewalld are useful against bots, scanning, and brute-force attacks, but they cannot stop a large volumetric DDoS attack that saturates the server's network connection.
For public HTTP/HTTPS services, an upstream protection layer such as Cloudflare or hosting-provider DDoS protection should be considered.
Internet
|
v
Cloudflare / Provider DDoS Protection
|
v
AlmaLinux Firewall
|
v
Fail2ban / SSH Protection
|
v
GitLab
21. Result
After these changes, the AlmaLinux 8 GitLab server has a substantially stronger security posture:
- Fail2ban installed and enabled.
- SSH brute-force attempts automatically detected.
- Attacking IP addresses automatically blocked through firewalld.
- Direct root SSH login disabled.
- Dedicated
ssh-useradministrative account created. - Sudo access verified.
- Firewalld active.
- Only SSH, HTTP, and HTTPS exposed through the public zone.
AllowZoneDriftingdisabled.- Correct AlmaLinux 8 EPEL repository restored.
- SELinux-compatible Fail2ban packages installed.
- SSH public-key authentication remains available for a future passwordless/key-only configuration.
Current SSH security:
Root SSH login : Disabled
ssh-user SSH login : Enabled
Password login : Enabled
Public key support : Enabled
Fail2ban : Enabled
Firewalld : Enabled
SSH brute-force bans : Enabled
HTTP/HTTPS : Enabled