This 2026 edition expands on that foundation with layered SSH hardening: ed25519 keys with high KDF iteration counts, a fully locked-down sshd_config, firewall-level rate limiting, incremental Fail2ban banning, optional port knocking, 2FA, automated audits, and a quarterly review schedule.
Recipes for:
- Debian
- openSUSE
- Ubuntu
AlmaLinux / Rocky Linux
Preparation
Several packages are not available from the standard repository. Add the EPEL repo (Extra packages for Enterprise Linux) to get access to all packages referenced below (adding this is free):
# dnf install epel-release -y
# dnf config-manager --set-enabled epel
# dnf update -y
Set up cron and crontab [1]:
# dnf install cronie-noanacron
and
# dnf remove cronie-anacron
Update and Create Admin User
New images often ship with known vulnerabilities. Before doing anything else, patch the system and create a dedicated non-root admin user. Never use root for day-to-day operations.
# dnf update -y
# adduser admin_user
# usermod -aG wheel admin_user
Verify sudo works before continuing:
# su - admin_user
$ sudo whoami
This should print: root. Do not disable root SSH access until you have confirmed the admin user can sudo successfully.
Firewall With firewall-cmd
The firewall should be enabled as part of the installation process, as you presumably have direct terminal access. You can do anything in this list via ssh, but the firewall must be setup to let ssh through. This is normally the default setting.
If the firewall is inactive, get the firewall going with systemctl. OBS: If you are accessing the server via SSH, the access may be lost!
# systemctl start firewalld.service
# systemctl enable firewalld.service
Now, you can add the ssh service (and perhaps also other services, such as http, https, etc):
# firewall-cmd --zone=public --add-service=ssh --permanent
# firewall-cmd --reload
SSH Keys: Use ed25519
The older practice of using RSA keys (id_rsa) has been superseded. Generate an ed25519 key with a high KDF iteration count (-a 100), which makes passphrase brute-forcing significantly slower. Run this on your local machine:
$ ssh-keygen -t ed25519 -a 100 -C "your_email@example.com"
Accept the default path (~/.ssh/id_ed25519) and set a passphrase. This creates two files:
~/.ssh/id_ed25519 # private key — never share this
~/.ssh/id_ed25519.pub # public key — safe to copy to servers
Copy the public key to the server:
$ ssh-copy-id -i ~/.ssh/id_ed25519.pub username@server
Before you try to log in from the client, restart ssh on the server:
# systemctl restart sshd.service
Next, log in from the client to verify key login works before proceeding:
$ ssh user@server
Keep this session open as a fallback. Open a second terminal and confirm key login works before disabling passwords.
Block ssh Password Login
Edit the ssh configuration file:
# vi /etc/ssh/sshd_config
The original guide only sets PasswordAuthentication no. A fully hardened config also disables root login, PAM bypass vectors, whitelists users, drops idle sessions, and reduces attack surface:
# Disable password-based logins entirely
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
UsePAM no
PermitEmptyPasswords no
PubkeyAuthentication yes
# Never allow direct root logins
PermitRootLogin no
# Whitelist which users can log in at all
AllowUsers admin_user
# Restrict to modern key types only
PubkeyAcceptedAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256
# Limit login window and retries
LoginGraceTime 30
MaxAuthTries 3
MaxSessions 5
# Drop idle sessions after 5 minutes
ClientAliveInterval 300
ClientAliveCountMax 0
TCPKeepAlive no
# Attack surface reduction
UseDNS no
AllowAgentForwarding no
AllowTcpForwarding no
X11Forwarding no
# Optional: move SSH off port 22 to reduce scanner noise
# Port 2222
# Optional: legal deterrent banner
# Banner /etc/issue.net
If you change the SSH port (e.g., to 2222), also update your firewall rule, your Fail2ban jail (port = 2222), and your ssh-copy-id command (-p 2222). Do not lock yourself out.
If you enabled the banner, create /etc/issue.net:
# echo "Unauthorized access to this system is prohibited. All activity is logged." > /etc/issue.net
Restart ssh:
# systemctl restart sshd.service
Firewall Rate Limiting for SSH
Add a proactive connection throttle at the firewall level. This stops brute-force attempts before they even reach sshd, complementing Fail2ban's reactive approach [2]:
# iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --set
# iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 4 -j DROP
This drops any IP trying more than 3 new connections per 60 seconds. Make the rules permanent:
# iptables-save > /etc/iptables/rules.v4
Filtering Malicious IPs With fail2ban
Fail2ban scans log files and bans IPs that show certain signs of malicious intent. Filters for many services are ready out of the box [2]. Simply:
# dnf install fail2ban
Never edit jail.conf directly — it gets overwritten on upgrade. Create a local override instead:
# cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
# vim /etc/fail2ban/jail.local
Edit the [DEFAULT] and [sshd] sections. The settings below ban IPs after 5 failed attempts within 10 minutes, for 1 hour initially, with the ban doubling for repeat offenders:
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1
bantime.increment = true
bantime.multiplier = 2
[sshd]
enabled = true
port = ssh
logpath = /var/log/secure
maxretry = 5
findtime = 600
bantime = 3600
# systemctl enable fail2ban
# systemctl start fail2ban
Monitor Fail2ban in real time:
# journalctl -u fail2ban -f
Port Knocking (Optional)
For maximum stealth, port knocking keeps the SSH port closed entirely until the server receives a specific sequence of connection attempts. This eliminates SSH from automated scanner results. Install knockd:
# dnf install knockd
# vim /etc/knockd.conf
[openSSH]
sequence = 7000,8000,9000
seq_timeout = 5
command = /usr/sbin/iptables -A INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
tcpflags = syn
[closeSSH]
sequence = 9000,8000,7000
seq_timeout = 5
command = /usr/sbin/iptables -D INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
tcpflags = syn
Enable knockd (set START_KNOCKD=1 in /etc/default/knockd first), then start it:
# systemctl enable knockd
# systemctl start knockd
To use from your local machine:
$ knock your.server.ip 7000 8000 9000
$ ssh user@your.server.ip
$ knock your.server.ip 9000 8000 7000
Port knocking is security through obscurity — use it in addition to, not instead of, key-only auth and Fail2ban. Change the default port sequence from 7000/8000/9000.
System Audit
For most systems, lynis is the go-to package for system audits. With epel enabled:
# dnf install lynis
# lynis audit system
A long report is shown, where the most important part is what is shown under Lynis *.*.* Results. Anything appearing under Warnings should be dealt with immediately. There is also a long list of suggestions that you may want to take a look at.
Automate with a weekly cron job so audits don't get forgotten:
# crontab -e
0 3 * * 1 /usr/bin/lynis audit system >> /var/log/lynis-report.log
Note: You can get quite a few things done with auditd. Take a look at this excellent article to get going.
Malware Scan With clamav
Install, enable and start clamav packages. Freshclam keeps the clamav-database up to date and should run continuously as a service:
# dnf install clamav clamd clamav-update
# systemctl enable clamav-freshclam
# systemctl start clamav-freshclam
The first time you run a scan, freshclam creates the database for you:
# freshclam
Clamscan should run at regular intervals. Add it to cron, using nice to avoid hogging the server:
# crontab -e
* 3 * * 1,3,5 nice -n 15 clamscan -ir -l /var/log/clamav.log --exclude-dir="^/proc/|^/sys/|^/dev/" --max-scansize=50M //.
The command runs every Monday, Wednesday, and Friday at 3 am. The -i flag reports only infected files, -r is recursive, and --exclude-dir skips directories unlikely to contain malware.
Unattended Security Upgrades
Use dnf-automatic for proper unattended upgrades rather than a raw cron job:
# dnf install dnf-automatic
# systemctl enable --now dnf-automatic.timer
Edit /etc/dnf/automatic.conf to set upgrade_type = security and configure email notifications.
Backup
The best backup solution for you will depend a lot on your setup. My favorite solution is — hands down — BorgBackup. It includes deduplication, compression, and authenticated encryption. Moreover, it works well for both desktops and servers. For the former, the Vorta GUI is really handy. With a separate machine holding the backups, the installation looks like this on both server and remote backup server (if both are AlmaLinux, Rocky Linux, or similar — for other distributions, see [3]):
# dnf install borgbackup
Initialize a repository on the remote backup server:
$ borg init --encryption=repokey /path/to/repo
Enter a passphrase when prompted, then export and store the repo key — you will need both to restore:
$ borg key export /path/to/repo key-backup.txt
Create a backup:
$ borg create ssh://user@server//path/to/repo::$(date +%Y-%m-%d) ~/
Verify backups monthly — a backup you have never tested is a backup you cannot trust:
$ borg check /path/to/repo
Two-Factor Authentication for SSH (Optional)
For environments where an extra layer beyond keys is warranted — particularly shared or team servers — you can require a TOTP code on every SSH login. Note: this requires UsePAM yes, which conflicts with the UsePAM no setting above. For most personal servers, key-only auth is already very strong; 2FA is most valuable on multi-user systems.
# dnf install google-authenticator
Run as the user who will log in, to generate a TOTP secret:
$ google-authenticator
Add this line to /etc/pam.d/sshd:
auth required pam_google_authenticator.so
And in /etc/ssh/sshd_config:
ChallengeResponseAuthentication yes
AuthenticationMethods publickey,keyboard-interactive
# systemctl restart sshd
Monitoring With Grafana, Loki and Promtail
Services such as clamav don't do much good if you are not keeping an eye on logs. That also goes for the server maintenance as such. There are many ways to look at logs. Personally, I really like the grafana log-stack, with grafana as a front end, combined with the loki database, and the promtail client-side agent that ships logs to loki. Configure Loki's retention_period to balance storage cost against log history — a 30-day default is a sensible starting point.
A normal setup is to have loki and grafana on a separate machine, and then use promtail to shovel logs into loki from each client. To get started quick, look at this webinar on loki and grafana. To get going with promtail, all the details you need are here.
For a lighter-weight start, logwatch sends daily SSH summaries by email:
# dnf install logwatch
# logwatch --detail High --service sshd --range today --format text
Schedule a daily report:
0 6 * * * /usr/sbin/logwatch --detail High --service sshd --range yesterday --mailto you@example.com --format html
Debian
Preparation
Most of the following commands require root / sudo access, so on Debian it makes sense to change to root user (as users are by default not sudoers on Debian installations):
$ su root
Update and Create Admin User
Patch the system immediately, then create a dedicated non-root admin user before doing anything else:
# apt update && apt upgrade -y
# adduser admin_user
# usermod -aG sudo admin_user
Verify sudo works before continuing:
# su - admin_user
$ sudo whoami
This should print: root. Do not disable root SSH access until you have confirmed this.
Firewall
There are many firewalls to choose from [4], but the simplest choice is to go with Ubuntu Uncomplicated Firewall (ufw) [5].
# apt install ufw -y
Set explicit default policies first, then whitelist only what you need. If you are accessing the server via ssh, allow ssh before enabling the firewall:
# ufw default deny incoming
# ufw default allow outgoing
# ufw allow ssh
# ufw enable
List of current rules:
# ufw status numbered
Other typical services to add are http and https. For a full list of available apps:
# ufw app list
Add connection rate limiting to throttle brute-force attempts before they reach sshd:
# ufw limit ssh/tcp
If you changed the SSH port (e.g., to 2222), use that port number instead of 'ssh' in the rules above, and delete the default ssh rule afterwards.
SSH Keys: Use ed25519
Generate an ed25519 key with a high KDF iteration count on your local machine:
$ ssh-keygen -t ed25519 -a 100 -C "your_email@example.com"
Copy the public key to the server:
$ ssh-copy-id -i ~/.ssh/id_ed25519.pub username@server
Restart ssh, then verify key login works before proceeding:
# systemctl restart sshd.service
$ ssh user@server
Keep this session open as a fallback while you make the following changes.
Block ssh Password Login
Edit the ssh configuration file:
# vi /etc/ssh/sshd_config
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
UsePAM no
PermitEmptyPasswords no
PubkeyAuthentication yes
PermitRootLogin no
AllowUsers admin_user
PubkeyAcceptedAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256
LoginGraceTime 30
MaxAuthTries 3
MaxSessions 5
ClientAliveInterval 300
ClientAliveCountMax 0
TCPKeepAlive no
UseDNS no
AllowAgentForwarding no
AllowTcpForwarding no
X11Forwarding no
# Port 2222
# Banner /etc/issue.net
Restart ssh:
# systemctl restart ssh
Filtering Malicious IPs With fail2ban
Fail2ban scans log files and bans IPs that show certain signs of malicious intent. Filters for many services are ready out of the box [2].
# apt install fail2ban
# cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
# vim /etc/fail2ban/jail.local
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1
bantime.increment = true
bantime.multiplier = 2
[sshd]
enabled = true
port = ssh
logpath = /var/log/auth.log
maxretry = 5
findtime = 600
bantime = 3600
# systemctl enable fail2ban
# systemctl start fail2ban
System Audit With lynis
Install lynis by adding the lynis repository. First, make a directory for the public key for the repository:
# mkdir -p /etc/apt/keyrings/
# wget -O - https://packages.cisofy.com/keys/cisofy-software-public.key | sudo tee /etc/apt/keyrings/cisofy-software-public.key > /dev/null
Add the repository to apt sources:
# echo "deb [signed-by=/etc/apt/keyrings/cisofy-software-public.key] https://packages.cisofy.com/community/lynis/deb/ stable main" | sudo tee /etc/apt/sources.list.d/cisofy-lynis.list
# apt update && apt install lynis
# lynis audit system
Anything appearing under Warnings should be dealt with immediately. Automate with a weekly cron job:
# crontab -e
0 3 * * 1 /usr/bin/lynis audit system >> /var/log/lynis-report.log
Scanning For Malware With clamav
Install clamav:
# apt install clamav cpulimit
# systemctl enable clamav-freshclam
# systemctl start clamav-freshclam
Set up regular scans with cron, using cpulimit to avoid resource hogging:
# crontab -e
* 3 * * 1,3,5 cpulimit -l 30 -- clamscan -ir -l /var/log/clamav.log --exclude-dir="^/proc/|^/sys/|^/dev/" --max-scansize=50M //.
Unattended Security Upgrades
Minimum setup:
# apt install unattended-upgrades -y
# vim /etc/apt/apt.conf.d/50unattended-upgrades
Change:
Unattended-Upgrade::Mail "";
Unattended-Upgrade::MailReport "only-on-error";
Create an apt configuration file:
# vim /etc/apt/apt.conf.d/02periodic
with the following contents:
APT::Periodic::Enable "1";
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::AutocleanInterval "21";
APT::Periodic::Verbose "2";
Check status:
# systemctl restart unattended-upgrades.service
# systemctl status unattended-upgrades.service
Backup
The best backup solution for you will depend a lot on your setup. My favorite solution is — hands down — BorgBackup. It includes deduplication, compression, and authenticated encryption. Moreover, it works well for both desktops and servers. For other distributions, see [3].
# apt install borgbackup
Initialize a repository on the remote backup server:
$ borg init --encryption=repokey /path/to/repo
Export and store the repo key:
$ borg key export /path/to/repo key-backup.txt
Create a backup:
$ borg create ssh://user@server//path/to/repo::$(date +%Y-%m-%d) ~/
Verify backups monthly:
$ borg check /path/to/repo
Monitoring With Grafana, Loki and Promtail
Services such as clamav don't do much good if you are not keeping an eye on logs. That also goes for the server maintenance as such. There are many ways to look at logs. Personally, I really like the grafana log-stack, with grafana as a front end, combined with the loki database, and the promtail client-side agent that ships logs to loki. Configure Loki's retention_period to balance storage cost against log history.
A normal setup is to have loki and grafana on a separate machine, and then use promtail to shovel logs into loki from each client. To get started quick, look at this webinar on loki and grafana. To get going with promtail, all the details you need are here.
openSUSE
Update and Create Admin User
Patch the system immediately, then create a dedicated non-root admin user:
$ sudo zypper update -y
$ sudo useradd -m -G wheel admin_user
$ sudo passwd admin_user
Verify sudo works before continuing.
Firewall With firewall-cmd
The firewall should be enabled as part of the installation process, as you presumably have direct terminal access. You can do anything in this list via ssh, but the firewall must be active to let ssh through, which means that it will initially block ssh. Catch 22!
Get the firewall going:
$ sudo systemctl start firewalld.service
$ sudo systemctl enable firewalld.service
Now, you can add the ssh service:
$ sudo firewall-cmd --zone=public --add-service=ssh --permanent
$ sudo firewall-cmd --reload
Add firewall-level rate limiting to throttle brute-force attempts:
$ sudo iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --set
$ sudo iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 4 -j DROP
SSH Keys: Use ed25519
Generate an ed25519 key with a high KDF iteration count on your local machine:
$ ssh-keygen -t ed25519 -a 100 -C "your_email@example.com"
$ ssh-copy-id -i ~/.ssh/id_ed25519.pub username@server
Restart ssh, then verify key login works before proceeding:
# systemctl restart sshd.service
$ ssh user@server
Block ssh Password Login
# vi /etc/ssh/sshd_config
PasswordAuthentication no
ChallengeResponseAuthentication no
PermitRootLogin no
PermitEmptyPasswords no
PubkeyAuthentication yes
UsePAM no
AllowUsers admin_user
PubkeyAcceptedAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256
LoginGraceTime 30
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 0
TCPKeepAlive no
UseDNS no
AllowAgentForwarding no
AllowTcpForwarding no
X11Forwarding no
# systemctl restart ssh
Filtering Malicious IPs With fail2ban
Fail2ban scans log files and bans IPs that show certain signs of malicious intent. Filters for many services are ready out of the box [2].
$ sudo zypper install fail2ban
$ sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
$ sudo vim /etc/fail2ban/jail.local
[DEFAULT]
ignoreip = 127.0.0.1/8
bantime.increment = true
bantime.multiplier = 2
[sshd]
enabled = true
port = ssh
logpath = /var/log/messages
maxretry = 5
findtime = 600
bantime = 3600
$ sudo systemctl enable fail2ban
$ sudo systemctl start fail2ban
Lynis
Install lynis by adding the lynis repository. First, import the GPG key:
# rpm --import https://packages.cisofy.com/keys/cisofy-software-rpms-public.key
# zypper addrepo --gpgcheck --name "CISOfy Lynis repository" --priority 1 --refresh --type rpm-md https://packages.cisofy.com/community/lynis/rpm/ lynis
# zypper refresh && zypper install lynis
# lynis audit system
Automate with a weekly cron job:
0 3 * * 1 /usr/bin/lynis audit system >> /var/log/lynis-report.log
Scanning For Malware With clamav
Install clamav:
$ sudo zypper install clamav
$ sudo freshclam
$ sudo systemctl enable clamav-freshclam
$ sudo systemctl start clamav-freshclam
Add clamscan to cron:
$ sudo crontab -e
* 3 * * 1,3,5 nice -n 15 clamscan -ir -l /var/log/clamav.log --exclude-dir="^/proc/|^/sys/|^/dev/" --max-scansize=50M //.
Unattended Security Upgrades
Install the yast2 online update tool:
$ sudo zypper install yast2-online-update-configuration
$ sudo yast2 online_update_configuration
These choices give you a weekly update of non-interactive security patches. If you want automatic reboots whenever a package installation requires a reboot, you can do this using the Reboot Manager:
$ sudo zypper install rebootmgr
$ sudo systemctl enable --now rebootmgr.service
Backup
$ sudo zypper install borgbackup
$ borg init --encryption=repokey /path/to/repo
$ borg key export /path/to/repo key-backup.txt
$ borg create ssh://user@server//path/to/repo::$(date +%Y-%m-%d) ~/
Verify backups monthly:
$ borg check /path/to/repo
Monitoring With Grafana, Loki and Promtail
Services such as clamav don't do much good if you are not keeping an eye on logs. That also goes for the server maintenance as such. There are many ways to look at logs. Personally, I really like the grafana log-stack, with grafana as a front end, combined with the loki database, and the promtail client-side agent that ships logs to loki.
A normal setup is to have loki and grafana on a separate machine, and then use promtail to shovel logs into loki from each client. To get started quick, look at this webinar on loki and grafana. To get going with promtail, all the details you need are here.
Ubuntu
Update and Create Admin User
Patch the system immediately, then create a dedicated non-root admin user before doing anything else:
$ sudo apt update && sudo apt upgrade -y
$ sudo adduser admin_user
$ sudo usermod -aG sudo admin_user
Verify sudo works before continuing. Do not disable root SSH access until you have confirmed this.
Firewall With ufw
Ubuntu Uncomplicated Firewall (ufw) is easy to use. Set explicit default policies first, then whitelist only what you need. If you are accessing the server via ssh, allow ssh before enabling the firewall:
$ sudo ufw default deny incoming
$ sudo ufw default allow outgoing
$ sudo ufw allow ssh
$ sudo ufw enable
Add connection rate limiting to throttle brute-force attempts before they reach sshd:
$ sudo ufw limit ssh/tcp
Other typical services to add are http and https. For a full list of available apps:
$ sudo ufw app list
SSH Keys: Use ed25519
Generate an ed25519 key with a high KDF iteration count on your local machine:
$ ssh-keygen -t ed25519 -a 100 -C "your_email@example.com"
$ ssh-copy-id -i ~/.ssh/id_ed25519.pub username@server
Restart ssh, then verify key login works before proceeding:
# systemctl restart sshd.service
$ ssh user@server
Keep this session open as a fallback while you make the following changes.
Block ssh Password Login
Edit the ssh configuration file:
# vi /etc/ssh/sshd_config
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
UsePAM no
PermitEmptyPasswords no
PubkeyAuthentication yes
PermitRootLogin no
AllowUsers admin_user
PubkeyAcceptedAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256
LoginGraceTime 30
MaxAuthTries 3
MaxSessions 5
ClientAliveInterval 300
ClientAliveCountMax 0
TCPKeepAlive no
UseDNS no
AllowAgentForwarding no
AllowTcpForwarding no
X11Forwarding no
# Port 2222
# Banner /etc/issue.net
Restart ssh:
# systemctl restart ssh
Filtering Malicious IPs With fail2ban
Fail2ban scans log files and bans IPs that show certain signs of malicious intent. Filters for many services are ready out of the box [2].
$ sudo apt install fail2ban
$ sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
$ sudo vim /etc/fail2ban/jail.local
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1
bantime.increment = true
bantime.multiplier = 2
[sshd]
enabled = true
port = ssh
logpath = /var/log/auth.log
maxretry = 5
findtime = 600
bantime = 3600
$ sudo systemctl enable fail2ban
$ sudo systemctl start fail2ban
System Audit With lynis
Install lynis by adding the lynis repository. First, make a directory for the public key for the repository:
$ sudo mkdir -p /etc/apt/keyrings/
$ sudo wget -O - https://packages.cisofy.com/keys/cisofy-software-public.key | sudo tee /etc/apt/keyrings/cisofy-software-public.key > /dev/null
$ echo "deb [signed-by=/etc/apt/keyrings/cisofy-software-public.key] https://packages.cisofy.com/community/lynis/deb/ stable main" | sudo tee /etc/apt/sources.list.d/cisofy-lynis.list
$ sudo apt update && sudo apt install lynis
$ sudo lynis audit system
Anything appearing under Warnings should be dealt with immediately. Automate with a weekly cron job:
$ sudo crontab -e
0 3 * * 1 /usr/bin/lynis audit system >> /var/log/lynis-report.log
Scanning For Malware With clamav
Install clamav:
$ sudo apt install clamav cpulimit
$ sudo systemctl enable clamav-freshclam
$ sudo systemctl start clamav-freshclam
Set up regular scans with cron and cpulimit:
$ sudo crontab -e
* 3 * * 1,3,5 cpulimit -l 30 -- clamscan -ir -l /var/log/clamav.log --exclude-dir="^/proc/|^/sys/|^/dev/" --max-scansize=50M //.
Unattended Security Upgrades
To get going with unattended security upgrades on Ubuntu is straightforward:
$ sudo apt install unattended-upgrades -y
For more options, take a look at the configuration file:
$ sudo vi /etc/apt/apt.conf.d/50unattended-upgrades
Furthermore, this article provides a lot of detail on commands and options.
Backup
$ sudo apt install borgbackup
$ borg init --encryption=repokey /path/to/repo
$ borg key export /path/to/repo key-backup.txt
$ borg create ssh://user@server//path/to/repo::$(date +%Y-%m-%d) ~/
Verify backups monthly:
$ borg check /path/to/repo
Monitoring With Grafana, Loki and Promtail
Services such as clamav don't do much good if you are not keeping an eye on logs. That also goes for the server maintenance as such. There are many ways to look at logs. Personally, I really like the grafana log-stack, with grafana as a front end, combined with the loki database, and the promtail client-side agent that ships logs to loki.
A normal setup is to have loki and grafana on a separate machine, and then use promtail to shovel logs into loki from each client. To get started fast, look at this webinar on loki and grafana. To get going with promtail, all the details you need are here.
References
[1] Cron in Rocky Linux - https://docs.rockylinux.org/en/guides/automation/cron_jobs_howto/
[2] Fail2ban Wiki - http://www.fail2ban.org/wiki/index.php/Main_Page
[3] Borg 1.2 Installation - https://borgbackup.readthedocs.io/en/1.2-maint/installation.html
[4] Firewalls (for Debian) - https://wiki.debian.org/Firewalls
[5] Install/Configure UFW Firewall on Debian 11 Bullseye - https://www.linuxcapable.com/how-to-setup-and-configure-ufw-firewall-on-debian-11-bullseye/
[6] Locking the Gate: SSH Security in 2026, Linux Magazine Issue 302 - https://www.linux-magazine.com/Issues/2026/302/SSH-Security
Other useful references:
How to Use the ssh-copy-id Command - https://bytexd.com/how-to-use-the-ssh-copy-id-command/
How to Disable SSH Login With Password - https://linuxhandbook.com/ssh-disable-password-authentication/
CISOFY Software Repository (Community) - https://packages.cisofy.com/community/
How to stop ClamAV/Clamscan from hogging the CPU/ draining the battery in Ubuntu 18.04? https://askubuntu.com/questions/1158514/how-to-stop-clamav-clamscan-from-hogging-the-cpu-draining-the-battery-in-ubuntu
UFW Essentials: Common Firewall Rules and Commands - https://www.digitalocean.com/community/tutorials/ufw-essentials-common-firewall-rules-and-commands
Linux System Auditing with auditd - https://www.tecmint.com/linux-system-auditing-with-auditd-tool-on-centos-rhel/
Grafana/Loki getting started webinar - https://grafana.com/go/webinar/loki-getting-started/
Loki and Promtail setup - https://ericeikrem.com/loki-promtail/