Regex and cron

Cron Expression for the Last Day of the Month

At 00:00 on the last day of each month, whether that is the 28th, 29th, 30th or 31st. Copy it, check the next runs in your time zone and grab a snippet for your platform.

  • Free foreverNo trial, no paywall, no usage limits.
  • No sign-upOpen the page and start. We never ask for an email to use a tool.
  • PrivateEverything runs in your browser. Your input is never uploaded or stored on our servers.
  • Works offlineOnce the page has loaded, the tool keeps working without a connection.

Cron expression

0 0 28-31 * *
Customize
0minuteMinute: at minute 0 (on the hour)0hourHour: at hour 0 (midnight)28-31day (month)Day of month: days 28 through 31 of the month*monthMonth: every month*day (week)Day of week: every day of the week

At 00:00 on the last day of each month, whether that is the 28th, 29th, 30th or 31st.

Standard cron has no “last day” value, so this runs on days 28–31 with a date check. The run times below apply that check.

Time zone
Run times, the week view and the snippets use this zone. It starts as your device's zone.
Show times as 9:00 AM instead of 09:00.

Next runs

Each run in your selected time zone, with the same moment in UTC. Daylight saving changes follow cronie, the cron on most Linux servers.
Copy run times (ISO 8601)

Next 7 days

Each square is one hour of local time in your selected zone. Darker squares have more runs. Daylight saving changes are not drawn.

Use it on your platform

Copy-ready config for each scheduler. Dialects differ: some only run in UTC, some can't do seconds, Cloudflare and AWS number weekdays from 1 = Sunday. The snippet handles the conversion and says what changed.
A line for crontab -e on Linux or macOS (Vixie cron, cronie).on.schedule for a workflow, with the timezone field GitHub added in March 2026 (UTC when omitted).A CronJob manifest with the timeZone field (Kubernetes 1.27+).Cron Triggers in wrangler.toml for a Worker. Runs in UTC and numbers weekdays from 1 = Sunday.crons in vercel.json plus the route it calls. Runs in UTC.EventBridge Scheduler (any time zone, recommended by AWS) and a legacy EventBridge scheduled rule (UTC).A .timer unit with OnCalendar=, the systemd replacement for cron.
Runs as writtenRuns in your time zone
crontab -e
Copy crontab -eDownload my-job.crontab
# ┌───────────── minute (0–59)
# │ ┌─────────── hour (0–23)
# │ │ ┌───────── day of month (1–31)
# │ │ │ ┌─────── month (1–12)
# │ │ │ │ ┌───── day of week (0–6, Sunday = 0)
# │ │ │ │ │
# * * * * *  command
CRON_TZ=UTC
0 0 28-31 * *  [ "$(TZ=UTC date -d tomorrow +\%d)" = 01 ] && /path/to/my-job.sh >> /var/log/my-job.log 2>&1
  • Cron has no last-day-of-month value, so the job starts on days 28–31 and the date check lets only the last one through. date -d tomorrow is GNU date; on macOS use date -v+1d +\%d.
  • The check sets TZ itself: CRON_TZ only decides when cron starts the job, not the time zone the command sees.
  • Inside a crontab line % starts a new line of input, so it must be escaped as \%.
  • CRON_TZ=UTC is honored by cronie (Fedora, RHEL, Arch). Other cron daemons ignore it and use the server's time zone, so check timedatectl first.
  • Edit with crontab -e. Cron starts the next run even if the last one is still going; wrap the command in flock -n /tmp/my-job.lock to prevent overlaps.

Need scheduled jobs, queues and monitoring that never miss a run? Talk to our team

Quick answer

Standard cron has no value for the last day of the month, so the usual expression is 0 0 28-31 * * with a guard in the command that only continues when tomorrow is the 1st. Cron starts the job on every candidate day from the 28th to the 31st, and the guard lets only the real last day through. Cloudflare Workers, AWS EventBridge and Quartz accept L for the last day instead.

  • Live next runs
  • Any time zone
  • Field-by-field breakdown
  • Seven platform snippets
  • Daylight saving aware

How to use this schedule

  1. 1Copy 0 0 28-31 * * with the Copy expression button.
  2. 2Check Next runs in your time zone to confirm it fires when you expect, including around daylight saving changes.
  3. 3Pick your platform under Use it on your platform and copy the ready-made crontab line, workflow, manifest or timer.
  4. 4Need a different time or day? Click Customize to open it in the cron expression generator and change any field.

How do I run a cron job on the last day of the month?

Months end on the 28th, 29th, 30th or 31st, and the five standard cron fields can't say "whichever comes last". 0 0 31 * * skips five months a year, and 0 0 L * * is rejected by Linux crontab. The portable answer is to schedule every possible last day and filter in the command:

# crontab: last day of the month at 00:00 (GNU date; escape % as \% in crontab)
0 0 28-31 * * [ "$(date -d tomorrow +\%d)" = 01 ] && /path/to/job

date -d tomorrow +%d prints tomorrow's day of the month. Only on the real last day is that 01, so the && lets the job run exactly once a month. On macOS and BSD, date uses a different flag: date -v+1d +%d. Inside a script file, write a plain %d; the backslash is only needed in the crontab line itself, where an unescaped % turns the rest of the line into standard input.

Which schedulers support L for the last day?

Several schedulers extend cron with L (last) in the day-of-month field, so no guard is needed:

SchedulerLast day of the month at 00:00
Cloudflare Workers Cron Triggers0 0 L * *
AWS EventBridgecron(0 0 L * ? *)
Quartz and Spring (seconds first)0 0 0 L * ?
systemd timerOnCalendar=*-*~01 00:00:00
Linux crontab, Kubernetes, GitHub Actions, VercelNot supported: use 28-31 plus a guard

If you control the job, there is an even simpler design: run at 00:00 on the 1st and process the month that just ended. The data is complete, there is no guard to get wrong, and the schedule is the plain 0 0 1 * * used for every month, which every scheduler understands.

What does each field in 0 0 28-31 * * mean?

FieldValueAllowedMeaning
Minute00–59at minute 0 (on the hour)
Hour00–23at hour 0 (midnight)
Day of month28-311–31days 28 through 31 of the month
Month*1–12, JAN–DECevery month
Day of week*0–7 (0 and 7 are Sunday), SUN–SATevery day of the week

Read together: At 00:00 on the last day of each month, whether that is the 28th, 29th, 30th or 31st.

The day-of-month range 28-31 only narrows the candidates; a date check in the command (or L on platforms that support it) picks the real last day.

How do I adjust 0 0 28-31 * * to my own schedule?

Small edits to 0 0 28-31 * * cover most real-world needs:

ExpressionWhat it does
0 0 L * *Last day of the month on Cloudflare Workers (not valid in crontab)
0 0 1 * *The 1st at midnight, processing the month that just ended
59 23 28-31 * *With the same guard, runs in the final minute of the month
0 0 31 * *Not a last-day schedule: only the seven months that have a 31st
0 0 28,29 2 *The candidate last days of February

Need something else? Click Customize at the top of the page to open this schedule in the cron expression generator and change any field.

Related schedules: First Monday of the month · Every month · Every quarter · Every year

Browse all schedules in the cron schedule library.

How do I run 0 0 28-31 * * in crontab, GitHub Actions, Kubernetes and the cloud?

PlatformWrite it asTime zone
Linux crontab0 0 28-31 * * + date checkServer's time zone; CRON_TZ= in cronie
GitHub Actions- cron: "0 0 28-31 * *" + date checkUTC by default; add timezone: (IANA name)
Kubernetes CronJobschedule: "0 0 28-31 * *" + date checkSet timeZone: (1.27+)
Cloudflare Workers0 0 L * *UTC only; supports L and # (Monday is 2)
Vercel Cron Jobs0 0 28-31 * * + date checkUTC only; more than once a day needs Pro (Hobby: once a day)
AWS EventBridge Schedulercron(0 0 L * ? *)Takes a time zone; legacy scheduled rules are UTC only
systemd timerOnCalendar=*-*~01 00:00:00Add a zone after the time, e.g. Europe/London

In a crontab (crontab -e), the full line looks like this:

# Set TZ to the zone your calendar runs in; CRON_TZ doesn't reach the command.
0 0 28-31 * *  [ "$(TZ=UTC date -d tomorrow +\%d)" = 01 ] && /path/to/job.sh

The live panel at the top of the page builds complete, copy-ready files for each platform (workflow, CronJob manifest, wrangler.toml, vercel.json, AWS CLI and systemd units) and can shift the schedule to UTC for platforms that need it.

What can go wrong with 0 0 28-31 * *?

  • In a crontab line, % must be written \%. Without the backslash, cron cuts the command at the first % and the guard silently breaks.
  • date -d tomorrow is GNU date syntax. macOS and BSD use date -v+1d, and minimal container images may ship a different date, so test the guard where the job actually runs.
  • The guard uses the server's time zone. At 00:00 UTC on the 31st it is still the 30th in the Americas, so set TZ= for the date command if your month is defined in another zone.
  • At 00:00 on the last day, that day has only just started. If the job should see the whole month, run on the 1st instead, or use 59 23 with the same guard.
  • On UTC-only platforms (Cloudflare Workers, Vercel, legacy EventBridge rules), the local time this runs at moves by an hour when daylight saving time starts or ends. Use GitHub's timezone:, Kubernetes timeZone, EventBridge Scheduler or cronie's CRON_TZ to pin it to local time.

Frequently asked questions

Linux crontab (Vixie cron and cronie) does not, and neither do Kubernetes, GitHub Actions or Vercel. Cloudflare Workers, AWS EventBridge, Quartz and Spring accept L in the day-of-month field.
Only seven months have a 31st, so the job would never run at the end of February, April, June, September or November. Cron skips months where the day does not exist instead of moving the run.
Cron starts the job on the 28th, 29th, 30th and 31st. The check prints tomorrow's day of the month and only lets the command continue when it is 01, which is true on exactly one of those days each month.
Use OnCalendar=*-*~01 00:00:00. The tilde replaces the dash before the day and counts back from the end of the month, so ~01 is the last day. Preview it with systemd-analyze calendar before enabling the timer.
Yes. Run the job at 00:00 on the 1st and have it process the previous month. The month's data is complete, and 0 0 1 * * works on every scheduler.

Last updated by the Playxoft engineering team.

From the engineers who built this tool

Need scheduled jobs that never miss a run?

Cron is the easy part. Retries, overlapping runs, time zones, queues and alerts when a job silently stops are where schedules break. We build background job systems you can trust, and we will review your setup for free.

More free tools

See all tools

Regex Tester

Test regex live with highlighted matches, plain-English explanations and code export.

Open tool

Cron Expression Generator

New

Build and explain cron schedules, see the next runs in any time zone, copy platform snippets.

Open tool

SQL Formatter

Beautify or minify SQL in 12 dialects, with keyword case, indent and line:column errors.

Open tool

cURL to Python Converter

New

Convert cURL commands into Python requests or httpx code with params, json, files and auth.

Open tool

cURL to Fetch Converter

New

Turn any cURL command into JavaScript fetch() code with headers, JSON, forms and auth.

Open tool

cURL to Axios Converter

New

Convert cURL commands into an Axios request config with params, data, auth and timeouts.

Open tool