Skip to content

base

The base role is the baseline every other role assumes. Apply it first.

  • Installs base_packages (ca-certificates, curl, gnupg, htop, jq, rsync, unattended-upgrades).
  • Sets the time zone (base_timezone, UTC by default).
  • Limits the systemd journal to base_journald_max_use (500M).
  • Turns on unattended security upgrades, with an optional automatic reboot at a set time.
  • Creates a swap file when base_swap_size_mb is greater than 0, and sets vm.swappiness.
  • Hardens sshd with a drop-in (on by default, see below).
  • Optionally adds static routes at boot, and lets services bind addresses that are not up yet.

With base_sshd_hardening on (the default) the role writes /etc/ssh/sshd_config.d/50-base.conf and checks it with sshd -t before it is used. The drop-in:

  • allows key-only login (AuthenticationMethods publickey);
  • lets root log in with a key only (base_sshd_permit_root_login);
  • disables agent and X11 forwarding, tunnels, gateway ports and user environment;
  • turns TCP forwarding off (base_sshd_allow_tcp_forwarding);
  • sets MaxAuthTries 3, LoginGraceTime 30, MaxSessions 10 (Ansible multiplexes sessions) and MaxStartups 10:30:60;
  • closes idle sessions after 10 minutes (ClientAliveInterval 300, ClientAliveCountMax 2);
  • sets LogLevel VERBOSE, so key fingerprints reach the log, which fail2ban and CrowdSec read;
  • allows only modern key exchange, cipher and MAC lists that OpenSSH 9.6 (Ubuntu 24.04) and later accept.

base_sshd_allow_users adds an AllowUsers line. Leave it empty and every user with a key may log in.

VariableDefaultMeaning
base_sshd_hardeningtrueWrite the hardening drop-in. The other base_sshd_* options apply only while it is on.
base_sshd_port22sshd port. fail2ban reads this value for its sshd jail.
base_sshd_permit_root_loginprohibit-passwordSet "no" once every play connects as an unprivileged user.
base_sshd_allow_users[]AllowUsers patterns. Include the user Ansible connects as.
base_sshd_listen_addresses[]Bind sshd to these addresses only.
base_swap_size_mb0Swap file size in MiB; 0 leaves swap alone.
base_unattended_rebootfalseReboot when an upgrade needs it, at base_unattended_reboot_time.
base_static_routes[]Routes as {to: CIDR, via: gateway}.

All variables: base in the reference.

- hosts: servers
become: true
roles:
- role: noixdev.infra.base
vars:
base_timezone: Europe/Berlin
base_swap_size_mb: 2048
base_sshd_allow_users: [deploy]
base_sshd_permit_root_login: "no"
base_unattended_reboot: true
base_unattended_reboot_time: "04:00"

Static routes behind a private gateway:

base_static_routes:
- { to: 192.0.2.0/24, via: 198.51.100.1 }

The role installs a oneshot unit that sets each route after the network is up, on the interface the gateway is reached through.

  • Include your Ansible user in base_sshd_allow_users. With a non-empty list, a user who is not on it can no longer log in, and the next run cannot connect.
  • Hardening is key-only. A host that you reach by password stops accepting it as soon as the role runs. Install your key first.
  • base_sshd_listen_addresses changes how sshd starts. When set, the role turns off socket activation (ssh.socket) and enables net.ipv4.ip_nonlocal_bind and net.ipv6.ip_nonlocal_bind, so sshd starts before an address is up. Choose addresses you can still reach.
  • Keep base_sshd_max_auth_tries above the number of keys a client offers, or clients with many keys in their agent are refused before the right one is tried.
  • MaxSessions below 10 breaks Ansible’s connection multiplexing.
  • The default key exchange list works on OpenSSH 9.6 and later. Add mlkem768x25519-sha256 first only where every host runs OpenSSH 9.9 or later, because sshd refuses to start on an unknown algorithm name.