AnsibleXVI · HandlersHandlers
Restart, reload, and doing it to everyone at once
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
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?
handlers:
- name: Reload nginx
ansible.builtin.systemd_service:
name: nginx
state: reloaded
- name: Restart nginx
ansible.builtin.systemd_service:
name: nginx
state: restartedThe distinction that matters operationally:
reloaded | restarted | |
|---|---|---|
| In-flight requests | Generally survive | Dropped |
| Listening socket | Kept open | Closed and reopened |
| Process identity | Same main PID, or a controlled handover | New process |
| Warm caches, pools, JIT state | Preserved | Lost |
| Applies unit-file changes | No | Yes |
| Applies changes to startup arguments | No | Yes |
| Recovers a wedged process | No | Yes |
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.
- 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 changedThe other question: how many hosts?
Here is a play that is entirely correct by everything this part has taught.
- 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: reloadedOn 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.
$ ansible-playbook -i inventories/prod site.yml --list-hostsplaybook: 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
Q1. A play edits /etc/systemd/system/billing.service and notifies a handler with state: reloaded. What is the outcome?
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?
Q3. Which of these are genuine reasons to choose restarted over reloaded? Select all that apply.
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.