What Is the Ansible cron Module?

The Ansible cron module (ansible.builtin.cron) manages cron jobs on remote hosts — create, modify, and delete scheduled tasks without manually editing crontab files. It's idempotent: running the same task twice doesn't create duplicate entries.

Parameters

ParameterDefaultDescription
name(required)Description of the cron job (used as unique identifier)
job—Command to execute
statepresentpresent to create, absent to remove
minute*Minute (0-59)
hour*Hour (0-23)
day*Day of month (1-31)
month*Month (1-12)
weekday*Day of week (0-6, 0=Sunday)
special_time—reboot, hourly, daily, weekly, monthly, yearly
usercurrent userUser whose crontab to modify
cron_file—File in /etc/cron.d/ instead of user crontab
envfalseManage environment variable instead of job
disabledfalseComment out the job (keep but don't run)
backupfalseBackup crontab before modifying

Create a Cron Job

---
- name: Manage cron jobs
  hosts: all
  become: true
  tasks:
    # Run every day at 2:30 AM
    - name: Database backup
      ansible.builtin.cron:
        name: "Database backup"
        minute: "30"
        hour: "2"
        job: "/opt/scripts/backup-db.sh >> /var/log/backup.log 2>&1"

    # Run every 5 minutes
    - name: Health check
      ansible.builtin.cron:
        name: "App health check"
        minute: "*/5"
        job: "/opt/scripts/healthcheck.sh"

    # Run every Monday at 6 AM
    - name: Weekly report
      ansible.builtin.cron:
        name: "Weekly report"
        minute: "0"
        hour: "6"
        weekday: "1"
        job: "/opt/scripts/weekly-report.sh"

    # Run on the 1st of every month
    - name: Monthly cleanup
      ansible.builtin.cron:
        name: "Monthly cleanup"
        minute: "0"
        hour: "3"
        day: "1"
        job: "/opt/scripts/monthly-cleanup.sh"

Special Time Strings

# Run at reboot
- ansible.builtin.cron:
    name: "Start app on boot"
    special_time: reboot
    job: "/opt/myapp/start.sh"

# Run hourly
- ansible.builtin.cron:
    name: "Sync files"
    special_time: hourly
    job: "/opt/scripts/sync.sh"

# Run daily (midnight)
- ansible.builtin.cron:
    name: "Log rotation"
    special_time: daily
    job: "/usr/sbin/logrotate /etc/logrotate.conf"

# Run weekly (Sunday midnight)
- ansible.builtin.cron:
    name: "Full backup"
    special_time: weekly
    job: "/opt/scripts/full-backup.sh"

Cron Environment Variables

# Set PATH for cron jobs
- ansible.builtin.cron:
    name: PATH
    env: true
    job: "/usr/local/bin:/usr/bin:/bin"

# Set MAILTO
- ansible.builtin.cron:
    name: MAILTO
    env: true
    job: "admin@example.com"

# Set SHELL
- ansible.builtin.cron:
    name: SHELL
    env: true
    job: /bin/bash

# Then add the actual cron job
- ansible.builtin.cron:
    name: "My scheduled task"
    minute: "0"
    hour: "*/4"
    job: "/opt/scripts/task.sh"

System Cron Files (/etc/cron.d/)

# Create a system-wide cron file (requires user field)
- ansible.builtin.cron:
    name: "App monitoring"
    cron_file: myapp-monitoring
    user: appuser
    minute: "*/5"
    job: "/opt/myapp/monitor.sh"
# Creates /etc/cron.d/myapp-monitoring

# Remove a system cron file
- ansible.builtin.cron:
    name: "App monitoring"
    cron_file: myapp-monitoring
    user: appuser
    state: absent

Remove a Cron Job

# Remove by name
- ansible.builtin.cron:
    name: "Old backup job"
    state: absent

# Remove a system cron file
- ansible.builtin.cron:
    name: "Old monitoring"
    cron_file: old-monitoring
    state: absent

Disable Without Removing

# Disable (comments out the entry)
- ansible.builtin.cron:
    name: "Database backup"
    minute: "30"
    hour: "2"
    job: "/opt/scripts/backup-db.sh"
    disabled: true

# Re-enable
- ansible.builtin.cron:
    name: "Database backup"
    minute: "30"
    hour: "2"
    job: "/opt/scripts/backup-db.sh"
    disabled: false

Practical Patterns

Application Server Cron Jobs

---
- name: Configure application cron jobs
  hosts: appservers
  become: true
  tasks:
    - name: Set cron environment
      ansible.builtin.cron:
        name: "{{ item.name }}"
        env: true
        job: "{{ item.value }}"
        user: appuser
      loop:
        - { name: PATH, value: "/opt/myapp/bin:/usr/local/bin:/usr/bin:/bin" }
        - { name: APP_ENV, value: "production" }

    - name: Cache cleanup every 15 minutes
      ansible.builtin.cron:
        name: "Cache cleanup"
        minute: "*/15"
        job: "/opt/myapp/bin/clean-cache"
        user: appuser

    - name: Session cleanup daily at 3 AM
      ansible.builtin.cron:
        name: "Session cleanup"
        minute: "0"
        hour: "3"
        job: "/opt/myapp/bin/clean-sessions --older-than 24h"
        user: appuser

    - name: Send metrics every minute
      ansible.builtin.cron:
        name: "Send metrics"
        job: "/opt/myapp/bin/send-metrics >> /dev/null 2>&1"
        user: appuser

Database Maintenance

---
- name: Database cron jobs
  hosts: dbservers
  become: true
  tasks:
    - name: Incremental backup every 6 hours
      ansible.builtin.cron:
        name: "DB incremental backup"
        minute: "0"
        hour: "*/6"
        job: "pg_dump mydb | gzip > /backup/mydb-$(date +\\%Y\\%m\\%d-\\%H\\%M).sql.gz"
        user: postgres

    - name: Full backup Sunday 1 AM
      ansible.builtin.cron:
        name: "DB full backup"
        minute: "0"
        hour: "1"
        weekday: "0"
        job: "/opt/scripts/full-db-backup.sh"
        user: postgres

    - name: Vacuum daily
      ansible.builtin.cron:
        name: "DB vacuum"
        special_time: daily
        job: "psql -c 'VACUUM ANALYZE;' mydb"
        user: postgres

    - name: Remove old backups
      ansible.builtin.cron:
        name: "Purge old backups"
        minute: "0"
        hour: "4"
        job: "find /backup -name '*.sql.gz' -mtime +30 -delete"
        user: root

Variable-Driven Cron Jobs

---
- name: Deploy cron jobs from variable
  hosts: all
  become: true
  vars:
    cron_jobs:
      - name: "Log cleanup"
        minute: "0"
        hour: "2"
        job: "find /var/log/myapp -name '*.log' -mtime +7 -delete"
      - name: "Certificate check"
        minute: "0"
        hour: "8"
        weekday: "1"
        job: "/opt/scripts/check-certs.sh"
      - name: "Status report"
        special_time: hourly
        job: "/opt/scripts/status.sh"

  tasks:
    - name: Create all cron jobs
      ansible.builtin.cron:
        name: "{{ item.name }}"
        minute: "{{ item.minute | default(omit) }}"
        hour: "{{ item.hour | default(omit) }}"
        day: "{{ item.day | default(omit) }}"
        weekday: "{{ item.weekday | default(omit) }}"
        special_time: "{{ item.special_time | default(omit) }}"
        job: "{{ item.job }}"
      loop: "{{ cron_jobs }}"

Cron Time Expression Cheatsheet

Scheduleminutehourdaymonthweekday
Every minute*****
Every 5 min*/5****
Every hour0****
Daily midnight00***
Daily 2:30 AM302***
Weekdays 9 AM09**1-5
Mon & Thu 6 PM018**1,4
1st of month001**
Every 15 min*/15****

Troubleshooting

Cron Job Not Running

Check if the crontab was created:

crontab -l              # User crontab
cat /etc/cron.d/myfile  # System crontab

Common issues:

  • Missing PATH — set it via cron env variable
  • Output not redirected — add >> /var/log/job.log 2>&1
  • Wrong user — check user parameter

Duplicate Entries

The name parameter is the unique key. If you change the name, a new entry is created (old one stays). Remove the old one explicitly:

- ansible.builtin.cron:
    name: "Old job name"
    state: absent

- ansible.builtin.cron:
    name: "New job name"
    job: "/opt/scripts/task.sh"

Conclusion

ansible.builtin.cron manages cron jobs idempotently — the name field is the unique identifier, so running the same task twice won't create duplicates. Use time fields (minute, hour, day, month, weekday) for precise scheduling, or special_time for common intervals. Set cron_file for system-wide cron in /etc/cron.d/. Always redirect output in cron jobs (>> /var/log/job.log 2>&1) and set the PATH environment variable to avoid "command not found" issues.