Validate Before You Replace a Live File

Scenario: A template renders a config with one mistyped directive. The task succeeds, the handler reloads the service, and the service refuses to start. Every host in the group is now down.

New words, in plain English

The dangerous property of good automation is that it applies your mistake perfectly, everywhere, in seconds. A template with a syntax error is not caught by --syntax-check (that checks the playbook, not the rendered output) and not caught by --check (which happily reports the file would change). It is caught when the service fails to start.

validate closes that gap. Ansible renders to a temporary file, runs the command you give with %s replaced by that path, and only replaces the real file if the command exits zero. The service's own config parser becomes part of your deployment.

Every serious daemon ships one: nginx -t -c %s, visudo -cf %s, sshd -t -f %s, httpd -t -f %s, named-checkconf %s. Where a service has such a checker, using it is close to mandatory - it is the difference between a failed task on one host and an outage across a tier.

validate composes with the rest of the safety net rather than replacing it: serial limits how many hosts a bad change can reach at once, and --check --diff shows you the change before it happens.

Analogy: validate is a spell-check that refuses to send the letter, run before the envelope is sealed rather than after the post has gone out to ten thousand addresses.

A worked example

- name: Render the nginx configuration
  ansible.builtin.template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
    mode: "0644"
    backup: true
    validate: 'nginx -t -c %s'      # %s = the temporary rendered file
  notify: reload nginx

# Other daemons ship their own checkers
#   validate: 'visudo -cf %s'
#   validate: 'sshd -t -f %s'
#   validate: 'named-checkconf %s'

# Layered defence for a risky change:
#   ansible-playbook site.yml --check --diff --limit web01
#   ansible-playbook site.yml --limit web01
#   ansible-playbook site.yml        # with serial: 1 in the play

Note that validate runs against the temporary file, so a checker that resolves relative includes or expects the file to be at its final path can behave differently. nginx -t -c %s is the common case that works; a config that includes neighbouring files by relative path is the common case that does not, and needs testing.

Beyond validation, template writes atomically: the rendered content goes to a temporary file on the target and is then moved into place. A process reading the config never sees a half-written file - a guarantee that a shell: echo > file approach cannot make.

Warning: --syntax-check validates the playbook, not the rendered output. A template can produce a completely broken config file from a perfectly valid playbook.
Goal: Put this to work in the ansible-lineinfile-duplicates lab. Open /labs/ansible, pick ansible-lineinfile-duplicates, and fix the real broken project - Ansible really does SSH into four managed hosts and converge them.