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.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 Ansibleusing become for sudo in Ansiblereading environment variables in Ansible playbooksthe Ansible loops reference

Advanced 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 habits

When to Use systemd Timers

• Need logging integration • Must handle missed executions • Require resource limits • Complex dependency chains • Want randomized delays to avoid thundering herd

Troubleshooting 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

Browse all Ansible tutorials · AnsiblePilot Home