Does “31” in Cron Day-of-Month Field Always Represent Month End? Cron Job Edge Case Analysis


2 views

When setting up cron jobs, many developers assume that specifying "31" in the day-of-month field will automatically execute the job on the last day of every month. However, this is not how cron implementations actually work across Unix-like systems.

Most cron implementations follow these rules for the day-of-month field:

  • If a month has fewer than 31 days, the job won't run at all that month
  • The scheduler doesn't automatically adjust to the last valid day of the month
  • This behavior is consistent across vixie-cron, cronie, and other common implementations

Consider this cron entry that runs on the 31st of every month:

0 0 31 * * /path/to/monthly_report.sh

Here's what actually happens:

January: Runs on 31st
February: Doesn't run
March: Runs on 31st
April: Doesn't run
May: Runs on 31st
June: Doesn't run
July: Runs on 31st
August: Runs on 31st
September: Doesn't run
October: Runs on 31st
November: Doesn't run
December: Runs on 31st

For true end-of-month execution, consider these approaches:

Option 1: Using L (Linux-specific)

Some modern cron implementations support the 'L' specifier:

0 0 L * * /path/to/script.sh

Option 2: Multiple Day Specification

Target the valid last days of all months:

0 0 28-31 * * [ $(date +\%d -d tomorrow) = 01 ] && /path/to/script.sh

Option 3: First-Day-of-Next-Month Approach

Run at midnight on the 1st and check if yesterday was month-end:

0 0 1 * * [ $(date +\%d -d yesterday) = $(date +\%d -d "$(date +\%Y-\%m-01) + 1 month - 1 day") ] && /path/to/script.sh
  • The behavior might vary slightly between cron implementations
  • Some systems (like AWS Cron) handle this differently
  • Always test your cron jobs across month boundaries
  • Consider adding logging to verify execution dates

For production systems requiring precise end-of-month execution, consider:

# Run daily but only process on month-end
0 0 * * * [ $(date +\%d -d tomorrow) = 01 ] && /path/to/end_of_month_task.sh

When configuring cron jobs with 31 as the day-of-month parameter, many developers assume it will automatically adjust for shorter months. However, cron's behavior is more literal than intuitive:

# This will NOT run on Feb 28/29, Apr 30, etc.
0 0 31 * * /path/to/monthly_script.sh

Through testing various cron implementations (Vixie cron, systemd, etc.), we observe:

  • Jobs set to 31st only trigger when the month contains 31 days
  • No automatic fallback to last day exists in standard cron
  • The execution depends entirely on the system's cron daemon

For true month-end execution regardless of date, consider these approaches:

Option 1: Dual Cron Entries

# Regular 31-day months
0 0 31 * * [ $(date +\%m) -ne 2 ] && /path/to/script

# Special handling for February
0 0 28 2 * /path/to/script

Option 2: Next-Day Check

# Runs daily but only executes if tomorrow is month 1
0 0 * * * [ $(date -d tomorrow +\%d) -eq 1 ] && /path/to/script

Option 3: Using Modified Wrapper Script

#!/bin/bash
current_day=$(date +\%d)
last_day=$(date -d "$(date +\%Y\%m01) +1 month -1 day" +\%d)

if [[ "$current_day" == "$last_day" ]]; then
    # Your month-end logic here
fi

Implementation varies slightly across platforms:

System Handling of Invalid Dates
Vixie cron Silently skips
systemd timers May log warnings
Amazon EventBridge Allows L for last day