Hardening audit: Lynis, ANSSI, CIS#

Why#

The previous chapters (SSH hardening, System hardening, nftables, fail2ban) apply individual security measures, each justified on its own. But how can one know whether the combination, taken as a whole, reaches a coherent hardening level, and more importantly, how can that level be objectively compared against recognized frameworks rather than a subjective sense of “the right things were done”? That’s the role of an automated audit.

This guide, including this chapter, does not aim for exhaustiveness on security. Lynis itself doesn’t cover everything (it says so explicitly in its own results, in the form of suggestions): it’s a decision-support tool, not a guarantee of absolute security. Treat the resulting score as one indicator among others, not a certification.

How#

Lynis: the reference auditor#

Lynis runs about 300 checks across more than 60 categories (authentication, network, firewall, storage, logging, SSH, cryptography, kernel hardening, malware detection…). Each test produces a PASS, a WARNING, or a SUGGESTION, and an overall score, the Hardening Index, summarizes the proportion of passed tests on a 0-100 scale.

apt install -y lynis debsums
mkdir -p /var/log/lynis

lynis audit system --quiet 2>&1 | tee /var/log/lynis/baseline-$(date +%Y%m%d_%H%M%S).log

grep "Hardening index" /var/log/lynis/lynis.log
# List points to fix
lynis show warnings
lynis show suggestions

# If "Unknown argument" (happens on some versions):
grep "Warning:" /var/log/lynis/lynis.log
grep "Suggestion:" /var/log/lynis/lynis.log

The frameworks behind Lynis#

Lynis doesn’t come out of nowhere: it operationalizes recommendations from recognized frameworks.

  • CIS Benchmarks (Center for Internet Security): detailed benchmarks per operating system (e.g. CIS Debian 12 Benchmark), with several levels (L1 Server, L1 Workstation, L2 Server…). Lynis broadly sits between CIS’s L1 (more pragmatic) and L2 (more demanding) levels.
  • ANSSI BP-028: the GNU/Linux system configuration recommendations from France’s National Cybersecurity Agency (ANSSI), 80 recommendations spread across 4 levels (minimal, intermediate, reinforced, high). Several directives already set in previous chapters come directly from it: kernel.kptr_restrict=2, kernel.dmesg_restrict=1, 0700 permissions on /etc/cron.*, UMASK 027, high SHA_CRYPT_ROUNDS (System hardening chapter).

Completing the auditd rules now that everything is installed#

The System hardening chapter deliberately limited auditd rules to what already existed at that stage (system accounts, SSH): a -w rule on a missing file makes its own loading fail. Now that chrony, nftables, and fail2ban are installed, the files they use exist, this is the right time to extend monitoring:

cat >> /etc/audit/rules.d/99-stratum1.rules <<'EOF'
-w /etc/chrony/                -p wa -k chrony_config
-w /etc/nftables.conf          -p wa -k nft_config
-w /etc/fail2ban/              -p wa -k f2b_config
EOF

augenrules --load
auditctl -l | grep -E "chrony_config|nft_config|f2b_config"

Interpreting a score#

Lynis scoreLevelTypical profile
50-60AcceptableStandard, unhardened Debian install
60-70GoodBasic hardening applied
70-80Very goodANSSI baseline + first Lynis pass
80-85ExemplaryTarget level for this project
85-90Elite+ AppArmor + AIDE (integrity monitoring)
90+Near-paranoidUsability trade-off often unfavorable

Pitfalls to avoid#

A Lynis warning isn’t automatically a real problem, but never dismiss one without understanding why. A real example encountered on this project: Lynis persistently flagged [WARNING] Can't find any security repository in /etc/apt/sources.list (test PKGS-7388). Cause: the server was then running a Debian version still in testing phase before the freeze, whose repositories used a convention (trixie-updates) different from the one Lynis specifically looks for (a *-security line). This was a documented false positive, specific to this phase of the Debian release cycle, which disappeared on its own after the move to stable (Debian 13 “trixie”, stable since August 2025). Good practice isn’t to silently ignore it, but to explicitly document it as an accepted false positive, with the reason, so a future audit doesn’t restart the investigation from scratch.

  • Aiming for 100/100 without thinking about the cost. Going from 82 to 90+ typically requires per-service AppArmor profiles (several days of work), a file integrity monitoring system like AIDE (recurring CPU/IO load), and a strict password policy (operational friction). For a single operator running a single server, this cost/benefit ratio is often unfavorable, AppArmor really pays off when comparing and maintaining hundreds of servers, much less on a single instance already audited monthly.
  • Copying a Lynis remediation without checking that it applies to the actual context. Some Lynis suggestions only make sense on specific architectures (e.g. separate /home and /var partitions, irrelevant on a Raspberry Pi with a single rootfs by design). Consciously accepting an inapplicable suggestion, documented as such, beats forcing a remediation that doesn’t match the actual architecture.
  • Handling each suggestion in isolation without a big picture. Of the roughly twenty suggestions typically remaining at 82/100, most are either informational (Lynis version, no external syslog on a standalone server) or deliberate policy trade-offs (password expiration period), list and decide on them once, rather than revisiting each individually on every audit.

Verify#

# Current score
grep "Hardening index" /var/log/lynis/lynis.log | tail -1

# Details of a specific test to understand a warning
lynis show details SSH-7408

# Audit freshness (Lynis itself flags a stale audit)
stat -c %Y /var/log/lynis/lynis.log

Schedule Lynis as a monthly task (cron or a systemd timer) rather than only running it at install time. A score that degrades over time (a package removed, a service re-enabled by an update, a permission manually changed) is far easier to fix caught early than after months of silent drift.


📚 Going further