Harden the fleet.
Confess every change.
The audit examined the conscience of your servers. This grants absolution — one approved change at a time.
Linux Fleet Harden reads the remediation plan from linux-audit, re-verifies every finding on the living host, and applies only the fixes you approve. It backs up before it touches anything, and it will not let you lock yourself out. Agentless. Dry-run by default.
$ python3 linux_harden.py plan.json --apply linux-harden 1.0.0 [build 2026-07-17.initial] Verifying current state on 2 host(s)... ok web01.example.com ok app02.example.com [2/5] 5.2.4 SSH: MaxAuthTries <= 4 key: ssh.max_auth_tries expected: 4 web01.example.com observed: 6 app02.example.com observed: 8 Apply to 2 host(s)? [y]es / [N]o / [s]elect / [q]uit:
The examination, then the absolution
Hardening a fleet is two acts. One finds what is wrong. The other makes it right — carefully, and never alone.
linux-audit
Reads eighty servers so you don't have to. It finds the sins: root login left open, a firewall asleep, a second soul wearing UID 0, patches unprayed. It writes them to a machine-readable plan and judges nothing it cannot read.
github.com/vikozs/linux-audit →linux-harden
Reads that plan and makes it right — but nothing is absolved without confession. It re-checks each finding on the host, asks your blessing per change, applies it, and reads it back to be sure. Then it writes down exactly what it did.
github.com/vikozs/linux-harden →The plan is the sacrament passed between them: linux-audit.remediation-plan, schema 1.0.
Confession. The Guard. Resurrection.
Remove one and the quorum is lost.
Confession
Nothing changes unattended. Every check is approved by a human, grouped per finding across the fleet with per-host opt-out. The disruptive rites — PermitRootLogin, PasswordAuthentication — make you type APPLY with your own hands. There is deliberately no unattended mode, and it refuses to run without a terminal.
The Guard
It edits sshd, tests the config, reloads (never restarts), then opens a fresh session to be certain you can still get in. If the door won't open, a watchdog on the host restores the old config by itself. No operator is cast out from their own session.
Resurrection
Every file is pulled to your machine before it is touched, with prior modes and runtime values recorded in a manifest. --rollback raises the previous state from the dead, newest run first. No change is final; every fallen config shall be restored.
The Rite of Hardening
Five movements, from the plan to the proof. Nothing is skipped, and the reader gets the bad news first.
Receive the plan
Load the plan and check its schema. A plan older than seven days is refused without --force, for the fleet is not what it was when the audit saw it.
Behold before you act
Dry-run by default. It prints exactly what it would offer — executable, manual, and the action types it will not touch — and then it changes nothing. Nothing happens without --apply.
Re-examine the living host
The plan is a snapshot; the host may have repented, or drifted elsewhere. A host already compliant is passed over. A value that cannot be read is left untouched — unknown means not compliant, never a guess.
Confess, per check, across the fleet
One decision covers every host with that finding. Opt out per host with [s]elect. Drifted hosts are confirmed individually even after a fleet-wide yes. Disruptive items require typing APPLY.
Absolve, and verify
applied means the host now reads back the desired state — not merely that a command exited zero. The proof is written twice: prose in an Excel report, structure in harden_results.json. Then re-audit for an independent witness.
Thou shalt not lock thyself out
Changing sshd over ssh is how afternoons die. Three seals guard every change to the daemon.
sshd -t
The config is validated before it is ever loaded. A file that will not parse is rejected and the original is restored, untouched. A syntax error never reaches a running daemon.
The watchdog
Before reloading, a sentinel is armed on the host. Reload — never restart — so living sessions are spared. If the sentinel is not disarmed within ninety seconds, it restores the old config and reloads again, alone, needing no hand and no connection.
The fresh session
The controller opens a brand-new SSH session. Only if it is admitted is the sentinel dismissed. The session that made the change cannot vouch for itself — it was already inside.
And should a drop-in in sshd_config.d overrule thy directive, the tool reports it plainly rather than fighting it. First obtained value wins — in sshd, as in bureaucracy.
Four sacraments in version one
The low-risk, well-defined fixes are applied. Everything else is named and left to a human — as it should be.
| Action | What it does | Applied? |
|---|---|---|
sshd_config | Set the directive, sshd -t, reload under the Guard. | yes |
sysctl | Set at runtime and persist in /etc/sysctl.d/. | yes |
login_defs | Set the key in /etc/login.defs (new accounts). | yes |
file_mode | chmod / chown the file to the desired posture. | yes |
package_* · service_enable · reboot | Patching, firewalls, reboots — the lock-you-out class. | a human |
manual — e.g. a second UID 0 | A backdoor or a break-glass account. Both need a person. | a human |
The tool never guesses at an action type it does not recognise. Unknown is reported, not improvised.
Install & first rite
Python 3.9+, openpyxl, the system ssh client. The same access the audit already needed. Nothing is installed on the targets.
# clone the canon
git clone https://github.com/vikozs/linux-harden.git
cd linux-harden
python3 -m pip install -r requirements.txt
# 1. the audit finds the sins python3 linux_audit.py -H hosts.txt --plan-out plan.json # 2. behold what would change (changes nothing) python3 linux_harden.py plan.json # 3. confess and absolve, interactively python3 linux_harden.py plan.json --apply # and to repent of a run: python3 linux_harden.py --rollback
Small mercies, all included
Dry-run by default
The safe path is the default path. You have to ask, twice, to change anything.
Per-check approval
One decision per finding across the fleet, with per-host opt-out. Scales past eighty servers.
Typed confirmation
Disruptive changes make you type APPLY. No one locks a fleet out on a stray keystroke.
Local backups
Every file pulled to your machine before it changes, with a manifest. On your disk, not a promise.
Rollback
--rollback restores files, modes, and sysctl values. sshd changes get the Guard again on the way back.
Two witnesses
Prose for humans in Excel, structure for machines in results.json. Neither parsed from the other.
Injection-safe reports
Values from audited hosts are validated before any shell, and every cell is written formula-safe.
Agentless
Pure SSH and sudo. Reuses your keys, ~/.ssh/config, ProxyJump. Nothing left behind.
Drift-aware
Hosts that changed since the audit are flagged and confirmed one by one, never swept along.
What this actually is
Breaking the fourth wall, as is the custom. Linux Fleet Harden is a real tool by vK. It really does harden Linux fleets over SSH, it really does back everything up first, and it really will refuse to lock you out. The liturgy is affectionate; the sshd guard is not a joke — it has saved at least one person's afternoon.
It applies heuristic, benchmark-style fixes: a hardening baseline, not a certified CIS-CAT anything. Run it only against systems you are authorised to change, in a window you are allowed to use. The tool absolves configuration sins. It does not absolve you of change control.
A sibling of linux-audit. Not affiliated with the CNCF, Red Hat, or the Church of the Eternal Cluster — though we share a rector in spirit, and a fondness for anyone who has been paged at 03:00.