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:

- AlmaLinux

- 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/