Skip to main content
Cloud DevOps Solutions
Get a quote Talk to an engineer

Ansible / Config Mgmt

The Server Baseline Role: Hardening, Time, SSH, Users and Packages

A server baseline is the configuration every machine receives before any application: hardened OS settings, time sync, SSH policy, managed users and a defined package set. We write it as one tested Ansible role so new and existing servers converge on the same secure starting point.

Key takeaways

  • The baseline is the highest-leverage role you will write, because it applies to every server.
  • Hardening must be tested against real workloads, or it will break them and be switched off.
  • SSH policy and user management are where most audit findings and access incidents start.
  • Time synchronisation is a security and debugging dependency, not a nicety.
  • Baseline changes roll out in stages, with a canary group and a way to back out.

Why every estate needs a baseline

Look at ten servers that were built over three years by different people. Time zones differ. Two allow root login over SSH. One has a developer’s personal account with passwordless sudo. Several run different versions of the same package. Nobody chose any of this, and nobody can say with confidence which of them is secure.

A baseline role removes the guesswork. It states, in code, what every server must look like, and it keeps stating it. It is the foundation for patching, CIS compliance and every application role that follows, and it is a core part of our Ansible configuration management service.

What good looks like

  • A new server reaches the standard configuration with one playbook run, with no manual steps.
  • Existing servers converge on the same standard without being rebuilt.
  • Access is explicit: named users, keys from a managed source, sudo rules in version control.
  • Exceptions exist but are visible as variables with a reason.
  • The role is tested on every operating system you run.

How it works in practice

Structure

One role, several task files included by tag, so each area can be run alone:

# roles/baseline/tasks/main.yml
- ansible.builtin.import_tasks: packages.yml
  tags: [baseline, packages]
- ansible.builtin.import_tasks: time.yml
  tags: [baseline, time]
- ansible.builtin.import_tasks: ssh.yml
  tags: [baseline, ssh]
- ansible.builtin.import_tasks: users.yml
  tags: [baseline, users]
- ansible.builtin.import_tasks: kernel.yml
  tags: [baseline, kernel]

SSH: the part where mistakes lock you out

The one rule is to validate before applying. A broken sshd_config on a remote server with no console is an outage.

- name: Deploy sshd configuration
  ansible.builtin.template:
    src: sshd_config.j2
    dest: /etc/ssh/sshd_config
    owner: root
    group: root
    mode: "0600"
    validate: /usr/sbin/sshd -t -f %s
  notify: Restart sshd

The template sets PermitRootLogin no, PasswordAuthentication no (unless policy says otherwise), MaxAuthTries, idle timeouts and an allowed-groups line. Handlers use a reload where the platform supports it, and the playbook keeps an existing connection open until a fresh login is proven to work. We never change SSH settings across a whole fleet in one go.

Users and sudo

Users are defined as data, not as tasks:

baseline_users:
  - name: asmith
    groups: [sudo]
    ssh_keys:
      - "ssh-ed25519 AAAA... asmith@example"
baseline_removed_users:
  - olduser

The role creates listed users, installs their public keys, and removes accounts that should not exist. Sudo rules are written to /etc/sudoers.d/ with validate: visudo -cf %s. For larger organisations, we integrate with your directory service instead, and the role only configures the client side.

Time

Servers with drifting clocks produce confusing logs, failed TLS handshakes and broken Kerberos or token validation. The role installs chrony, points it at agreed sources, and verifies with chronyc tracking that the system is synchronised. On cloud VMs we often use the provider’s internal time service.

Packages, kernel and services

The role installs the required tooling (for example curl, chrony, auditd), removes packages that should not be present, disables unused services and sets sysctl values with ansible.posix.sysctl. Choices such as disabling IPv6, tightening net.ipv4 parameters or enabling a host firewall are made with your team, not assumed, because they can surprise applications.

Decisions and trade-offs

One baseline or per-role variants. A database host and a bastion legitimately differ. We keep one baseline for the common core and use group variables to switch specific areas, rather than forking the role.

Strict by default or permissive by default. We default to the secure setting and make exceptions explicit. That makes loosening something a visible decision.

Configure in place or build golden images. Baking the baseline into images with Packer gives faster, more consistent launches. Running the role against running servers keeps them from drifting afterwards. Many clients do both, using the same role in both places.

When not to bother. If your hosts are immutable container nodes managed by a cloud provider, much of the baseline is already handled by the image, and you may need only the parts that cover access. Our Kubernetes consulting team can advise on what the node image already covers.

Common failure modes

  • Lockout after SSH changes. Prevented by validation, staged rollout and a console fallback.
  • Hardening that breaks applications. Prevented by testing and exception variables.
  • Key sprawl. Authorised keys edited by hand. The role owns the file, so unmanaged keys are removed.
  • Different behaviour across distributions. Handled with OS-specific variable files and tests on each.
  • Secrets on disk. Root-readable credentials left by earlier scripts. We search for and relocate them, tied to the secrets work.

Delivery and handover

You receive the baseline role in your repository, tests for every supported operating system, a rollout plan with canary groups, a variable reference listing each switch and its default, and a written baseline standard that your security team can review. Maintenance is simple because it builds on the same role library conventions as everything else.

A rollout example: applying a baseline to servers that already exist

Greenfield servers are easy, because the baseline is simply part of the build. The harder case is a running estate where each server has quirks nobody documented. We roll out in stages. First, run the role in check mode with diff output across a representative sample and read what it would change. Expect surprises: a service account with a shell, an SSH setting a legacy application relies on, a package pinned for a reason nobody remembers.

Second, resolve each surprise as either a fix on the server or a documented exception in group variables, never a quiet weakening of the role. Third, apply the role to a non-critical group, watch for a full business cycle, and only then widen the rollout. Keep the first apply separate from later tuning, so a failure points at one change.

Two controls deserve extra care because they can lock you out: SSH configuration and firewall rules. Validate the SSH configuration before reloading the daemon, keep an existing session open while testing a new one, and make sure there is an out-of-band console route for the worst case. These habits cost minutes and prevent the one incident that makes people distrust automation.

How to tell it is working

  • A scheduled check mode run across the fleet reports no unexpected changes.
  • Any server can be rebuilt to the baseline from scratch, and a fresh one looks identical to an old one.
  • An audit question about SSH settings or user access is answered by reading a file.
  • Leavers’ accounts are removed by a pull request, not by memory.

To see how your current servers compare to a sensible baseline, contact us.

References

How we deliver it

  1. 01

    Survey the fleet

    We gather facts across servers to find operating system versions, existing users, SSH settings and package differences.

  2. 02

    Define the standard

    A written baseline covers each area, with decisions agreed with your security and application owners.

  3. 03

    Build the role

    Separate task files handle packages, time, SSH, users, kernel parameters, auditing and services, each tagged.

  4. 04

    Test on every supported OS

    Molecule scenarios run the role on each distribution you use and verify the end state.

  5. 05

    Roll out in stages

    Canary hosts, then a non-production tier, then production in batches, watching for regressions.

  6. 06

    Keep it enforced

    Scheduled runs in check mode report drift, and corrections are applied through the normal pipeline.

Tools and technologies

Ansibleansible.posixcommunity.generalchronyOpenSSHauditdsysctlfirewalldufwMoleculeLynisOpenSCAP

Frequently asked questions

What belongs in a server baseline?
Core packages and removals, time synchronisation, SSH policy, user and sudo management, kernel and network parameters, a host firewall, logging and audit configuration, automatic security updates policy and banner or MOTD settings. Application-specific settings do not belong there. If a setting only matters to one service, it lives in that service's role.
How do you harden SSH with Ansible?
We manage sshd_config as a validated template: key-based authentication, no root login, no password authentication where policy allows, limited users or groups, modern ciphers and idle timeouts. The template task uses validate with sshd -t so a bad config is rejected before it can lock you out.
Will hardening break our applications?
It can, which is why we test before rolling out. Stricter kernel parameters, removed packages or closed ports can surprise software that relied on defaults. We run the role on staging copies of each workload, record exceptions as variables, and apply production changes in small batches with checks between them.
Is chrony or ntpd better for time sync?
Chrony is the default on current mainstream Linux distributions and handles intermittent connectivity and virtual machines well. We use it unless a platform dictates otherwise. Whatever the daemon, the role points servers at agreed internal or cloud time sources and verifies synchronisation.
Does the baseline apply to Windows?
A Windows baseline is a separate role set using the ansible.windows and community.windows collections over WinRM or SSH. The structure and testing approach are the same, but the settings and modules differ, so we scope it separately and test it on your supported Windows Server versions.

Talk to an engineer about ansible server baseline role

Book a free 30-minute call. We review your current setup and tell you honestly whether and how we can help.

Written by Saddique Khan, DevOps / SRE / Cloud Consultant Engineer. Last updated 2026-10-08.