May 27, 2026

Cron job last day of month: 3 reliable approaches

Reading time :  
6
 min
Rebecca Pearson
Rebecca Pearson

Cron job last day of month: 3 reliable approaches

Standard cron has no built-in expression for "last day of the month." The five-field format — minute, hour, day, month, weekday — lets you specify day 28, 29, 30, or 31, but never "whatever the last day happens to be." This gap has tripped up sysadmins since cron first shipped in Version 7 Unix in 1975. If you need a cron job last day of month, you have three reliable options: a conditional wrapper script, a date-check inside the job, or a serverless scheduler that understands calendar logic natively.

A 2024 Linux Foundation report found that 90% of public cloud workloads run on Linux — and most of those use cron or a cron-like scheduler. Getting month-end scheduling right matters for invoice generation, report rollups, and compliance snapshots. CodeWords offers a fourth path: describe your schedule to Cody and let the platform handle the calendar math.

Unlike generic AI automation posts, this guide shows real CodeWords workflows — not just theory.

TL;DR

  • Standard cron syntax cannot express "last day of month" directly — you need a wrapper or a smarter scheduler
  • The most portable solution is a shell script that checks if tomorrow's day-of-month is 01
  • CodeWords serverless scheduling supports natural-language schedules like "last day of every month at 9 PM"

Why can't standard cron handle the last day of the month?

The cron time specification uses five fields, each with a fixed range: minutes (0–59), hours (0–23), day of month (1–31), month (1–12), and day of week (0–7). There's no wildcard or keyword for "last." You can write 0 0 31 * *, but that only fires in months with 31 days — January, March, May, July, August, October, December. February, April, June, September, and November get skipped entirely.

Think of cron's calendar awareness like a clock with no month hand. It knows what day it is, but it doesn't know what day the month ends. You need external logic to bridge that gap — a small piece of code that asks "is today the last day?" before running the actual job.

This isn't a flaw in cron's design. Cron was built for simplicity and predictability. Complex scheduling was always intended to live in the job itself or in a higher-level orchestrator. According to the POSIX specification (IEEE Std 1003.1-2024), cron's day-of-month field is intentionally limited to numeric values and ranges.

How do you write a shell script that runs on the last day?

The cleanest approach uses date to check if tomorrow is the first of the next month:

#!/bin/bash
# Exit unless today is the last day of the month
[ "$(date -d tomorrow +%d)" = "01" ] && /path/to/your/actual-script.sh

On macOS (BSD date), the syntax differs slightly:

[ "$(date -v+1d +%d)" = "01" ] && /path/to/your/actual-script.sh

Schedule this wrapper to run every day at your desired time:

0 21 28-31 * * /path/to/last-day-check.sh

Running on days 28–31 narrows the window. The wrapper exits silently on non-last days, and only executes the real script when tomorrow == 01. This approach has zero external dependencies and works on any POSIX-compliant system.

For production environments, add logging so you can verify the job actually ran. Pipe output to a log file or send a notification via your team's Slack channel. Silent cron jobs are a debugging nightmare.

What about using a conditional inside the job itself?

If you'd rather keep everything in one script — no wrapper — embed the date check at the top:

import datetime

today = datetime.date.today()
tomorrow = today + datetime.timedelta(days=1)
if tomorrow.day != 1:
    exit(0)

# Your month-end logic here
print("Running month-end report...")

This Python version is portable across operating systems and avoids the date command syntax differences between GNU and BSD. Schedule the script to run daily with 0 21 * * * and let the conditional handle the rest.

The trade-off: your cron job fires every day, but only does meaningful work on the last day. For lightweight scripts this is fine. For resource-intensive jobs, the daily trigger wastes a process spawn. In those cases, restrict cron to days 28–31 as shown above.

How does CodeWords handle month-end scheduling?

CodeWords sidesteps cron's limitations entirely. Its serverless scheduler parses natural-language expressions and converts them to the correct trigger logic:

Tell Cody: "Run this workflow on the last day of every month at 9 PM Eastern."

CodeWords generates a scheduled FastAPI service with calendar-aware triggering. Under the hood, it uses Python's calendar.monthrange() to determine the last day dynamically. No shell wrappers. No daily dummy runs.

This matters for workflow automation patterns like:

  • Invoice generation — pull billable hours from Google Sheets and generate PDFs on the last day
  • Database snapshots — export monthly aggregates from MySQL for compliance
  • Report distribution — compile analytics and email stakeholders via Gmail integration

CodeWords persists state in Redis, so if your month-end job depends on data accumulated throughout the month, it can read from a running tally rather than reprocessing everything at once. Check CodeWords pricing for scheduled execution quotas.

What if you need the last business day, not the last calendar day?

Many finance and accounting workflows require the last business day — skipping weekends and optionally holidays. Standard cron definitely can't handle this. Your options:

  • Python's numpy.busday_offset — computes the last business day of any month in one line. Requires NumPy.
  • Custom holiday calendar — maintain a list of company holidays and subtract from the last calendar day until you land on a workday.
  • CodeWords natural language — tell Cody "last business day of each month, excluding US federal holidays." The platform maintains holiday calendars for major regions.
import numpy as np
from datetime import date

last_bday = np.busday_offset(date(2026, 6, 1), -1, roll='backward')

Competitors like n8n and Pipedream support cron triggers but don't offer last-business-day logic natively. Zapier supports monthly schedules but requires a workaround for variable month lengths. CodeWords handles both calendar-aware and business-day scheduling through Cody's conversational interface.

Frequently asked questions

Does 0 0 L * * work in cron?

No. The L modifier is a Quartz scheduler extension (used in Java-based systems like Jenkins). Standard POSIX cron does not support it.

Can I use @monthly in crontab?

@monthly is equivalent to 0 0 1 * * — it runs on the first day of each month, not the last. It won't solve this problem.

How do I test my last-day-of-month cron job?

Use faketime on Linux to simulate month-end dates: faketime '2026-02-28 21:00:00' /path/to/script.sh. This lets you verify behavior for February, short months, and leap years without waiting.

Does this work for leap years?

Yes. The date -d tomorrow and datetime.timedelta(days=1) approaches both account for leap years automatically. February 29 in a leap year correctly returns tomorrow.day == 1 on the 29th.

Beyond the cron workaround

Scheduling a cron job last day of month has been a rite of passage for engineers since the 1970s. The workarounds are well-understood, but they're still workarounds — duct tape over a limitation in cron's original design. Serverless schedulers that understand calendar semantics eliminate the duct tape entirely, freeing you to focus on what the job does rather than when it fires.

The implication: as workflow automation moves toward natural-language configuration, the gap between "describe what you want" and "it runs correctly" keeps shrinking.

Build your month-end workflow on CodeWords — tell Cody the schedule and let the platform handle the calendar.

Contents
Ready to try CodeWords?
Get started free
Sign in
Sign in