The first Lynis run on a fresh VPS usually gives something around 60 and a long list of suggestions in which it is unclear what to grab first. The good news: 80 to 85 is one evening's work, and most of the steps are not cosmetic but genuinely worth doing. The bad news: the last fifteen points are unreachable on a rented virtual machine, and chasing them is a mistake.

What the index shows and what it does not

The hardening index is the ratio of passed tests to the total, with weights — not a percentage of security. Lynis runs some three hundred tests and produces two separate lists: warnings, which look like real problems, and suggestions, which are things that could be improved. Always start with the warnings; there are usually only a handful.

A hundred points does not happen: some suggestions require decisions made when the system was installed (separate partitions for /home, /tmp and /var), some need access to the bootloader that a VPS does not have, and some contradict each other. And comparing your index with somebody else's is pointless: Lynis deducts points for what runs on the machine. Every running service is one more open port, its own config and a dozen new suggestions, which is why high figures are more common where nothing but SSH is running. For a working server with a site, a database and mail, 85 is a good result.

Run it as root

sudo lynis audit system

Without sudo Lynis skips every test that needs system files and reports a lower index — not because the server is bad but because the checks were invisible. The report is written to /var/log/lynis-report.dat, the readable log to /var/log/lynis.log.

Every line of output starts with a test identifier such as SSH-7408 or KRNL-6000. That identifier is how you find both the explanation and the way to switch a test off — remember its shape, everything below revolves around it.

What actually moves the number

SSH (SSH-7408). The heaviest block: a single test checks a dozen parameters at once. Do not edit the whole of sshd_config; drop in a separate file so a package update cannot wipe your work:

sudo tee /etc/ssh/sshd_config.d/10-hardening.conf <<'EOF'
PermitRootLogin no
PasswordAuthentication no
MaxAuthTries 3
MaxSessions 2
X11Forwarding no
AllowTcpForwarding no
ClientAliveInterval 300
ClientAliveCountMax 2
LogLevel VERBOSE
TCPKeepAlive no
EOF
sudo sshd -t && sudo systemctl reload ssh

sshd -t before reloading the service is mandatory: a mistake in the config while your session is live will leave you outside. And before setting PasswordAuthentication no, make sure the key really works — from a second open terminal.

Kernel parameters (KRNL-6000). The test checks a couple of dozen sysctl values. A file for them:

sudo tee /etc/sysctl.d/99-hardening.conf <<'EOF'
kernel.dmesg_restrict = 1
kernel.kptr_restrict = 2
kernel.sysrq = 0
fs.protected_hardlinks = 1
fs.protected_symlinks = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.all.log_martians = 1
EOF
sudo sysctl --system

Missing tools. A fair share of the suggestions is simply "install what is not there": auditd (a record of system events), aide (file integrity), debsums (package integrity), unattended-upgrades (automatic security updates), sysstat and acct (process accounting), a malware scanner. Each closes one or two suggestions, but install them not for the points: without them you will reconstruct nothing after an incident.

Small items with a good ratio of effort to reward. umask 027 in /etc/login.defs; password ageing in the same file; the banners in /etc/issue and /etc/issue.net; compilers readable only by root (chmod 700 /usr/bin/gcc). About the banners, honestly: they do nothing for security, they are a legal formality — but they do give a point.

One trap in login.defs

Editing umask with a sed line replacement often leaves several UMASK lines in the file at once. Lynis then does not count the value, and test AUTH-9328 stays red although the setting looks done. Check after editing:

grep -c '^UMASK' /etc/login.defs

There must be exactly one. The same applies to every other parameter in that file.

False positives and how to silence them properly

Some warnings do not apply to your server at all. A live example: on the VPS of one large provider, test PKGS-7388 reports that no security repository was found — simply because the repositories are described in deb822 format with a reference to a mirror file, and Lynis is looking for the familiar line. Security updates arrive perfectly well.

Silence that not by deleting lines from the report but with a profile. Profile files use the .prf extension (not .prof — easy to lose half an hour there), and your own rules go into /etc/lynis/custom.prf:

skip-test=PKGS-7388
skip-test=KRNL-5788

One rule: every line needs a comment next to it saying why the test is skipped. Six months from now you will not remember whether it was disabled because it does not apply or because fixing it was too much trouble — and the difference between those two cases is the whole point.

What not to do

Do not chase the figure. The index is easy to inflate by switching off inconvenient tests, and that is the only way past a certain level — except the server does not become any safer for it. Something else is useful: record your own value and watch it change. An index that drops after an update or somebody else's edit to a config is a far more valuable signal than its absolute size.

Which is exactly why Lynis is worth running on a schedule rather than once, keeping the reports so there is something to compare against. What that looks like collected on one page is on the demo below: the index, the list of warnings with their test identifiers, and what has changed since the previous run.