Linux Fleet Harden lfh.kosir.info
──═╡ A highly available absolution ╞═──

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.

operator@controller — linux-harden --apply
$ 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: 
Two rites, one liturgy

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.

Rite the first · the examination

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 →
Rite the second · the absolution

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.

The three pillars of safe hardening

Confession. The Guard. Resurrection.

Remove one and the quorum is lost.

I.

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.

II.

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.

III.

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 order of service

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.

The crown jewel

Thou shalt not lock thyself out

Changing sshd over ssh is how afternoons die. Three seals guard every change to the daemon.

The first seal

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 second seal

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 third seal

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.

What may be absolved

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.

ActionWhat it doesApplied?
sshd_configSet the directive, sshd -t, reload under the Guard.yes
sysctlSet at runtime and persist in /etc/sysctl.d/.yes
login_defsSet the key in /etc/login.defs (new accounts).yes
file_modechmod / chown the file to the desired posture.yes
package_* · service_enable · rebootPatching, firewalls, reboots — the lock-you-out class.a human
manual — e.g. a second UID 0A 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.

Come as you are; you will be reconciled

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
The lesser blessings

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.