AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

Ansible systemd Module: Manage Services, Timers & Units (Guide)

By Luca Berton · Published 2024-01-01 · Category: installation

How to manage systemd services with Ansible systemd module (ansible.builtin.systemd). Start, stop, enable, reload services, manage timers and units.

The ansible.builtin.systemd module (also available as ansible.builtin.systemd_service) manages systemd units — services, timers, sockets, and more. It's the preferred way to manage services on modern Linux systems (RHEL 7+, Ubuntu 16.04+, Debian 8+).

Basic Operations

Start a Service

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

Stop a Service

- name: Stop nginx
  ansible.builtin.systemd:
    name: nginx
    state: stopped

Restart a Service

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

Reload a Service

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

Enable at Boot

- name: Enable and start nginx
  ansible.builtin.systemd:
    name: nginx
    state: started
    enabled: true

Disable and Stop

- name: Disable and stop nginx
  ansible.builtin.systemd:
    name: nginx
    state: stopped
    enabled: false

See also: Ansible 2.17.0-rc1: Elevating Automation with ‘Gallows Pole’

systemd vs service Module

Featureansible.builtin.systemdansible.builtin.service
systemd-specific✅ Full support❌ Basic only
daemon_reload✅ Yes❌ No
Mask/unmask✅ Yes❌ No
Timers✅ Yes❌ No
Scope (user/system)✅ Yes❌ No
Cross-platform❌ systemd only✅ SysV, Upstart, systemd
Use whenModern Linux (systemd)Multi-platform compatibility

Daemon Reload

After modifying unit files, reload the systemd daemon:

- name: Deploy custom service file
  ansible.builtin.copy:
    src: myapp.service
    dest: /etc/systemd/system/myapp.service
    mode: '0644'

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

Reload Daemon Without Managing a Service

- name: Just reload systemd daemon
  ansible.builtin.systemd:
    daemon_reload: true

Mask and Unmask Units

Masking prevents a service from being started at all (even manually):

- name: Mask bluetooth (prevent it from ever starting)
  ansible.builtin.systemd:
    name: bluetooth
    masked: true

- name: Unmask bluetooth
  ansible.builtin.systemd:
    name: bluetooth
    masked: false

See also: Ansible wait_for Module: Wait for Conditions, Ports & Files (Guide)

Manage Timers

Systemd timers are the modern replacement for cron:

- name: Deploy timer unit
  ansible.builtin.copy:
    content: |
      [Unit]
      Description=Run backup daily

      [Timer]
      OnCalendar=daily
      Persistent=true

      [Install]
      WantedBy=timers.target
    dest: /etc/systemd/system/backup.timer
    mode: '0644'

- name: Deploy backup service unit
  ansible.builtin.copy:
    content: |
      [Unit]
      Description=Backup service

      [Service]
      Type=oneshot
      ExecStart=/usr/local/bin/backup.sh
    dest: /etc/systemd/system/backup.service
    mode: '0644'

- name: Enable and start timer
  ansible.builtin.systemd:
    name: backup.timer
    state: started
    enabled: true
    daemon_reload: true

User Services (--user scope)

Manage user-level systemd services:

- name: Enable user service
  ansible.builtin.systemd:
    name: my-user-app
    state: started
    enabled: true
    scope: user
  become: false

See also: Ansible uri Module: Make HTTP/REST API Calls from Playbooks (Guide)

Deploy Custom Service Files

Complete Service Deployment Pattern

- name: Create systemd service for application
  ansible.builtin.copy:
    content: |
      [Unit]
      Description={{ app_name }} Application
      After=network.target postgresql.service
      Requires=postgresql.service

      [Service]
      Type=simple
      User={{ app_user }}
      Group={{ app_group }}
      WorkingDirectory={{ app_dir }}
      ExecStart={{ app_dir }}/bin/start.sh
      ExecReload=/bin/kill -HUP $MAINPID
      Restart=on-failure
      RestartSec=5
      StandardOutput=journal
      StandardError=journal
      SyslogIdentifier={{ app_name }}

      # Security hardening
      NoNewPrivileges=true
      ProtectSystem=strict
      ProtectHome=true
      ReadWritePaths={{ app_dir }}/data {{ app_dir }}/logs
      PrivateTmp=true

      # Environment
      Environment=NODE_ENV=production
      Environment=PORT={{ app_port }}
      EnvironmentFile=-/etc/default/{{ app_name }}

      [Install]
      WantedBy=multi-user.target
    dest: "/etc/systemd/system/{{ app_name }}.service"
    owner: root
    group: root
    mode: '0644'
  notify: reload and restart app

handlers:
  - name: reload and restart app
    ansible.builtin.systemd:
      name: "{{ app_name }}"
      state: restarted
      daemon_reload: true

Using Templates

- name: Deploy service from template
  ansible.builtin.template:
    src: myapp.service.j2
    dest: /etc/systemd/system/myapp.service
    mode: '0644'
  notify:
    - reload systemd
    - restart myapp

handlers:
  - name: reload systemd
    ansible.builtin.systemd:
      daemon_reload: true

  - name: restart myapp
    ansible.builtin.systemd:
      name: myapp
      state: restarted

Gather Service Facts

- name: Get all service states
  ansible.builtin.service_facts:

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

- name: List all running services
  ansible.builtin.debug:
    msg: "{{ ansible_facts.services | dict2items | selectattr('value.state', 'equalto', 'running') | map(attribute='key') | list }}"

Wait for Service to Be Ready

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

- name: Wait for application to be ready
  ansible.builtin.uri:
    url: "http://localhost:{{ app_port }}/health"
    status_code: 200
  register: health
  retries: 30
  delay: 5
  until: health.status == 200

Real-World Patterns

Rolling Service Restart

- name: Rolling restart of web servers
  hosts: webservers
  serial: 1
  tasks:
    - name: Restart nginx
      ansible.builtin.systemd:
        name: nginx
        state: restarted

    - name: Wait for nginx to be ready
      ansible.builtin.uri:
        url: "http://localhost/health"
        status_code: 200
      retries: 10
      delay: 3
      until: result.status == 200
      register: result
- name: Manage application stack
  ansible.builtin.systemd:
    name: "{{ item }}"
    state: started
    enabled: true
  loop:
    - redis
    - postgresql
    - myapp-worker
    - myapp-web

Conditional Service Management

- name: Enable firewalld on RHEL
  ansible.builtin.systemd:
    name: firewalld
    state: started
    enabled: true
  when: ansible_os_family == 'RedHat'

- name: Enable ufw on Ubuntu
  ansible.builtin.systemd:
    name: ufw
    state: started
    enabled: true
  when: ansible_distribution == 'Ubuntu'

Troubleshooting

Check Service Status

- name: Check service status
  ansible.builtin.command: systemctl status myapp
  register: status
  changed_when: false
  failed_when: false

- name: Show status
  ansible.builtin.debug:
    msg: "{{ status.stdout_lines }}"

View Service Logs

- name: Get recent service logs
  ansible.builtin.command: journalctl -u myapp -n 50 --no-pager
  register: logs
  changed_when: false

- name: Show logs
  ansible.builtin.debug:
    msg: "{{ logs.stdout_lines }}"

FAQ

What is the difference between systemd and service modules?

The systemd module provides full systemd functionality: daemon_reload, mask/unmask, timers, user scope. The service module is cross-platform (works with SysV, Upstart, and systemd) but only supports basic start/stop/enable operations.

When do I need daemon_reload?

Use daemon_reload: true after creating, modifying, or deleting systemd unit files in /etc/systemd/system/. This is equivalent to running systemctl daemon-reload on the command line.

What is the difference between restart and reload?

restart stops and starts the service (new process). reload sends a signal (usually SIGHUP) telling the service to re-read its configuration without stopping. Not all services support reload.

How do I manage services on systems without systemd?

Use ansible.builtin.service instead — it auto-detects the init system (SysV, Upstart, systemd, OpenRC) and uses the appropriate backend.

How do I create a systemd service with Ansible?

Deploy a .service unit file to /etc/systemd/system/ using the copy or template module, then use the systemd module with daemon_reload: true and state: started to activate it.

Conclusion

The ansible.builtin.systemd module is the standard for service management on modern Linux:

  • Basic ops: state: started/stopped/restarted/reloaded
  • Boot config: enabled: true/false
  • After unit changes: daemon_reload: true
  • Prevent start: masked: true
  • Timers: Same module, specify timer unit name
  • Always use handlers for config-triggered restarts

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home