Lynis 3.1.7 is the latest release on the project’s GitHub release record, and it still does what it has done for years: it walks a Linux box from the inside, runs a few hundred checks, and hands you a hardening score plus a to-do list. A recent Serverküche walkthrough from October 17, 2026 ran that version on Debian 13 and made the same point most admins learn the hard way. The scan takes a minute or two, but deciding which suggestions to follow takes the evening.
This tutorial walks through a full Lynis security audit in 13 steps. You will install the current release from the vendor repository, read the report files properly, fix the findings that matter, skip the ones that would break a Docker host, and schedule a monthly run that fails loudly when the score drops. If you want a benchmark to compare against afterwards, our CIS Benchmarks server hardening guide covers the control catalog that many Lynis tests mirror. Budget about 45 minutes for the first pass.
Don't miss new tech stories on Google
Add Tech Insider once in the Google app and our stories appear in your news suggestions.
What Lynis Is and What Changed in 2026
Lynis is an open-source auditing tool for Linux, macOS and other Unix-like systems, maintained by CISOfy. CISOfy describes itself as an independent, bootstrapped company that has worked on information security since 2013. The tool is a shell script with no agent and no daemon. You run it as root, it inspects the local machine, and it writes two files you can parse later. That design explains both its strengths (nothing to deploy, nothing to keep patched) and its limits (it only sees the host it runs on).
Here is the current state as of October 27, 2026. The GitHub release record lists 3.1.7 as the newest numbered release, published on June 25, 2026, and no newer version had been confirmed at the time of writing. The repository stays active, though. Reportedly, an issue opened on October 16 and updated October 17 describes Lynis 3.1.7 failing to recognize arch-audit as an installed package-audit tool on CachyOS, which skips test PKGS-7320. Another issue, opened July 31 and updated again on October 27, covers a false positive in kernel test KRNL-5830 on Raspberry Pi OS. Neither is a vulnerability. Both show what you should expect from a script that checks hundreds of distro-specific details: occasional wrong answers on less common systems.
One more detail trips people up. Some third-party tutorials published this month still test Lynis 3.0.9, which is older than 3.1.7. If a guide shows a version below 3.1.7 in its output, its test list is older than the one you will run. Newer releases carry newer checks, so scores from different versions do not compare cleanly.
Lynis vs outside scanners vs benchmark checklists
People often file Lynis next to Nessus or OpenVAS. That comparison hides the real difference. Network scanners probe open ports from outside. Lynis reads configuration from inside. The table below shows where each approach fits.
| Approach | Vantage point | Finds | Misses |
|---|---|---|---|
| Lynis | Local, root shell | Weak SSH settings, sysctl gaps, missing auditd rules, file permissions, stale packages | Remote exposure, web app bugs, network-level flaws |
| Network vulnerability scanner | Remote, over the wire | Open ports, banner-based CVEs, TLS problems | Local config such as umask, mount options, PAM |
| CIS Benchmark PDF | Manual reading | Full control catalog with rationale | Nothing automatically, you check each control by hand |
| Lynis Enterprise | Central dashboard | Fleet view and compliance reporting | Pricing and plan details were not confirmed from official pages this week |
Use Lynis as the fast first pass, then use the benchmark for the controls Lynis does not test. An October 2 guide on infosec4tc makes the same split and notes that Lynis is not a CIS-certified scanner and does not map one to one onto every CIS control. Treat that as the correct mental model.
Prerequisites and Versions
You need little. Lynis runs on any machine where you have root, which is the reason it works so well on throwaway VPS instances and home labs. Use a test machine or a snapshot for the first run, because several steps below change kernel parameters and SSH settings. Ubuntu is a safe default for this tutorial: the 2025 Stack Overflow Developer Survey, which collected more than 49,000 responses from developers in 177 countries, found that 27.7% of respondents used Ubuntu professionally.
| Component | Version used here | Notes |
|---|---|---|
| Lynis | 3.1.7 | Latest release on the GitHub release record as of October 27, 2026 |
| Operating system | Ubuntu 24.04 LTS or Debian 13 | Both appear in October 2026 Lynis tutorials, commands below work on either |
| Privileges | root or sudo | Lynis skips many tests without root |
| Shell tools | bash, grep, sed, curl, systemd | All ship by default on both distributions |
| Optional | Docker | Only needed to follow the container-host warnings in Step 7 |
| Time needed | About 45 minutes | The audit itself takes one to two minutes |
Two safety rules apply before you touch anything. First, keep a second SSH session open for the whole tutorial, because a typo in an SSH drop-in file plus a blind restart locks you out. Second, write down what the machine does. A mail server, a Docker host and a bastion box deserve different answers to the same Lynis suggestion, and you cannot judge a suggestion without knowing the workload.
Step 1: Take a Snapshot and Record a Baseline
Start by capturing the machine’s current state, so you can roll back and so you can prove later what changed. On a cloud VPS, take a provider snapshot. On a physical box or a VM, copy the files you are about to edit. The commands below create a dated backup folder and save the facts you will need for judging suggestions, such as the kernel version, listening ports and running services.
sudo install -d -m 0700 /root/lynis-lab
sudo cp -a /etc/ssh /root/lynis-lab/ssh.bak
sudo cp -a /etc/sysctl.d /root/lynis-lab/sysctl.d.bak
sudo cp -a /etc/login.defs /root/lynis-lab/login.defs.bak
uname -r | sudo tee /root/lynis-lab/kernel.txt
sudo ss -tulpn | sudo tee /root/lynis-lab/listening.txt
systemctl list-units --type=service --state=running --no-pager | sudo tee /root/lynis-lab/services.txt
The listening-ports file matters more than it looks. When Lynis later suggests closing something, you can check this list and ask whether anything real depends on it. Open a second terminal now and log in over SSH. Leave it idle. If a configuration change breaks the first session, the second one lets you undo it.
Step 2: Install Lynis From the Vendor Repository
Distribution packages lag. The Serverküche tutorial found Debian offering an older 3.1.4 build while the CISOfy repository served 3.1.7. That gap matters because Lynis is mostly a collection of tests, and an old test collection misses recently added checks. The vendor repository gives you updates through apt without cloning anything.
sudo install -d -m 0755 /etc/apt/keyrings
sudo curl -fsSL https://packages.cisofy.com/keys/cisofy-software-public.key \
-o /etc/apt/keyrings/cisofy.asc
head -1 /etc/apt/keyrings/cisofy.asc
echo "deb [signed-by=/etc/apt/keyrings/cisofy.asc] https://packages.cisofy.com/community/lynis/deb/ stable main" \
| sudo tee /etc/apt/sources.list.d/cisofy-lynis.list
sudo apt-get update
sudo apt-get install -y lynis
lynis --version
Storing the key as an ASCII-armored .asc file lets apt read it directly, so you do not need gnupg installed. The signed-by option ties that key to this one repository, which limits what a compromised key could do. The head command should print the PGP header line, not an HTML error page.
-----BEGIN PGP PUBLIC KEY BLOCK-----
3.1.7
On RHEL, Rocky or Alma, install from EPEL with sudo dnf install epel-release && sudo dnf install lynis. If you want the newest code on any distribution, clone the repository instead with git clone https://github.com/CISOfy/lynis and run ./lynis from inside the folder. The clone method needs no installation, which makes it handy for one-off audits of machines you do not own.
Step 3: Run Your First Lynis Security Audit
One command starts the audit. The --quick flag removes the pauses where Lynis waits for a keypress, and --no-colors keeps the saved text readable. Piping through tee gives you a copy to compare against later.
sudo lynis audit system --quick --no-colors | sudo tee /root/lynis-lab/run1.txt
Lynis groups its tests into categories and prints a status next to each line. You will see sections such as boot and services, kernel, memory and processes, users and groups, shells, file systems, storage, networking, printers and spools, software, SSH support, logging and files, and security frameworks. A run on a typical VPS takes one to two minutes. Heavy machines with many packages or large file trees take longer, and some desktop distributions run slowly during the file permission checks.
The end of the run prints a summary block. This is the illustrative layout, and your numbers will differ:
Lynis security scan details:
Hardening index : NN [############## ]
Tests performed : NNN
Plugins enabled : 0
Warnings (N):
! Found one or more vulnerable packages. [PKGS-7392]
Suggestions (NN):
* Consider hardening SSH configuration [SSH-7408]
* Configure minimum password age in /etc/login.defs [AUTH-9286]
* Install a PAM module for password strength testing [AUTH-9262]
* Install package apt-show-versions for patch management purposes [PKGS-7394]
Do not chase the number yet. Fresh servers usually land in the middle of the scale. A mid-October Reddit thread on r/cybersecurity compared default scores across many Linux distributions using Lynis 3.1.7, and the spread between the best and worst default installs was wide. The thread’s author flagged it as a basic comparison, not a measure of overall security, and that caveat deserves repeating.
Step 4: Understand Warnings, Suggestions and the Hardening Index
Lynis separates its findings into two buckets, and the difference decides how you spend your time. A warning describes something Lynis considers wrong right now, such as an unpatched package. A suggestion is a recommendation that may or may not suit your workload. You read every warning. You triage every suggestion.
The hardening index is the most misread figure in the output. It is not a percentage of security. Each test awards earned points out of possible points, and the index divides the two. A server without a web server earns no points and no maximum for the web server tests, so a machine that does run one can score lower with the same level of care. The honest way to use the index is to compare it against your own earlier runs on the same host with the same skipped tests. Comparing it against someone else’s box tells you very little.
| Output element | Meaning | Your action |
|---|---|---|
| Warning | A state Lynis judges wrong now | Read each one, fix or document it |
| Suggestion | A hardening idea that depends on workload | Triage: adopt, adapt or skip with a reason |
| Hardening index | Earned points divided by possible points | Track against your own history only |
| Tests performed | Count of checks that actually ran | Watch it, a sudden drop means skipped tests or missing root |
| Test ID such as SSH-7408 | Unique key for one check | Use it with lynis show details and in custom.prf |
Every finding carries a test ID. Ask Lynis to explain one with sudo lynis show details SSH-7408. The output gives a description and points you at the standard or documentation behind the check. Get in the habit of looking up the ID before you change anything.
Step 5: Read the Report File, Not the Screen
The terminal output is the weakest of Lynis’s three information sources. Ten identical lines reading “Consider hardening SSH configuration” tell you nothing about which options it means. The details sit in the machine-readable report, and the full reasoning sits in the log.
/var/log/lynis-report.datholds the results as key/value pairs, ideal for grep and scripts./var/log/lynis.logholds the full protocol, including why each test passed or failed.- The screen output is for skimming only.
sudo grep '^warning\[\]' /var/log/lynis-report.dat
sudo grep '^suggestion\[\]=SSH-7408' /var/log/lynis-report.dat
sudo grep '^hardening_index' /var/log/lynis-report.dat
sudo grep 'different value than expected' /var/log/lynis.log | sed 's/.*sysctl key //'
Each suggestion line is pipe-separated. The third field carries the concrete instruction, for example an SSH option name with the current and recommended value. The last grep pulls out every kernel parameter whose live value differs from what Lynis expects, one per line, which turns a vague complaint into a ready-made checklist. Illustrative lines look like this:
suggestion[]=SSH-7408|Consider hardening SSH configuration|X11Forwarding (set YES to NO)|-|
suggestion[]=SSH-7408|Consider hardening SSH configuration|MaxAuthTries (set 6 to 3)|-|
kernel.kptr_restrict has a different value than expected in scan profile. Expected=2, Real=0
net.ipv4.conf.all.rp_filter has a different value than expected in scan profile. Expected=1, Real=0
Copy the list into a text file and sort it into three piles: fix now, fix with care, and skip with a reason. The next five steps work through those piles in order of risk.
Step 6: Patch Packages and Harden SSH Safely
The most boring warning is usually the most valuable one. PKGS-7392 means Lynis found installed packages with pending security updates. Lynis is not the problem when this keeps returning. Missing automation is. Install the updates, then turn on unattended security upgrades so the warning stops coming back.
sudo apt list --upgradable
sudo apt-get -y upgrade
sudo apt-get install -y unattended-upgrades apt-listchanges
sudo dpkg-reconfigure -plow unattended-upgrades
For SSH, do not edit the main sshd_config. Current Debian and Ubuntu releases load every file in /etc/ssh/sshd_config.d/, so a drop-in survives package updates untouched. Create one file with the options from your SSH-7408 list. Leave PermitRootLogin and PasswordAuthentication for later, because you should only flip them after a working key login exists. Our Ed25519 key setup guide covers that prerequisite.
sudo tee /etc/ssh/sshd_config.d/99-hardening.conf >/dev/null <<'EOF'
LogLevel VERBOSE
MaxAuthTries 3
MaxSessions 2
TCPKeepAlive no
X11Forwarding no
AllowAgentForwarding no
EOF
sudo sshd -t && sudo systemctl reload ssh
sudo sshd -T | grep -E '^(loglevel|maxauthtries|maxsessions|x11forwarding)'
loglevel VERBOSE
maxauthtries 3
maxsessions 2
x11forwarding no
Always run sshd -t before the reload. It validates the configuration without touching the service. LogLevel VERBOSE adds the fingerprint of the key used for each login, which is the detail you want during an incident. On RHEL-family systems the service is called sshd, so swap the reload command accordingly. If your SSH-7408 list also includes a non-standard port, treat that as optional noise reduction and not as security, since scanners find any open port quickly.
Step 7: Tune Kernel Parameters Without Breaking Containers
This is where blind obedience to Lynis causes outages. The kernel section flags sysctl keys whose values differ from its scan profile. Most are safe to adopt. A few are traps on a machine that runs containers, and one is simply stricter than Lynis expects. Sort them before you apply anything.
| Sysctl key | Lynis wants | Safe to adopt? | Why |
|---|---|---|---|
| kernel.kptr_restrict | 2 | Yes | Hides kernel pointers from unprivileged users |
| kernel.sysrq | 0 | Yes on servers | Disables the magic SysRq key |
| kernel.yama.ptrace_scope | 1 or higher | Yes, check debuggers | Restricts ptrace, which can annoy developers on dev boxes |
| net.core.bpf_jit_harden | 2 | Yes | Hardens the BPF JIT compiler |
| net.ipv4.conf.all.rp_filter | 1 | Test first | Can drop legitimate asymmetric traffic |
| net.ipv4.conf.all.forwarding | 0 | No on Docker hosts | Docker needs forwarding to route container traffic |
| kernel.modules_disabled | 1 | No on container hosts | Blocks loading modules such as overlay or br_netfilter later |
| kernel.unprivileged_bpf_disabled | 1 | Already stricter at 2 | Lynis checks for exact equality and flags an improvement |
Write the adopted values into a dedicated file so you can find and revert them. Reload with sysctl -p and then prove that networking still works.
sudo tee /etc/sysctl.d/99-lynis.conf >/dev/null <<'EOF'
kernel.kptr_restrict = 2
kernel.sysrq = 0
kernel.yama.ptrace_scope = 1
net.core.bpf_jit_harden = 2
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0
EOF
sudo sysctl -p /etc/sysctl.d/99-lynis.conf
If the host runs Docker, test a container right after reloading: docker run --rm alpine sh -c "wget -qO- -T5 https://example.com >/dev/null && echo NET-OK". Seeing NET-OK means containers still reach the network. A failure almost always traces back to the forwarding key. The same caution applies when you harden clusters, and our Kubernetes security hardening walkthrough explains why node-level sysctls interact with pod networking.
Step 8: Fix Authentication, Umask, Banners and Unused Modules
The next batch of findings lives in account policy and file defaults. These changes are low risk, but one of them has a side effect worth knowing about before you start.
sudo sed -i 's/^PASS_MAX_DAYS.*/PASS_MAX_DAYS\t365/; s/^PASS_MIN_DAYS.*/PASS_MIN_DAYS\t1/' /etc/login.defs
grep -q '^UMASK' /etc/login.defs || printf 'UMASK\t\t027\n' | sudo tee -a /etc/login.defs
grep '^ENCRYPT_METHOD' /etc/login.defs
Setting UMASK 027 keeps new files from being world-readable, which is sensible. It also means files you create after your next login get mode 0640 instead of 0644. A service that runs under its own dynamic user can then fail with “Permission denied” on a config file you created. You will meet this again in the troubleshooting section.
Look at the last command’s output before obeying AUTH-9229 and AUTH-9230. Those tests ask for SHA_CRYPT_MIN_ROUNDS values. If your distribution hashes passwords with yescrypt, as current Debian does, those settings do nothing. Adding them would silence the message without improving anything, which is a fake improvement. Skip them and document why.
sudo tee /etc/modprobe.d/99-lynis-blacklist.conf >/dev/null <<'EOF'
install usb-storage /bin/true
install firewire-core /bin/true
install dccp /bin/true
install sctp /bin/true
install rds /bin/true
install tipc /bin/true
EOF
modprobe -n -v sctp
The install ... /bin/true form is stronger than blacklist, because a blacklist only stops automatic loading while a manual modprobe still works. The dry run should print install /bin/true. Skip this block on a desktop or laptop that needs USB storage.
The banner tests, BANN-7126 and BANN-7130, search /etc/issue for legal-sounding words from a hard-coded English list. A banner written in another language fails regardless of clarity. Write a short English notice, copy it to /etc/issue.net, and add Banner /etc/issue.net to your SSH drop-in so remote logins see it too. Validate with sshd -t and reload as before.
Step 9: Add Package Integrity Checks and an auditd Ruleset
Several suggestions point at tools that should exist on any server you care about. Package integrity checking, accounting and the Linux audit daemon are the usual set. Install them in one go on Debian or Ubuntu.
sudo apt-get install -y debsums apt-show-versions acct auditd
sudo sed -i 's/^CRON_CHECK=.*/CRON_CHECK=weekly/' /etc/default/debsums
sudo systemctl enable --now auditd
debsums compares the checksums of installed files with the ones in their packages, so you notice if someone swapped out /usr/bin/ssh. Lynis also wants that check to run on a schedule, which the weekly setting provides. Now the surprise: a freshly installed auditd has no rules, and the next Lynis run answers with a new finding, ACCT-9630, about an audit daemon running with an empty ruleset. That is correct behavior. A daemon with no rules burns resources and records nothing.
sudo tee /etc/audit/rules.d/99-baseline.rules >/dev/null <<'EOF'
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/sudoers -p wa -k privileged
-w /etc/sudoers.d/ -p wa -k privileged
-w /etc/ssh/sshd_config -p wa -k sshd
-w /etc/ssh/sshd_config.d/ -p wa -k sshd
-w /var/log/lastlog -p wa -k logins
EOF
sudo augenrules --load
sudo auditctl -l
The flag -p wa logs writes and attribute changes. The -k option attaches a keyword you can search later, for example sudo ausearch -k sshd. If the host runs Docker, add -w /usr/bin/docker -p x -k docker, because access to the Docker socket is effectively root. Do not install auditd at all if nobody will ever read the log. In that case ACCT-9630 stays absent and you save the overhead.
Step 10: Re-Run the Audit and Measure the Change
Run the audit a second time and compare against the first run. This is the loop you will repeat every quarter: change a group of settings, measure, keep what helped.
sudo lynis audit system --quick --no-colors | sudo tee /root/lynis-lab/run2.txt
grep -E 'Hardening index|Tests performed' /root/lynis-lab/run1.txt /root/lynis-lab/run2.txt
diff <(grep -E '^ *[*!] ' /root/lynis-lab/run1.txt | sort) <(grep -E '^ *[*!] ' /root/lynis-lab/run2.txt | sort)
The grep line puts both indexes side by side. The diff shows which findings disappeared and which appeared. New findings after a change are normal, since adding auditd created ACCT-9630 and a freshly enabled service can reveal checks that previously had nothing to test. When Serverküche ran its own sequence on a Docker-hosting Debian 13 server, the warnings section vanished entirely and the suggestion list roughly halved. Your result depends on your starting point, so do not expect identical movement.
Step 11: Record Deliberate Exceptions in custom.prf
Some suggestions you will decide against on every run. A single-disk VPS does not need separate partitions for /home and /var. A host reachable only through a provider console may not need a GRUB password. Re-reading and re-rejecting these suggestions each month teaches you to ignore the report. The profile file /etc/lynis/custom.prf is the correct place to silence them, and every line deserves a reason.
sudo tee /etc/lynis/custom.prf >/dev/null <<'EOF'
# FILE-6310: separate partitions. Single-disk VPS, repartitioning is riskier than the gain.
skip-test=FILE-6310
# AUTH-9229, AUTH-9230: SHA rounds. Passwords use yescrypt, so these values have no effect.
skip-test=AUTH-9229
skip-test=AUTH-9230
# BOOT-5122: GRUB password. Console access already implies access to the provider panel.
skip-test=BOOT-5122
EOF
Here is a behavior that surprises many readers: skipping tests does not raise the score. A skipped test removes both its earned and its possible points from the calculation, so the index stays put while the suggestion list shrinks. That makes the profile file a noise filter, not a score booster, and it is why you can trust the index not to be gamed. One warning applies. skip-test disables a check permanently, even when the situation changes. Review the file at every quarterly audit and ask whether each reason still holds.
Leave a few suggestions visible on purpose. Malware scanning (HRDN-7230) and central logging (LOGG-2154) are hard to justify on one small server, yet they become sensible once a second machine joins. If you do decide to add a scanner later, our ClamAV on Linux tutorial covers the daemon setup and its memory cost.
Step 12: Schedule Monthly Audits With a systemd Timer
A one-time audit is a snapshot. The value shows up when you can watch the trend, especially after every new service you deploy. Lynis ships a cron-friendly mode that disables colors and prompts. Wrap it in a service and a timer, and add a small gate script that fails the unit when the index drops below a floor you choose.
sudo tee /usr/local/sbin/lynis-gate.sh >/dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
MIN_INDEX="${MIN_INDEX:-70}"
HIST=/var/log/lynis-history.csv
/usr/sbin/lynis audit system --cronjob --quiet || true
IDX=$(grep '^hardening_index=' /var/log/lynis-report.dat | cut -d= -f2)
WARN=$(grep -c '^warning\[\]' /var/log/lynis-report.dat || true)
SUGG=$(grep -c '^suggestion\[\]' /var/log/lynis-report.dat || true)
[ -f "$HIST" ] || echo "date,index,warnings,suggestions" > "$HIST"
echo "$(date -I),$IDX,$WARN,$SUGG" >> "$HIST"
if [ "$IDX" -lt "$MIN_INDEX" ]; then
echo "Lynis index $IDX is below floor $MIN_INDEX" >&2
exit 1
fi
EOF
sudo chmod 0750 /usr/local/sbin/lynis-gate.sh
sudo tee /etc/systemd/system/lynis-audit.service >/dev/null <<'EOF'
[Unit]
Description=Lynis security audit with score gate
[Service]
Type=oneshot
Environment=MIN_INDEX=70
ExecStart=/usr/local/sbin/lynis-gate.sh
EOF
sudo tee /etc/systemd/system/lynis-audit.timer >/dev/null <<'EOF'
[Unit]
Description=Monthly Lynis audit
[Timer]
OnCalendar=monthly
RandomizedDelaySec=1h
Persistent=true
[Install]
WantedBy=timers.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now lynis-audit.timer
systemctl list-timers lynis-audit.timer
Set MIN_INDEX a few points under your current score, not at some aspirational number. A failing unit then means something changed for the worse, and systemctl --failed or your monitoring will show it. An Ansible role listing on IaC Bazaar uses the same design: it runs Lynis from a systemd timer and fails the audit unit below a minimum hardening index, and it warns that scores only compare across runs with the same skipped tests. Persistent=true catches up a missed run if the machine was off at the scheduled time.
Step 13: Audit a Small Fleet Over SSH
Once one host works, extend the loop to several. Lynis does not need an agent, so a plain SSH loop is enough for a home lab or a handful of servers. The script below copies nothing permanently. It runs the audit remotely, pulls back the key numbers and prints a table. It assumes key-based SSH and passwordless sudo for the audit user.
#!/usr/bin/env bash
# fleet-lynis.sh - usage: ./fleet-lynis.sh host1 host2 host3
set -u
printf '%-24s %-8s %-9s %-11s\n' HOST INDEX WARNINGS SUGGESTIONS
for h in "$@"; do
out=$(ssh -o BatchMode=yes -o ConnectTimeout=8 "$h" \
'sudo lynis audit system --cronjob --quiet >/dev/null 2>&1;
sudo awk -F= "/^hardening_index=/{i=\$2} /^warning\\[\\]/{w++} /^suggestion\\[\\]/{s++} END{print i, w+0, s+0}" /var/log/lynis-report.dat' 2>/dev/null) || out="- - -"
set -- $out
printf '%-24s %-8s %-9s %-11s\n' "$h" "${1:--}" "${2:--}" "${3:--}"
done
The loop expands the host list once before it starts, so reusing $1 to $3 for the parsed numbers is safe. Hosts that refuse the connection print dashes instead of stopping the run. For anything beyond a few machines, the commercial Lynis Enterprise dashboard exists for centralized reporting, though its current plans and prices could not be confirmed from official pages this week. Plan on a log shipper and a dashboard of your own if you stay on the free tool.
Complete Working Project: A One-File Lynis Baseline Script
The steps above are easier to repeat when they live in one file. The script below installs Lynis from the vendor repository if it is missing, runs an audit, saves a dated copy of the report, and prints a summary that groups suggestions by test category. It changes nothing on the system apart from installing Lynis and writing to /var/lib/lynis-baseline. Review it, then run it as root. The install block targets Debian and Ubuntu and is skipped when lynis already exists, so on other distributions install Lynis first.
#!/usr/bin/env bash
# lynis-baseline.sh - audit, archive, summarize. Run as root.
set -euo pipefail
OUT=/var/lib/lynis-baseline
STAMP=$(date +%Y%m%d-%H%M%S)
mkdir -p "$OUT"
chmod 0700 "$OUT"
[ "$(id -u)" -eq 0 ] || { echo "Run as root." >&2; exit 1; }
if ! command -v lynis >/dev/null 2>&1; then
echo "[*] Installing Lynis from the CISOfy repository"
install -d -m 0755 /etc/apt/keyrings
curl -fsSL https://packages.cisofy.com/keys/cisofy-software-public.key \
-o /etc/apt/keyrings/cisofy.asc
head -1 /etc/apt/keyrings/cisofy.asc | grep -q 'BEGIN PGP' \
|| { echo "Key download failed" >&2; exit 1; }
echo "deb [signed-by=/etc/apt/keyrings/cisofy.asc] https://packages.cisofy.com/community/lynis/deb/ stable main" \
> /etc/apt/sources.list.d/cisofy-lynis.list
apt-get update -qq
apt-get install -y -qq lynis
fi
echo "[*] Lynis version: $(lynis --version)"
echo "[*] Running audit (1-2 minutes)"
lynis audit system --cronjob --quiet || true
cp /var/log/lynis-report.dat "$OUT/report-$STAMP.dat"
cp /var/log/lynis.log "$OUT/log-$STAMP.log"
IDX=$(grep '^hardening_index=' /var/log/lynis-report.dat | cut -d= -f2)
TESTS=$(grep '^tests_executed=' /var/log/lynis-report.dat | cut -d= -f2 | tr '|' '\n' | grep -c . || true)
WARN=$(grep -c '^warning\[\]' /var/log/lynis-report.dat || true)
SUGG=$(grep -c '^suggestion\[\]' /var/log/lynis-report.dat || true)
echo
echo "=== Summary for $(hostname) at $STAMP ==="
echo "Hardening index : $IDX"
echo "Tests executed : $TESTS"
echo "Warnings : $WARN"
echo "Suggestions : $SUGG"
echo
echo "=== Warnings ==="
grep '^warning\[\]' /var/log/lynis-report.dat | cut -d'|' -f1,2 | sed 's/^warning\[\]=//' || true
echo
echo "=== Suggestions by category ==="
grep '^suggestion\[\]' /var/log/lynis-report.dat \
| sed 's/^suggestion\[\]=//' | cut -d'|' -f1 \
| cut -d- -f1 | sort | uniq -c | sort -rn
echo
echo "=== Change since previous run ==="
PREV=$(ls -1 "$OUT"/report-*.dat 2>/dev/null | sort | tail -n 2 | head -n 1)
CUR="$OUT/report-$STAMP.dat"
if [ -n "$PREV" ] && [ "$PREV" != "$CUR" ]; then
diff <(grep '^suggestion\[\]' "$PREV" | cut -d'|' -f1 | sort -u) \
<(grep '^suggestion\[\]' "$CUR" | cut -d'|' -f1 | sort -u) || true
else
echo "No earlier run found, this is your baseline."
fi
echo
echo "Reports saved in $OUT"
Here is the illustrative shape of the output on a first run. Numbers are placeholders, and your own counts will differ:
[*] Lynis version: 3.1.7
[*] Running audit (1-2 minutes)
=== Summary for web01 at 20261027-091500 ===
Hardening index : NN
Tests executed : NNN
Warnings : N
Suggestions : NN
=== Suggestions by category ===
N SSH
N KRNL
N AUTH
N PKGS
N FILE
N ACCT
=== Change since previous run ===
No earlier run found, this is your baseline.
Save the file as lynis-baseline.sh, make it executable with chmod +x, and run it with sudo ./lynis-baseline.sh. Run it again after each hardening group and the last section shows which test IDs went away or appeared. Copy the archive folder off the machine now and then, because the saved reports prove your audit history to anyone who asks.
Common Pitfalls That Waste an Evening
Most Lynis trouble comes from people, not from the tool. These six mistakes appear again and again in forum threads and in the October tutorials this article draws on.
- Chasing 100. The index is a point ratio and depends on which services run. A perfect score is neither achievable nor useful on most hosts. Aim for a steady rise against your own history.
- Comparing scores between machines or versions. A host with a web server and one without face different possible points, and an older Lynis release runs an older test list. Compare like with like.
- Applying every sysctl line. IP forwarding and module-loading lockdowns break Docker hosts. Read the table in Step 7 before you reload.
- Silencing tests to look good. Skipping a test does not move the index, so skipping only makes sense when the check is wrong for your workload. Write the reason next to every
skip-testline. - Adding settings that satisfy a test but change nothing. The SHA rounds example on a yescrypt system is the classic case. Check that a setting applies to your system before you add it.
- Running as a normal user. Without root, Lynis skips many tests and the index becomes meaningless. Watch the tests-performed number as a sanity check.
A seventh habit helps more than it looks. Run Lynis after every significant change, such as a new reverse proxy, a new database or a newly opened port. Those are exactly the moments that create gaps a monthly timer only finds weeks later.
Troubleshooting Lynis: 9 Problems and Fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Containers lose network access after sysctl reload | net.ipv4.conf.all.forwarding set to 0 | Remove the line from your sysctl file and run sysctl -w net.ipv4.conf.all.forwarding=1, restarting Docker alone does not help |
| A service fails with “Permission denied” on its own config file | UMASK 027 made your new file mode 0640 and the service runs as another user | Set the mode explicitly, for example chmod 0644 on the file |
| Time-sync warning appears even though the clock is correct | Lynis checks the age of the sync marker file and warns beyond 2048 seconds, which equals systemd’s default maximum poll interval | Lower PollIntervalMaxSec in a timesyncd drop-in, or add the test to custom.prf |
| Ten identical SSH suggestions with no detail | Screen output hides the options | Grep the report for ^suggestion\[\]=SSH-7408 and read the third field |
| New finding ACCT-9630 after installing auditd | Audit daemon running with an empty ruleset | Add rules under /etc/audit/rules.d/ and run augenrules --load, or remove auditd |
| Lynis offers an old version after adding the repository | Typo in the source filename or no apt-get update | The file must end in .list and sit in /etc/apt/sources.list.d/, then check apt-cache policy lynis |
| Package audit test skipped on an Arch-based system | Reportedly, 3.1.7 did not detect arch-audit on CachyOS, as described in an October issue | Check the GitHub issue for a workaround, and do not read the skipped test as a pass |
| Kernel test KRNL-5830 flags a Raspberry Pi system | A reported false positive, because two kernel flavors exist per version | Confirm manually, then skip the test in custom.prf with a comment |
| Index changes although you changed nothing | A new Lynis release added tests, or tests ran without root | Compare tests-performed counts and the Lynis version between runs |
A tenth issue deserves mention even though it is not a bug: after a major Lynis update, new findings sometimes appear. Those are usually new tests and not new problems on your server. Read the changelog entry before you assume something broke.
Advanced Tips for Fleets, Pipelines and Compliance
Feed the report into your logging stack
The key/value report is easy to ship. A small script that converts lynis-report.dat into JSON lines lets a log forwarder send each warning as an event. If you already run a SIEM, tag the events with the host name and test ID so you can chart which checks fail most often across the fleet. Without a SIEM, the history CSV from Step 12 plus a spreadsheet chart already shows the trend.
Use scan profiles for different server roles
Lynis reads profile files from /etc/lynis, and you can keep one per role. A database server profile can skip web checks, and a bastion profile can keep everything. Pass the file with --profile so each role has its own history and its own meaningful index. Run lynis show profiles to see what is active.
Pair Lynis with the controls it does not test
Lynis audits one host from the inside. Pair it with perimeter work, such as the checklist in our router security settings guide, and with the broader material in the cybersecurity section. For compliance frameworks, Lynis can support HIPAA, ISO 27001 and PCI DSS style checks according to its GitHub description, but a passing run is evidence for an auditor, not a certification.
Review AI-generated hardening commands by hand
Copilot-style assistants will happily turn a Lynis report into shell commands. No retrieved source confirms that any assistant validates Lynis advice against your workload. Treat generated commands as drafts, compare them with the test’s own explanation from lynis show details, and apply them on a snapshot first.
Frequently Asked Questions About Lynis
Is Lynis free?
Yes. The Lynis community tool is open source and costs nothing to run. CISOfy also sells Lynis Enterprise as a commercial offering with centralized dashboards and compliance reporting, but this week’s official pages did not confirm current plan names or prices. A software directory lists a starting price, and that is third-party information you should verify with the vendor.
What is the current Lynis version?
According to the project’s GitHub release page, 3.1.7 is the latest numbered release, and no newer version had been confirmed as of October 27, 2026. The repository shows commits since that release, but commits alone do not make a new version. Check lynis --version and the release page before you trust any tutorial that shows a different number.
What is a good Lynis hardening index?
There is no universal good number. The index depends on which services run and which tests are skipped. Some guides call anything under 70 to 80 a sign of many open improvements, but treat that as a rule of thumb. Track your own baseline and aim for steady improvement with zero unexplained warnings.
Does Lynis fix problems automatically?
No. Lynis only reports. You apply the changes yourself, which is a feature, because many suggestions are wrong for some workloads. The Docker examples in Step 7 show how a blindly applied suggestion can cut every container off the network.
Is Lynis a vulnerability scanner like Nessus or OpenVAS?
Not quite. Network scanners probe from outside, while Lynis runs locally as root and inspects configuration. It does flag packages with pending security updates, but it will not find a vulnerable web application. Use both kinds of tools when the stakes are high.
Can I run Lynis without installing it?
Yes. The project describes itself as agentless with optional installation. Clone the repository, change into the folder and run ./lynis audit system. This suits one-off checks of machines where you cannot add packages.
How often should I run it?
Monthly for stable servers, plus a manual run after any significant change. The timer in Step 12 handles the first part. Budget roughly 20 minutes per quarter to skim the report, triage new findings and re-read your custom.prf reasons.





