GitPedia

Harden.yml

Ansible playbook for Linux hardening

From pyllyukko·Updated June 29, 2026·View on GitHub·

* Debian (Trixie) * :dragon: Kali * π Raspberry Pi OS * Slackware (>= [15.0](http://www.slackware.com/announce/15.0.php)) The project is written primarily in Jinja, distributed under the MIT License license, first published in 2013. Key topics include: ansible, ansible-playbook, debian, hardening, linux.

harden.yml :lock:

Ansible playbook to harden your Linux system.

lint

asciicast of harden.yml 1294b6f

Supported distros

molecule

  • Debian (Trixie)
    • :dragon: Kali
    • π Raspberry Pi OS
  • Slackware (>= 15.0)

:question: Why I made this

  • Bastille is obsolete
  • Not a member of CIS, so no downloading of the ready made scripts
    • Also to go "beyond CIS" with things like:
      • Reducing CA certs
      • Hardening PAM
      • Making smart audit rules that don't spam your logs with unnecessary and useless information
      • Properly locking down system accounts
      • Other stuff that is not covered by CIS benchmarks
  • For learning
  • For minimizing the effort needed to tweak fresh installations
    • Also for consistency

:question: What does it do?

For a complete list you can run ansible-playbook --list-tasks harden.yml.

Network

  • Enables TCP wrappers
    • :bulb: Some people consider TCP wrappers as obsolete and unnecessary, because nowadays firewall(s) take care of this kind of network level access. I disagree, because TCP wrappers still provide an additional layer of control in a case where the firewall(s) might fail for any number of reasons (usually misconfiguration). TCP wrappers also work as an network level ACL for the programs that utilize it and is a "native" control for those programs.
  • IP stack hardening via sysctl settings
  • Creates a basic firewall
  • Configures NetworkManager as follows:
    • For each existing connection:
      • Disables IPv6 (ipv6.method -> disabled)
    • Configures few defaults to /etc/NetworkManager/conf.d/:
      • Sets dhcp-send-hostname to false
      • Sets wifi.scan-rand-mac-address to true

:wood: Logging

  • :calendar: Configure log retention time to be 6 months
  • Configures logrotate to shred files
    • :information_source: NOTE: Read the fine print in SHRED(1): "CAUTION: shred assumes the file system and hardware overwrite data in place. Although this is common, many platforms operate otherwise."
  • Configure journald to use Forward Secure Sealing (FSS) and enable auditing (see notes on how to create keys for FSS)
  • Enables auditing
  • Run ansible-playbook --list-tasks --tags logging harden.yml for a full list

:bar_chart: Accounting

  • Enables system accounting (sysstat)
    • :calendar: Sets it's log retention to 99999 days (the logs are really small, so it doesn't eat up disk space)
  • Enables process accounting
  • Run ansible-playbook --list-tasks --tags accounting harden.yml for a full list

:peanuts: Kernel

  • :no_entry: Disables the use of certain kernel modules via modprobe (see files/modprobe.d/)
    • Disable Firewire
    • :warning: WARNING: Also disables usb-storage, which will disable support for USB mass medias
  • sysctl settings hardening
  • Run ansible-playbook --list-tasks --tags kernel harden.yml for a full list

:file_folder: Filesystem

  • Hardens mount options (creates /etc/fstab.new) (see fstab.awk)
  • :house: Sets strict permissions to users home directories
  • :no_entry: Limits permissions to various configuration files and directories that might contain sensitive content (see permissions tag for a complete list)
  • :do_not_litter: Clean up /tmp during boot (see tmp.conf.new)
  • Removes SUID and/or SGID bits from various binaries (see ansible-playbook --list-tasks --tags suid,sgid harden.yml for details)

Application specific

  • Configures basic auditing based on stig.rules if audit is installed (see audit.yml)
  • :blowfish: Configures sshd_config and ssh_config (see ansible-playbook --list-tasks --tags ssh harden.yml for details)
    • Removes 2048-bit moduli from /etc/ssh/moduli
    • :information_source: Removes SUID bit from ssh-keysign, so host-based authentication will stop working. Host-based authentication shouldn't be used anyway.
    • :information_source: Password authentication is not disabled so you wouldn't lock yourself out of your system accidentally
    • See ssh_config.j2 and sshd_config.j2
  • :sandwich: Configures sudo (see sudoers.j2)
    • :warning: WARNING: If there are rules in /etc/sudoers.d/ that match our become: true tasks that do not have explicit EXEC, it can "break" sudo as we define Defaults noexec in the main sudoers file. There is a "Fix generic rules" task in sudoers.yml which tries to tackle this problem, but it's not guaranteed to work.
    • :warning: WARNING: Sudo binary /usr/bin/sudo is chmodded so, that only sudo_group (see vars.yml) can run it
    • :wood: You can set the sudo_iolog in vars.yml to true to enable I/O logging
    • You can set the sudo_ids in vars.yml to true to enable "Intrusion Detection" as described in Sudo Mastery chapter 9 (#59)
    • See also notes
  • :smiling_imp: ClamAV configuration (see clamav.yml)
  • rkhunter configuration (see rkhunter.yml)
  • :tiger: Tiger: Configures tigerrc & tiger.ignore
  • Lynis configuration (see lynis.yml)
  • Configures AIDE (see aide.yml)
  • Display managers:
    • Disables user lists in GDM3 & LightDM
    • :no_entry: Disables guest sessions and VNC in LightDM
  • :feather: Minor Apache HTTP server hardening
  • Minor PHP (php.ini) hardening

User accounts / authentication / authorization

  • Sets default umask to a more stricter 077 (see https://github.com/pyllyukko/harden.yml/wiki/umask)
  • :timer_clock: Sets console session timeout via $TMOUT (Bash)
  • 🎟️ Properly locks down system accounts (0 - SYS_UID_MAX && !root)
    • :no_entry: Lock the user's password
    • :shell: Sets shell to /sbin/nologin
    • Set maxlogins to 0 in limits.conf (enforced by pam_limits)
      • :information_source: You can see it in the logs as: pam_open_session: Permission denied
    • Expire the account
    • :no_entry: Set RLIMIT_NPROC and RLIMIT_NOFILE to 0 in pam_limits for those system accounts that don't need to run any processes
      • :information_source: You can see this in action in the PAM limits test
      • :information_source: See unnecessary_system_accounts in vars.yml for the list of accounts
      • :information_source: RLIMIT_NOFILE will cause pam_open_session: Too many open files errors and RLIMIT_NPROC will cause fork: Resource temporarily unavailable errors
  • :busts_in_silhouette: Makes minor modifications to existing accounts. See ansible-playbook --list-tasks --tags accounts harden.yml for details.

Password policy

  • 🎟️ Configures the default password inactivity period

  • Configures the password aging values to /etc/login.defs

    • PASS_MAX_DAYS
    • PASS_MIN_DAYS
    • PASS_WARN_AGE
  • These settings are also applied to existing user accounts

  • Two password quality backends are supported. The playbook auto-detects which is installed and prefers passwdqc over libpwquality (see passwdqc.conf.j2 and pwquality.conf.j2)

  • Creates a CrackLib dictionary to be used with libpwquality (see ansible-playbook --list-tasks --tags cracklib harden.yml and the CrackLib related handlers in handlers.yml)

    • The small dictionary (derived from cracklib-small) that usually comes bundled with CrackLib packages contains 51526 words and the one we generate (from cracklib-words) contains 1911477 words
  • Creates a passwdqc cuckoo filter file based on RockYou

  • Downloads John the Ripper's password.lst to be used with passwdqc's wordlist option

  • Configures /etc/security/pwhistory.conf

    • :information_source: pam_pwhistory needs to be manually enabled with pam-auth-update in Debian
  • Run ansible-playbook --list-tasks --tags passwords harden.yml to list all password related tasks

NIST SP 800-63

You can set the password_policy variable to nist_sp800_63 in vars.yml for NIST Special Publication (SP) 800-63-4 style password policy.

  • :warning: This is an experimental feature and :construction: under construction :construction:
  • See New password policies according to NIST SP 800-63 #88
  • :information_source: If you opt to use this, it is highly recommended to use passwdqc instead of libpwquality, as passwdqc's wordlist & filter features are superior compared to libpwquality's use of CrackLib

The main differences are:

PropertyTraditionalSP 800-63
Password maximum age (days)36599999
Password minimum length1415
Composition/complexity requirements

🎟️ Authorization

  • Create a strict securetty
  • Creates /etc/ftpusers
  • Restricts the use of cron and at
  • Disallow non-admin users from authenticating as other users in polkit
  • Run ansible-playbook --list-tasks --tags authorization for a full list

PAM

  • Configures /etc/security/namespace.conf
  • 🎟️ Configures /etc/security/access.conf for pam_access (authorization) (see access.conf.j2)
  • Configures password quality — /etc/security/pwquality.conf or /etc/passwdqc.conf — depending on which is installed (see Password policy section)
  • 🛞 Require pam_wheel in /etc/pam.d/su
  • :no_entry: Creates a secure /etc/pam.d/other
    • See also A strong /etc/pam.d/other
    • :bulb: Fun fact: As Debian 13 doesn't seem to ship /etc/pam.d/systemd-run0, this will also block the use of run0
  • Configures /etc/security/limits.conf as follows:
    • Disable core dumps
    • Sets maximum amount of processes (or threads, see setrlimit(2))
    • :no_entry: Sets nproc to 0 for system users that don't need to run any processes
  • Run ansible-playbook --list-tasks --tags pam harden.yml to list all PAM related tasks
  • You can also run ansible-playbook --check --diff --tags pam harden.yml to see details of the changes
  • pam

Miscellaneous

  • :placard: Creates legal banners (see banners.yml)
  • Reduce the amount of trusted CAs (see ca-certificates.conf.new)
    • ca-certs
    • You can also run make /etc/ssl/certs/ca-certificates.crt to update the CAs
    • :information_source: Do note, that Slackware will overwrite /etc/ca-certificates.conf when upgrading ca-certificates package. See Slackware's ChangeLog entry for Wed Oct 6 00:02:15 UTC 2021.
    • :bulb: See the CA incidents wiki page for a curated list of historical CA compromises, mis-issuance and other incidents that motivate reducing the default trust store.
  • :shell: Restricts the number of available shells (/etc/shells)
  • :shell: Creates an option to use a restricted shell (rbash)
    • Only available for Debian & Slackware and for the sshd service because of the required PAM configuration changes (regarding pam_env & enforcing PATH)
    • :information_source: See Restricted shell
    • :warning: WARNING: Contains plenty of caveats, details and hazards. Make sure you read and understand (at least) everything in the aforementioned wiki page, test it thoroughly and accept the risk that it may contain escapes.
  • Disable core dumps via /etc/systemd/coredump.conf
  • Kerberos hardening via /etc/krb5.conf

Slackware specific

  • Disables unnecessary services
  • Run ansible-playbook --list-tasks --tags slackware harden.yml for a full list
  • Make Xorg rootless
  • :wood: Makes default log files group adm readable (as in Debian)
  • 🛞 Restricts the use of cron so that only users in the wheel group are able to create cronjobs (as described in /usr/doc/dcron-4.5/README)
  • Mount /proc with hidepid=2
  • :wood: Make installpkg store the MD5 checksums
  • :bar_chart: Enable process accounting (acct)
  • :busts_in_silhouette: Does some housekeeping regarding group memberships (see login_defs-slackware.yml)
  • 🎟️ Configures inittab to use shutdown -a (and /etc/shutdown.allow)
  • Reconfigured bunch of services (run ansible-playbook --list-tasks --tags slackware harden.yml | grep '\bservices\b' for a full list)
  • Configures cgroups (v1, because of too old libcgroup) into /etc/cg{config,rules}.conf
  • Enables bootlogd

PAM

  • Creates a custom /etc/pam.d/system-auth, which has the following changes:
    • :timer_clock: Use pam_faildelay
    • 🎟️ Use pam_faillock
    • 🎟️ Use pam_access
    • :no_entry: Removes nullok from pam_unix
    • Sets crypt rounds for pam_unix
    • Change password minlen from 6 to 14
    • Enables pam_pwhistory
    • See system-auth.j2
  • The following PAM modules are added to /etc/pam.d/postlogin:
    • pam_umask
    • pam_cgroup
    • pam_keyinit
  • Add pam_namespace to /etc/pam.d/{login,sddm,sshd,xdm}
  • Removes auth include postlogin from several files, as postlogin should (and has) only session module types
  • :sandwich: Creates /etc/pam.d/sudo, as that seemed to be missing
  • 🎟️ Disallows the use of su (see su.new)
  • :no_entry: Block /etc/pam.d/remote (see /etc/pam.d/remote)
  • Adds pam_shells to /etc/pam.d/chsh (require a valid shell in order to change your shell)

Debian specific

  • Disables unnecessary systemd services
  • :shield: Enables AppArmor
  • Configure SUITE in debsecan
  • Install debsums and enable weekly cron job
  • Installs a bunch of security related packages (see debian_packages.yml)
  • Configures chkrootkit and enables daily checks
  • Configures APT not to install suggested packages

pam-configs

Creates bunch of pam-configs that are toggleable with pam-auth-update:

PAM moduleTypeDescription
🛞 pam_wheel<sup>1</sup>authRequire wheel group membership (su)
🎟️ pam_succeed_ifauth & accountRequire UID >= 1000 && UID <= 60000 (or 0 & login)
:no_entry: pam_unix<sup>1</sup>authRemove nullok
:timer_clock: pam_faildelayauthDelay on authentication failure
pam_ssh_agent_authauthSSH agent authentication for sudo<sup>3</sup>
🎟️ pam_faillockauth & accountDeter brute-force attacks
🎟️ pam_accessaccountUse login ACL (/etc/security/access.conf)
🎟️ pam_timeaccount/etc/security/time.conf
🎟️ pam_lastlogaccountLock out inactive users (no login in 90 days)
pam_namespacesessionPolyinstantiated temp directories
pam_lastlogsessionDisplay info about last login and update the lastlog and wtmp files<sup>2</sup>
pam_pwhistorypasswordLimit password reuse
  1. <span id="fn1"/>Not a pam-config, but a modification to existing /etc/pam.d/ files
  2. <span id="fn2"/>For all login methods and not just the console login
  3. <span id="fn3"/>Disabled by default and requires libpam-ssh-agent-auth package. Needs to have higher priority than krb5 or other password auths.
    • sshd needs to have AllowAgentForwarding yes
    • You need to configure sudo with Defaults env_keep += "SSH_AUTH_SOCK"

Out of scope

At least the following hardening areas are currently not covered at all:

  • Bootloader password enforcement
  • Secure Boot / UEFI Hardening
  • Network:
  • X11/Wayland hardening
  • Automatic security updates (beyond installing unattended-upgrades in Debian)
  • Wireless interfaces like WiFi and Bluetooth
  • Authentication:
    • Biometric authentication
    • MFA
  • Remote logging
  • Compiler restrictions (e.g. removing/blocking development tools like gcc, make etc.)

Usage

  • Edit the harden.yml and modify hosts or create a completely new playbook by making a copy of the harden.yml file
    • You can comment out the "task sets" that you don't need
  • Check vars.yml in case you want to tweak some of the settings
  • You can check all the tasks before running the playbook by running ansible-playbook --list-tasks harden.yml
  • Harden your system by running ansible-playbook harden.yml
    • You might need to provide credentials with -K or via inventory

:information_source: Notes

  • :busts_in_silhouette: Make sure regular users that should be able to login are members of the allowed_group group
  • :sandwich: Sudo hardening:
    • noexec is on by default, so you need to take this into account in your custom rules
    • :timer_clock: Interactive shells to root have timeout, so use screen for those longer administrative tasks
  • :arrows_counterclockwise: Rebooting the system after running this is highly recommended
  • The AIDE DB creation is made asynchronously and without polling, so let that finish before rebooting
  • :bulb: You might want to get additional (unofficial) rules for ClamAV with clamav-unofficial-sigs (although see #425). At least the following rulesets are freely available:
  • :warning: WARNING: There is a hazard with immutable loginuid enabled in auditing in non-systemd systems (Slackware). See longer description of this in the wiki.
  • :file_folder: Review /etc/fstab.new manually and deploy applicable changes to /etc/fstab
  • :bulb: Consider running a hardened kernel. For Slackware you can check out my other project kspp_confnbuild that has been (mostly) configured according to KSPP's recommendations. You can use kernel-hardening-checker to check your kernel configs.
  • :envelope: Make sure your system is able to send e-mails somehow. Many of the tools will be sending alerts about various anomalies.
  • Customize the firewall to suit your needs
  • :wood: Logging:
    • :eyes: Consider installing and configuring Logwatch
    • :key: Run journalctl --setup-keys to generate a new key pair for Forward Secure Sealing (FSS)
    • Configure remote logging
  • Consider purchasing Openwall passwdqc filter files to check for leaked credentials during password change (see "passwdqc filter" tasks in pam.yml)
  • Variable sudo_group is also considered as administrator group (see tag polkit)
  • Consider setting PasswordAuthentication to no in /etc/ssh/sshd_config
  • Consider running arpwatch to detect ARP cache poisoning
  • passwdqc > libpwquality

Tags

Tags that you can use with ansible-playbook --tags:

  • pki
  • kernel
  • rng
  • network
    • firewall
    • ipv6
    • networkmanager
  • :wood: logging
  • :file_folder: Filesystem related:
    • :no_entry: permissions
    • fstab
    • suid & sgid
  • Specific software:
    • :bar_chart: sysstat
    • :blowfish: ssh
    • rkhunter
    • chkrootkit
    • aide
    • audit (use --skip-tags audit in Slackware if you don't have audit installed)
    • debsecan
    • debsums
    • lynis (to only configure Lynis you can use --tags lynis --skip-tags packages)
    • :sandwich: sudo
    • kerberos
    • :smiling_imp: clamav (use --skip-tags clamav in Slackware if you don't have clamav installed)
      • yara
    • :shield: apparmor
    • cron (also includes tasks regarding at)
    • php
    • :feather: apache
      • hsts
    • :clock10: ntp
    • lightdm
    • gnome
    • :tiger: tiger
    • john
    • 🔄 unattended-upgrades
  • :placard: banners
  • AAA:
    • :bar_chart: accounting (includes sysstat)
    • 🎟️ authorization
    • passwords
      • cracklib
    • :busts_in_silhouette: accounts
    • pam
      • limits
  • cgroup (Slackware)
  • hidepid (Slackware)
  • inittab (Slackware)
  • :shell: shells
  • umask
  • :timer_clock: timeout
  • polkit
  • systemd (experimental, enable with harden_systemd_services)

There are also operating system tags for tasks that only apply to specific OS.
You can speed up the hardening by skipping OSs that don't apply. E.g. if you're
hardening a Slackware system you can use --skip-tags debian.

Other tags are just metadata for now. You can list all the tags with
ansible-playbook --list-tags harden.yml.

Other features

  • :no_entry: There is a lock_account.yml playbook that you can use to lock user accounts. Just modify the hosts & user.
  • Limited hardening for FreeBSD (see freebsd.yml)
  • :sandwich: Experimental feature: If you enable sudo_ids in vars.yml, it enables "Sudo Intrusion Detection" as seen in chapter 9 of Sudo Mastery
    • Only for SHELLS Cmnd_Alias for now
  • Experimental and limited systemd service hardening (see services-systemd.yml)
    • Needs to be enabled with harden_systemd_services

Makefile

TargetDescription
pamcheckCheck (and diff) your Slackware's PAM configs
:blowfish: /etc/ssh/moduli.newCreate a new SSH moduli
/etc/audit/rules.d/31-privileged.rules.newFind privileged binaries to be audited
/etc/audit/rules.d/40-authorized_keys.rules.newFind SSH authorized_keys from /home/ to be audited
crlsDownload CRLs
/etc/ssl/certs/ca-certificates.crtCreate limited/reduced CA list

virtualenv

A recent version of Ansible is recommended to run harden.yml. If you want to do this with virtualenv, you can install it as follows:

  1. virtualenv venv
  2. . venv/bin/activate
  3. pip install ansible jmespath

Tests

See tests README

References

Hardening guides

Some of these documents are quite old, but most of the stuff still applies.

Other docs

Contributors

Showing top 4 contributors by commit count.

View all contributors on GitHub →

This article is auto-generated from pyllyukko/harden.yml via the GitHub API.Last fetched: 6/29/2026