Systemd Module — Managing Services
Every single “Restart app” handler in this course — going all the way back to the very first notify example, through the blocks and handlers section’s real-world patterns, right up through last chapter’s config-deployment example — has been ansible.builtin.debug, printing a message rather than actually doing anything. This is where that placeholder finally becomes real.
The systemd Module
Both our lab hosts — ubuntu and fedora — run systemd, despite being different distributions with different package managers. That’s exactly why this module doesn’t need an os_family split the way apt/dnf did: systemd itself is the same underlying init system regardless of which distro sits on top of it.
- name: Ensure nginx is running
ansible.builtin.systemd:
name: nginx
state: startedstate: Controlling A Service’s Current Status
started— ensure the service is running. Idempotent: does nothing if it’s already running.stopped— ensure the service is stopped.restarted— stop and start it again, unconditionally, every time this task runs.reloaded— ask the service to re-read its configuration without a full stop/start cycle, for services that support it.
- name: Restart nginx
ansible.builtin.systemd:
name: nginx
state: restartedenabled: Controlling Whether A Service Starts On Boot
This is a genuinely separate concern from state: — worth being explicit about, since conflating “is it running right now” with “will it start automatically on boot” is an easy early mistake:
- name: Ensure nginx starts on boot
ansible.builtin.systemd:
name: nginx
enabled: trueA service can be currently running but not enabled for boot, or enabled for boot but currently stopped — state and enabled are two independent properties, and neither implies the other.
Combining state And enabled
The typical real pattern sets both together:
- name: Ensure nginx is running and enabled on boot
ansible.builtin.systemd:
name: nginx
state: started
enabled: trueReplacing Every “Restart App” Placeholder, For Real
Here’s the actual payoff. Every handler this entire course has shown you, doing real work at last:
handlers:
- name: Restart app
ansible.builtin.systemd:
name: myapp
state: restartedDrop this in place of any debug-based “Restart app” handler from earlier in this course, and every notify:-triggered pattern already covered — change-detection, the loop-notification behavior, force_handlers, all of it — works exactly the same way, except it now genuinely restarts a service instead of printing a message about one.
daemon_reload: When You’ve Changed A Unit File
If a task modifies the unit file itself — not the application’s own config, but the systemd service definition — systemd needs to be told to reload its own internal knowledge of that file before a restart will correctly pick up the change. Combining directly with template from the previous chapter:
- name: Deploy custom systemd unit file
ansible.builtin.template:
src: myapp.service.j2
dest: /etc/systemd/system/myapp.service
notify: Reload systemd and restart app
handlers:
- name: Reload systemd and restart app
ansible.builtin.systemd:
name: myapp
state: restarted
daemon_reload: truedaemon_reload: true handles the reload of systemd’s own unit-file cache; state: restarted in the same task then applies it.
A Gotcha: restarted Is Not The Same As reloaded
state: restarted fully stops and starts a service — any active connection to it drops, however briefly, during that window. state: reloaded asks a service to re-read its configuration without a full stop, and for a service that genuinely supports it (nginx is a common example), that means zero dropped connections. Defaulting to restarted out of habit — because it’s what most examples show — introduces unnecessary downtime for anything that would have handled reloaded gracefully instead. The right choice depends entirely on whether the specific service you’re managing actually implements a real reload; not every service does, and trying reloaded against one that doesn’t can behave unpredictably rather than simply failing cleanly.
Best Practices
- Set
stateandenabledexplicitly, together, when both genuinely matter — never assume one implies the other. - Put real
systemdtasks in handlers, not thedebugplaceholders used for illustration throughout the rest of this course — this is the module that makes every earliernotify:example actually do something. - Use
daemon_reload: truewhenever a task changes the unit file itself, not just the application’s own configuration. - Prefer
state: reloadedoverstate: restartedwhen the specific service you’re managing genuinely supports a graceful reload — check its own documented behavior rather than defaulting to a full restart out of habit.