Managing Services with Systemd in Ansible

By LinuxCert.Guru Team·

Objective

This guide covers service management in Ansible using the systemd module. By the end, you will know how to:

  • Start, stop, restart, and reload services on remote hosts using Ansible
  • Enable and disable services so they start automatically at boot
  • Use daemon_reload to pick up new or modified unit files before managing services
  • Combine service management with package installation and configuration deployment in real playbooks
  • Understand the difference between the systemd and service modules and when to use each
  • Apply these patterns to RHCE exam tasks involving service automation

Managing services manually on one server is straightforward. Managing them across fifty servers consistently is not, unless you automate it. Ansible's systemd module lets you define the desired state of any service and apply it across your entire inventory in a single playbook run, with the same result every time regardless of the current state of each host.

systemd Module vs service Module

Ansible has two modules for service management. Understanding the difference helps you choose the right one:

Module What It Does When to Use It systemd-specific Features
ansible.builtin.service Generic service management, works with SysV init, Upstart, and systemd When you need portability across different init systems No: cannot use daemon_reload, scope, or unit
ansible.builtin.systemd systemd-specific service management On RHEL, Fedora, Ubuntu 16.04+, and any modern Linux using systemd Yes: daemon_reload, masked, scope, unit file path

For RHCE exam tasks on RHEL, always use ansible.builtin.systemd. It gives you full control over systemd-specific behaviour that the generic service module cannot access.

The systemd Module: Key Parameters

Parameter Values What It Controls
name Service name (e.g. nginx, sshd) Which service to manage
state started, stopped, restarted, reloaded The desired runtime state of the service
enabled true, false Whether the service starts automatically at boot
daemon_reload true, false Runs systemctl daemon-reload before managing the service
masked true, false Masks or unmasks the service (prevents it from being started by any means)
force true, false Forces the operation even if the service is in an unexpected state

Basic Service Management

Starting and Enabling a Service

The most common task: ensure a service is running right now and configured to start automatically after a reboot.

---
- name: Manage nginx service
  hosts: webservers
  tasks:

    - name: Start nginx and enable it at boot
      ansible.builtin.systemd:
        name: nginx
        state: started
        enabled: true

What Ansible does with this task:

  • If nginx is already running and enabled: does nothing, reports ok
  • If nginx is stopped: starts it, reports changed
  • If nginx is not enabled: enables it, reports changed
  • Both state and enabled are checked independently. You can start without enabling, or enable without starting, or do both at once.

Stopping and Disabling a Service

---
- name: Disable and stop a service
  hosts: all
  tasks:

    - name: Stop and disable firewalld
      ansible.builtin.systemd:
        name: firewalld
        state: stopped
        enabled: false

Restarting a Service

---
- name: Restart a service
  hosts: webservers
  tasks:

    - name: Restart nginx to apply new configuration
      ansible.builtin.systemd:
        name: nginx
        state: restarted

Note: restarted always restarts the service even if it was already running. Use this carefully in production. For most configuration changes, triggering a restart through a handler (shown below) is safer because it only restarts when something actually changed.

Reloading a Service

- name: Reload nginx configuration without downtime
  ansible.builtin.systemd:
    name: nginx
    state: reloaded

The difference between restarted and reloaded: a restart stops and starts the process (brief interruption), a reload sends a signal to the running process to re-read its configuration without stopping (no interruption). Not every service supports reload. Check your service documentation before relying on it.

daemon_reload: When and Why

When you create a new systemd unit file or modify an existing one, systemd does not know about the change until you run systemctl daemon-reload. Ansible's daemon_reload: true parameter handles this automatically before performing the service operation.

---
- name: Deploy a custom service unit and start it
  hosts: all
  tasks:

    - name: Copy the custom service unit file
      ansible.builtin.copy:
        src: files/myapp.service
        dest: /etc/systemd/system/myapp.service
        owner: root
        group: root
        mode: '0644'

    - name: Reload systemd and start the new service
      ansible.builtin.systemd:
        name: myapp
        state: started
        enabled: true
        daemon_reload: true

When daemon_reload: true is set, Ansible runs the equivalent of systemctl daemon-reload before touching the service. Without this, starting a newly copied unit file would fail because systemd still has no record of it.

The Right Pattern: Handlers for Service Restarts

The most common mistake in Ansible service management is using state: restarted directly in a task. This restarts the service every time the playbook runs, even when nothing changed. The correct pattern is to use a handler that only triggers when a configuration file or unit file actually changes.

---
- name: Install and configure nginx
  hosts: webservers
  tasks:

    - name: Install nginx
      ansible.builtin.dnf:
        name: nginx
        state: present

    - name: Deploy nginx configuration
      ansible.builtin.template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        owner: root
        group: root
        mode: '0644'
        validate: nginx -t -c %s
      notify: Restart nginx

    - name: Deploy virtual host configuration
      ansible.builtin.template:
        src: vhost.conf.j2
        dest: /etc/nginx/conf.d/mysite.conf
        owner: root
        group: root
        mode: '0644'
      notify: Restart nginx

    - name: Start and enable nginx
      ansible.builtin.systemd:
        name: nginx
        state: started
        enabled: true

  handlers:
    - name: Restart nginx
      ansible.builtin.systemd:
        name: nginx
        state: restarted

How handlers work here:

  • The notify: Restart nginx directive queues the handler when the task reports changed
  • If neither configuration file changed, neither task reports changed, and the handler never runs
  • If both configuration files changed, the handler is queued twice but runs only once at the end of the play
  • The service restarts exactly when it needs to, and never when it doesn't

A Complete Real-World Example: Installing and Managing a Web Stack

---
- name: Deploy and manage LAMP stack services
  hosts: webservers
  vars:
    http_port: 80
    db_service: mariadb

  tasks:

    - name: Install required packages
      ansible.builtin.dnf:
        name:
          - httpd
          - mariadb-server
          - php
          - php-mysqlnd
        state: present

    - name: Deploy Apache configuration
      ansible.builtin.template:
        src: httpd.conf.j2
        dest: /etc/httpd/conf/httpd.conf
        owner: root
        group: root
        mode: '0644'
        validate: httpd -t -f %s
      notify: Restart httpd

    - name: Start and enable Apache
      ansible.builtin.systemd:
        name: httpd
        state: started
        enabled: true

    - name: Start and enable MariaDB
      ansible.builtin.systemd:
        name: "{{ db_service }}"
        state: started
        enabled: true

    - name: Ensure firewalld is running
      ansible.builtin.systemd:
        name: firewalld
        state: started
        enabled: true

    - name: Open HTTP port in firewalld
      ansible.posix.firewalld:
        service: http
        permanent: true
        state: enabled
        immediate: true

  handlers:
    - name: Restart httpd
      ansible.builtin.systemd:
        name: httpd
        state: restarted

Managing Multiple Services at Once

---
- name: Ensure all required services are running
  hosts: all
  vars:
    required_services:
      - name: sshd
        enabled: true
        state: started
      - name: chronyd
        enabled: true
        state: started
      - name: rsyslog
        enabled: true
        state: started
      - name: auditd
        enabled: true
        state: started

  tasks:

    - name: Manage all required services
      ansible.builtin.systemd:
        name: "{{ item.name }}"
        state: "{{ item.state }}"
        enabled: "{{ item.enabled }}"
      loop: "{{ required_services }}"

Looping over a list of services with their desired state keeps the playbook clean and makes it easy to add or remove services by editing the variable, not the task logic.

Masking Services

Masking a service prevents it from being started by any means, including manual systemctl start commands, dependencies, or other services. It is stronger than simply disabling.

---
- name: Harden system by masking unnecessary services
  hosts: all
  tasks:

    - name: Mask the bluetooth service
      ansible.builtin.systemd:
        name: bluetooth
        masked: true

    - name: Mask the avahi-daemon service
      ansible.builtin.systemd:
        name: avahi-daemon
        masked: true

    - name: Unmask a service that was previously masked
      ansible.builtin.systemd:
        name: cups
        masked: false
        enabled: false
        state: stopped

Verifying Service State in a Playbook

After managing a service, you can verify its state programmatically using the service_facts module and then act on the result:

---
- name: Verify service state after management
  hosts: all
  tasks:

    - name: Start nginx
      ansible.builtin.systemd:
        name: nginx
        state: started
        enabled: true

    - name: Gather service facts
      ansible.builtin.service_facts:

    - name: Confirm nginx is running
      ansible.builtin.debug:
        msg: "nginx is {{ ansible_facts.services['nginx.service'].state }}"
      when: "'nginx.service' in ansible_facts.services"

    - name: Fail if nginx is not running
      ansible.builtin.fail:
        msg: "nginx failed to start on {{ inventory_hostname }}"
      when:
        - "'nginx.service' in ansible_facts.services"
        - ansible_facts.services['nginx.service'].state != 'running'

Common Mistakes

  • Using state: restarted directly instead of a handler. A task with state: restarted restarts the service on every playbook run regardless of whether anything changed. This causes unnecessary downtime and is incorrect for idempotent automation.
    • Use state: started in the service task and trigger restarts through handlers notified by configuration changes
  • Forgetting daemon_reload after deploying a unit file. If you copy a new .service file to /etc/systemd/system/ and immediately try to start the service without daemon_reload: true, systemd will not find the unit and the task will fail.
    • Always set daemon_reload: true on the first systemd task that runs after writing a unit file
  • Confusing enabled with state. enabled: true makes the service start at boot but does not start it right now. state: started starts it now but does not affect boot behaviour. Most tasks need both.
    • For the RHCE exam specifically: a service that is started but not enabled fails the reboot check
  • Using the service module instead of systemd on RHEL. The generic service module works but cannot use daemon_reload or masked. On RHEL systems where these features matter, always use ansible.builtin.systemd.
  • Not validating configuration before restarting. Restarting a service with a broken configuration file takes it down with no automatic recovery. Use the validate option on the template or copy task before the service restart handler runs.

State Values: What Each One Does

State Value Equivalent systemctl Command Behaviour Idempotent?
started systemctl start Starts the service if not already running. Does nothing if already running. Yes
stopped systemctl stop Stops the service if running. Does nothing if already stopped. Yes
restarted systemctl restart Always stops and starts the service, regardless of current state. No
reloaded systemctl reload Sends SIGHUP to the process to re-read configuration without stopping. No

Quick Reference

# Start a service and enable it at boot
- ansible.builtin.systemd:
    name: nginx
    state: started
    enabled: true

# Stop a service and disable it
- ansible.builtin.systemd:
    name: bluetooth
    state: stopped
    enabled: false

# Reload systemd after writing a unit file, then start the service
- ansible.builtin.systemd:
    name: myapp
    state: started
    enabled: true
    daemon_reload: true

# Mask a service (prevent it from starting by any means)
- ansible.builtin.systemd:
    name: avahi-daemon
    masked: true

# Restart via handler (correct pattern for config changes)
handlers:
  - name: Restart nginx
    ansible.builtin.systemd:
      name: nginx
      state: restarted

Service Management and the RHCE Exam

Service management with the systemd module is a core RHCE exam objective. Exam tasks in this area commonly look like:

  • Install a package and ensure its service is started and enabled across all managed hosts
  • Deploy a configuration file and restart the service only if the file changed
  • Ensure a list of services are all running and enabled on specific host groups
  • Deploy a custom systemd unit file and start the service it defines
  • Disable and stop a service that should not be running in a hardened environment

The exam environment reboots managed hosts after time ends and checks whether services are in the expected state. A service started with state: started but not enabled: true will be down after the reboot and the task scores zero. Both parameters are always required when the task says the service should be running.

Practice This in a Real Environment

Service management looks simple on paper. The subtleties, particularly the difference between start and enable, the handler pattern for restarts, and when to use daemon_reload, only become clear when you have watched an idempotent playbook run multiple times and seen exactly what changes and what doesn't.

LinuxCert.Guru has a dedicated hands-on lab for this: Managing Services with Systemd. It covers starting, stopping, enabling, reloading, and automating service management on real RHEL hosts with auto-graded tasks that verify both the running state and the boot enablement, the same way the RHCE exam checks your work.

Practice the Managing Services with Systemd lab at LinuxCert.Guru → https://linuxcert.guru/rhce/?name=rhce-managing-services-systemd

Conclusion

Ansible's systemd module gives you complete control over service state across your entire infrastructure from a single playbook. The concepts are straightforward but the patterns matter for both reliability and exam performance.

  • Use ansible.builtin.systemd on RHEL, not the generic service module, for full systemd feature access
  • Always set both state and enabled when a service needs to be running after reboots
  • Use handlers with state: restarted rather than putting restarts directly in tasks
  • Set daemon_reload: true whenever you deploy a new or modified unit file before managing the service
  • Use masked: true when a service should be completely prevented from starting, not just disabled
  • Validate configuration files before the service restart handler runs to avoid taking services down with broken configs

 

$_
Ready to go beyond reading?Practice with free, auto-graded RHCSA labs.
Start free →