ansible.builtin.cron Module: Schedule Cron Jobs & Tasks (Complete Guide)
By Luca Berton · Published 2026-04-03 · Category: troubleshooting
How to manage cron jobs with Ansible cron module (ansible.builtin.cron). Create, modify, remove scheduled tasks, set cron variables.
The ansible.builtin.cron module manages crontab entries on remote hosts. Create, modify, and remove scheduled tasks without manually editing crontab files.
Basic Cron Job
- name: Run backup every day at 2 AM
ansible.builtin.cron:
name: "daily backup"
minute: "0"
hour: "2"
job: "/opt/scripts/backup.sh >> /var/log/backup.log 2>&1"
See also: Mastering Time in Ansible: An Introduction to the now() Function
Schedule Patterns
# Every 15 minutes
- ansible.builtin.cron:
name: "health check"
minute: "*/15"
job: "/opt/scripts/healthcheck.sh"
# Every Monday at 9 AM
- ansible.builtin.cron:
name: "weekly report"
minute: "0"
hour: "9"
weekday: "1"
job: "/opt/scripts/weekly-report.sh"
# First day of each month
- ansible.builtin.cron:
name: "monthly cleanup"
minute: "0"
hour: "3"
day: "1"
job: "/opt/scripts/monthly-cleanup.sh"
# Every weekday at 8:30 AM
- ansible.builtin.cron:
name: "weekday sync"
minute: "30"
hour: "8"
weekday: "1-5"
job: "/opt/scripts/sync-data.sh"
# Specific months
- ansible.builtin.cron:
name: "quarterly audit"
minute: "0"
hour: "6"
day: "1"
month: "1,4,7,10"
job: "/opt/scripts/audit.sh"
Cron Parameters Reference
| Parameter | Description | Default | Values |
|-----------|-------------|---------|--------|
| minute | Minute | | 0-59, /N |
| hour | Hour | | 0-23, /N |
| day | Day of month | | 1-31, /N |
| month | Month | | 1-12, jan-dec |
| weekday | Day of week | | 0-6 (0=Sun), mon-sun |
| special_time | Special schedule | - | reboot, hourly, daily, weekly, monthly, yearly, annually |
See also: Ansible Development: Write Custom Modules, Plugins & Collections
Special Time Shortcuts
# On reboot
- ansible.builtin.cron:
name: "start app on boot"
special_time: reboot
job: "/opt/app/start.sh"
# Hourly
- ansible.builtin.cron:
name: "hourly metrics"
special_time: hourly
job: "/opt/scripts/collect-metrics.sh"
# Daily (midnight)
- ansible.builtin.cron:
name: "daily log rotation"
special_time: daily
job: "/opt/scripts/rotate-logs.sh"
Manage Cron for Specific Users
- name: Create cron for appuser
ansible.builtin.cron:
name: "app maintenance"
user: appuser
minute: "0"
hour: "4"
job: "/opt/app/maintenance.sh"
become: true
See also: Ansible Delete File & Remove File: file Module absent State Guide
Environment Variables in Cron
- name: Set cron environment variable
ansible.builtin.cron:
name: PATH
env: true
job: "/usr/local/bin:/usr/bin:/bin"
- name: Set MAILTO
ansible.builtin.cron:
name: MAILTO
env: true
job: "admin@example.com"
Remove Cron Jobs
- name: Remove old cron job
ansible.builtin.cron:
name: "old backup"
state: absent
- name: Remove cron environment variable
ansible.builtin.cron:
name: MAILTO
env: true
state: absent
Disable Without Removing
- name: Temporarily disable cron job
ansible.builtin.cron:
name: "daily backup"
minute: "0"
hour: "2"
job: "/opt/scripts/backup.sh"
disabled: true
Cron with Ansible Variables
- name: Create environment-specific cron
ansible.builtin.cron:
name: "{{ item.name }}"
minute: "{{ item.minute }}"
hour: "{{ item.hour }}"
job: "{{ item.job }}"
loop:
- { name: "backup db", minute: "0", hour: "2", job: "/opt/scripts/backup-{{ env }}.sh" }
- { name: "clean logs", minute: "30", hour: "3", job: "/opt/scripts/clean-logs.sh --env {{ env }}" }
Cron.d Files
# Create a file in /etc/cron.d/ instead of user crontab
- name: Create cron.d entry
ansible.builtin.cron:
name: "system backup"
cron_file: system-backup
user: root
minute: "0"
hour: "1"
job: "/opt/scripts/system-backup.sh"
Practical Example: Complete Server Maintenance
- name: Setup server maintenance crons
hosts: all
become: true
tasks:
- name: Daily log rotation
ansible.builtin.cron:
name: "rotate logs"
special_time: daily
job: "find /var/log/myapp -name '*.log' -mtime +7 -delete"
- name: Database backup (2 AM)
ansible.builtin.cron:
name: "db backup"
minute: "0"
hour: "2"
job: "pg_dump myapp | gzip > /backup/db-$(date +\%Y\%m\%d).sql.gz"
user: postgres
- name: SSL certificate renewal check (weekly)
ansible.builtin.cron:
name: "certbot renew"
special_time: weekly
job: "certbot renew --quiet --post-hook 'systemctl reload nginx'"
- name: Disk space alert (every 6 hours)
ansible.builtin.cron:
name: "disk alert"
minute: "0"
hour: "*/6"
job: "df -h / | awk 'NR==2 && $5+0 > 80 {print "DISK WARNING: " $5}' | mail -s 'Disk Alert' admin@example.com"
FAQ
How do I list existing cron jobs?
Run ansible all -m command -a "crontab -l" to see current user's crontab. For specific users: crontab -l -u username.
Why isn't my cron job running?
Common causes: wrong PATH (cron has minimal PATH), missing executable permissions, wrong user context, or output not redirected (mail delivery fails silently).
How do I ensure idempotency?
The name parameter is the unique key. Ansible uses it to find and update existing entries. Always use descriptive, unique names.
Create Cron Job
- name: Run backup every day at 3 AM
ansible.builtin.cron:
name: "daily backup"
minute: "0"
hour: "3"
job: "/opt/scripts/backup.sh >> /var/log/backup.log 2>&1"
become: true
Schedule Reference
# Every 5 minutes
- cron: { name: "health", minute: "*/5", job: "/opt/check.sh" }
# Hourly at :30
- cron: { name: "sync", minute: "30", job: "/opt/sync.sh" }
# Daily at midnight
- cron: { name: "cleanup", minute: "0", hour: "0", job: "/opt/clean.sh" }
# Weekly on Sunday at 2 AM
- cron: { name: "weekly", minute: "0", hour: "2", weekday: "0", job: "/opt/weekly.sh" }
# Monthly on 1st at 4 AM
- cron: { name: "monthly", minute: "0", hour: "4", day: "1", job: "/opt/monthly.sh" }
# Weekdays only at 9 AM
- cron: { name: "workday", minute: "0", hour: "9", weekday: "1-5", job: "/opt/report.sh" }
# Every 15 min during business hours
- cron: { name: "biz", minute: "*/15", hour: "9-17", weekday: "1-5", job: "/opt/monitor.sh" }
Special Time Strings
- cron:
name: "reboot task"
special_time: reboot
job: "/opt/startup.sh"
# Options: reboot, yearly, annually, monthly, weekly, daily, hourly
Remove Cron Job
- ansible.builtin.cron:
name: "old backup"
state: absent
Disable (Comment Out)
- ansible.builtin.cron:
name: "maintenance task"
disabled: true
minute: "0"
hour: "2"
job: "/opt/maintenance.sh"
Set Environment Variables
- ansible.builtin.cron:
name: MAILTO
env: true
job: admin@example.com
- ansible.builtin.cron:
name: PATH
env: true
job: "/usr/local/bin:/usr/bin:/bin"
- ansible.builtin.cron:
name: "app task"
job: "cd /opt/myapp && python3 task.py"
User-Specific Crontab
# Root crontab
- cron:
name: "system cleanup"
user: root
job: "/opt/cleanup.sh"
become: true
# Application user
- cron:
name: "app task"
user: appuser
job: "/opt/myapp/run-task.sh"
become: true
Deploy Multiple Cron Jobs
- ansible.builtin.cron:
name: "{{ item.name }}"
minute: "{{ item.minute | default('*') }}"
hour: "{{ item.hour | default('*') }}"
day: "{{ item.day | default('*') }}"
weekday: "{{ item.weekday | default('*') }}"
job: "{{ item.job }}"
user: "{{ item.user | default(omit) }}"
loop:
- { name: "backup", minute: "0", hour: "2", job: "/opt/backup.sh" }
- { name: "cleanup", minute: "30", hour: "3", job: "/opt/clean.sh" }
- { name: "monitor", minute: "*/10", job: "/opt/monitor.sh" }
become: true
Cron Module Parameters
| Parameter | Description |
|-----------|-------------|
| name | Description (used as identifier) |
| job | Command to run |
| minute | 0-59, */N, or ranges |
| hour | 0-23 |
| day | 1-31 |
| month | 1-12 |
| weekday | 0-6 (Sunday=0) |
| special_time | reboot, daily, weekly, etc. |
| user | Crontab owner |
| state | present/absent |
| disabled | Comment out the job |
| env | Set crontab environment variable |
| cron_file | Write to /etc/cron.d/ instead |
FAQ
How do I list current cron jobs?
- command: crontab -l
register: cron_list
changed_when: false
- debug: var=cron_list.stdout_lines
Why does my cron job fail but works manually?
Cron runs with a minimal environment. Set PATH explicitly or use absolute paths in your job command.
Can I use cron_file for system-level jobs?
- cron:
name: "system backup"
cron_file: myapp-backup
user: root
minute: "0"
hour: "2"
job: "/opt/backup.sh"
This writes to /etc/cron.d/myapp-backup instead of user crontab.
Create a Cron Job
- ansible.builtin.cron:
name: "Daily backup"
minute: "0"
hour: "2"
job: "/opt/scripts/backup.sh >> /var/log/backup.log 2>&1"
become: true
Special Time Strings
- cron:
name: "Every reboot"
special_time: reboot
job: "/opt/scripts/startup.sh"
# Options: reboot, hourly, daily, weekly, monthly, annually, yearly
- cron:
name: "Daily cleanup"
special_time: daily
job: "/opt/scripts/cleanup.sh"
Complex Schedule
# Every 15 minutes
- cron:
name: "Health check"
minute: "*/15"
job: "/opt/scripts/health.sh"
# Weekdays at 9 AM
- cron:
name: "Morning report"
minute: "0"
hour: "9"
weekday: "1-5"
job: "/opt/scripts/report.sh"
# First day of month
- cron:
name: "Monthly audit"
minute: "0"
hour: "3"
day: "1"
job: "/opt/scripts/audit.sh"
Remove a Cron Job
- cron:
name: "Old backup"
state: absent
become: true
Cron for Specific User
- cron:
name: "App health check"
user: appuser
minute: "*/5"
job: "/opt/myapp/healthcheck.sh"
become: true
Environment Variables
- cron:
name: PATH
env: true
job: "/usr/local/bin:/usr/bin"
become: true
- cron:
name: MAILTO
env: true
job: "admin@example.com"
become: true
Disable Without Removing
- cron:
name: "Maintenance job"
minute: "0"
hour: "4"
job: "/opt/scripts/maintenance.sh"
disabled: true
become: true
Multiple Cron Jobs
- cron:
name: "{{ item.name }}"
minute: "{{ item.minute }}"
hour: "{{ item.hour | default('*') }}"
job: "{{ item.job }}"
loop:
- { name: "Backup DB", minute: "0", hour: "2", job: "/opt/backup-db.sh" }
- { name: "Clean logs", minute: "30", hour: "3", job: "/opt/clean-logs.sh" }
- { name: "Health check", minute: "*/10", job: "/opt/health.sh" }
become: true
Cron File (Separate File)
# Creates /etc/cron.d/myapp instead of editing user crontab
- cron:
name: "App cleanup"
cron_file: myapp
user: appuser
minute: "0"
hour: "4"
job: "/opt/myapp/cleanup.sh"
become: true
FAQ
How to list existing cron jobs?
- command: crontab -l -u {{ username }}
register: crontab
changed_when: false
cron vs systemd timers?
Cron is simpler and universal. systemd timers offer better logging, dependency management, and missed-run handling. Use cron for simple schedules.
How to prevent overlapping runs?
Use flock: job: "flock -n /tmp/backup.lock /opt/backup.sh"
Related Articles
• managing Nginx via Ansible • using become for sudo in Ansible • reading environment variables in Ansible playbooks • the Ansible loops referenceAdvanced Cron Patterns
Run Every N Minutes
- name: Run health check every 5 minutes
ansible.builtin.cron:
name: "health check"
minute: "*/5"
job: "/opt/scripts/healthcheck.sh >> /var/log/healthcheck.log 2>&1"
Business Hours Only
- name: Run sync during business hours only
ansible.builtin.cron:
name: "business sync"
minute: "0"
hour: "9-17"
weekday: "1-5"
job: "/opt/scripts/sync.sh"
First Day of Every Month
- name: Monthly report
ansible.builtin.cron:
name: "monthly report"
minute: "0"
hour: "6"
day: "1"
job: "/opt/scripts/monthly-report.sh"
Multiple Specific Times
- name: Run at 8 AM, 12 PM, and 6 PM
ansible.builtin.cron:
name: "three times daily"
minute: "0"
hour: "8,12,18"
job: "/opt/scripts/check.sh"
Managing Cron Environment Variables
- name: Set PATH for cron jobs
ansible.builtin.cron:
name: PATH
env: true
job: "/usr/local/bin:/usr/bin:/bin"
- name: Set MAILTO for error notifications
ansible.builtin.cron:
name: MAILTO
env: true
job: "admin@example.com"
- name: Set SHELL
ansible.builtin.cron:
name: SHELL
env: true
job: "/bin/bash"
Cron Security Best Practices
Restrict Cron Access
- name: Allow only specific users to use cron
ansible.builtin.copy:
content: |
root
deploy
ansible
dest: /etc/cron.allow
mode: '0644'
- name: Remove cron.deny (cron.allow takes precedence)
ansible.builtin.file:
path: /etc/cron.deny
state: absent
Audit Existing Cron Jobs
- name: List all user crontabs
ansible.builtin.shell: |
for user in $(cut -f1 -d: /etc/passwd); do
crontab=$(crontab -l -u "$user" 2>/dev/null)
if [ -n "$crontab" ]; then
echo "=== $user ==="
echo "$crontab"
fi
done
register: all_crontabs
changed_when: false
- name: Show all cron jobs
ansible.builtin.debug:
var: all_crontabs.stdout_lines
Log All Cron Output
- name: Cron job with proper logging
ansible.builtin.cron:
name: "logged backup"
minute: "0"
hour: "2"
job: "/opt/backup.sh >> /var/log/backup.log 2>&1"
- name: Rotate cron logs
ansible.builtin.cron:
name: "rotate cron logs"
special_time: weekly
job: "find /var/log -name '*.log' -mtime +30 -delete"
Cron vs systemd Timers in Detail
| Feature | Cron | systemd Timer |
|---------|------|---------------|
| Simplicity | Simple, one line per job | Requires .service + .timer files |
| Logging | Manual (redirect stdout/stderr) | Automatic via journald |
| Missed runs | Skipped | Persistent=true catches up |
| Dependencies | None | Can depend on other units |
| Randomized delay | Not built-in | RandomizedDelaySec |
| Resource control | None | CPU/memory/IO limits |
| User support | Per-user crontabs | Per-user systemd units |
| Availability | All Unix/Linux | systemd-based distros only |
When to Use Cron
• Simple scheduled tasks • Cross-platform compatibility (CentOS, Ubuntu, Alpine, macOS) • Quick one-liners • Existing infrastructure with cron habitsWhen to Use systemd Timers
• Need logging integration • Must handle missed executions • Require resource limits • Complex dependency chains • Want randomized delays to avoid thundering herdTroubleshooting Cron Issues
Cron Job Not Running
- name: Check cron service is running
ansible.builtin.service:
name: "{{ 'cron' if ansible_os_family == 'Debian' else 'crond' }}"
state: started
enabled: true
- name: Check cron logs
ansible.builtin.shell: grep CRON /var/log/syslog | tail -20
register: cron_logs
changed_when: false
when: ansible_os_family == 'Debian'
Permission Denied
- name: Ensure script is executable
ansible.builtin.file:
path: /opt/scripts/backup.sh
mode: '0755'
owner: root
Environment Issues
Cron runs with a minimal environment. Common fix:
- name: Use full paths in cron job
ansible.builtin.cron:
name: "backup with full paths"
minute: "0"
hour: "2"
job: "/usr/bin/env PATH=/usr/local/bin:/usr/bin:/bin /opt/scripts/backup.sh"Category: troubleshooting