Skip to main content
RunBook Academy

AnsibleXVI · HandlersHandlers

Restart, reload, and doing it to everyone at once

Advanced⏱ ~20 minansible-playbooksystemctl

What you'll learn

  • Choose between reloaded and restarted from what the service actually does
  • Recognise the configuration changes that a reload cannot apply
  • Calculate how many hosts a handler will act on before running the play
  • Name serial as the control for simultaneous fleet-wide handler execution

Prerequisites

Verified against ansible-core 2.21.x · ansible (community package) 14.x · Python (controller) 3.12+ · ansible-lint 26.x · Molecule 26.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-11

Not yet marked complete on this device.

The handler mechanism has now been established as a change gate: the right hosts, at the right time, for the right reason. This lesson is about the two questions the mechanism does not answer for you.

What does the handler do to the service? A reload and a restart are different operations with different costs, and the module makes both look like a one-word argument.

How many hosts does it do it to, simultaneously? A handler that fires on every host in a 400-host play is a 400-host service event. The handler is correct. The blast radius is not.

reloaded versus restarted

The Linux course covers what systemd actually does for each — unit states, ExecReload, what happens to an open socket. This lesson assumes that and asks the operational question on top of it: which one should the handler ask for?

Service impact possiblethe two handlers
handlers:
- name: Reload nginx
  ansible.builtin.systemd_service:
    name: nginx
    state: reloaded

- name: Restart nginx
  ansible.builtin.systemd_service:
    name: nginx
    state: restarted

The distinction that matters operationally:

reloadedrestarted
In-flight requestsGenerally surviveDropped
Listening socketKept openClosed and reopened
Process identitySame main PID, or a controlled handoverNew process
Warm caches, pools, JIT statePreservedLost
Applies unit-file changesNoYes
Applies changes to startup argumentsNoYes
Recovers a wedged processNoYes

Prefer reloaded when the service supports it and the change is a configuration change. That is the majority of what a config-management handler does, and the reason to prefer it is not elegance — it is that a reload does not appear in your error budget.

Service impact possibleunit file changes need their own path
- name: Deploy the unit file
ansible.builtin.template:
  src: billing.service.j2
  dest: /etc/systemd/system/billing.service
  mode: '0644'
notify: billing unit changed

- name: Deploy the application configuration
ansible.builtin.template:
  src: billing.conf.j2
  dest: /etc/billing/billing.conf
  mode: '0640'
notify: billing config changed

handlers:
- name: Reload systemd and restart billing
  ansible.builtin.systemd_service:
    name: billing
    state: restarted
    daemon_reload: true
  listen: billing unit changed

- name: Reload billing
  ansible.builtin.systemd_service:
    name: billing
    state: reloaded
  listen: billing config changed

The other question: how many hosts?

Here is a play that is entirely correct by everything this part has taught.

Service impact possiblecorrect, and dangerous
- name: Manage the reverse proxy fleet
hosts: webservers
become: true
tasks:
  - name: Deploy the site configuration
    ansible.builtin.template:
      src: site.conf.j2
      dest: /etc/nginx/conf.d/site.conf
      mode: '0644'
    notify: Reload nginx

handlers:
  - name: Reload nginx
    ansible.builtin.systemd_service:
      name: nginx
      state: reloaded

On most runs this does nothing. On the run where somebody changes a value in group_vars/webservers.yml, every host in the group reports changed and every host notifies the handler.

With the default linear strategy and the default forks of 5, the controller works through the flush five hosts at a time as fast as SSH allows. On 400 hosts that is not a rolling reload. It is a fleet-wide reload compressed into however long 400 SSH round trips take, with no gap for anything to be noticed in.

If the handler is restarted rather than reloaded, that is a fleet-wide simultaneous outage.

Knowing the number before you run

The count is knowable before the play runs, and checking it is cheap.

Read-only / Safehow many hosts could this handler act on
$ ansible-playbook -i inventories/prod site.yml --list-hosts
playbook: site.yml

play #1 (webservers): Manage the reverse proxy fleet	TAGS: []
  pattern: ['webservers']
  hosts (412):
    web001.example.com
    web002.example.com
    web003.example.com
    ...

hosts (412) is the upper bound on the handler’s blast radius for this run. Every one of those hosts will reload nginx if the template renders differently for it.

The follow-up question — will it render differently for all of them — is answered by --check --diff, which shows the rendered difference per host without writing anything. A change to a value in group_vars that appears in the diff for all 412 hosts is a 412-host reload; a change affecting one host’s host_vars is a one-host reload. Those are very different changes and they look identical in the Git diff of the template.

Knowledge check

Knowledge check · 4 questions

  1. Q1. A play edits /etc/systemd/system/billing.service and notifies a handler with state: reloaded. What is the outcome?

  2. Q2. A play targets 412 hosts. Someone changes one value in group_vars/webservers.yml. The template task notifies a reload handler. What happens on the run?

  3. Q3. Which of these are genuine reasons to choose restarted over reloaded? Select all that apply.

  4. Q4. Raising forks on a play whose handler restarts a service makes the run faster and also makes the resulting service event sharper.

Passing score: 75%. Answers are checked in this browser.