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
| Feature | ansible.builtin.systemd | ansible.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 when | Modern 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
See also: Ansible uri Module: Make HTTP/REST API Calls from Playbooks (Guide)
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
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
See also: Ansible wait_for Module: Wait for Conditions, Ports & Files (Guide)
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
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
Multiple Related Services
- 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
Related Articles
• Ansible Handlers: Trigger Actions on Change • Ansible block, rescue, always: Error Handling Guide • Ansible retries & until: Retry Failed Tasks • Ansible Cron Module: Schedule Tasks GuideCategory: installation