HTTPfy

Last day of every month

At 00:00 on the last day of every month (28th, 29th, 30th or 31st). Standard cron has no 'last day' value, so the line fires on days 28-31 and a guard in the command lets only the last one through.

12runs in the next 365 days
28 days / 31 daysshortest / longest gap between runs
00:00next run, Mon 30 Nov UTC

What it is used for

Month-end closing, billing and archiving that must happen before the new month starts.

Field by field

FieldValueMatches
Minute00
Hour00
Day of month28-3128, 29, 30, 31
Month*every value
Day of week*every value

Because cron cannot express this directly, the crontab line carries a guard that skips the unwanted days:

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

Next 10 run times

Computed when this page loaded (2026-11-11 02:24 UTC), assuming the scheduler runs in UTC. Only the days that pass the guard are listed.

#UTCYour local time
1Mon 2026-11-30 00:00
2Thu 2026-12-31 00:00
3Sun 2027-01-31 00:00
4Sun 2027-02-28 00:00
5Wed 2027-03-31 00:00
6Fri 2027-04-30 00:00
7Mon 2027-05-31 00:00
8Wed 2027-06-30 00:00
9Sat 2027-07-31 00:00
10Tue 2027-08-31 00:00

Things to watch with this schedule

  • The guard uses GNU date (Linux). On macOS or BSD use date -v+1d +%d. Inside a crontab every % must be escaped as \%, otherwise cron cuts the command at the % and feeds the rest to stdin.
  • Quartz, AWS EventBridge and several cron libraries accept L (last day) in the day-of-month field. Vixie cron (Debian, Ubuntu) and cronie (RHEL, Fedora) do not, so the guard is the portable form.

Kubernetes CronJob

Kubernetes uses the same five fields. Set spec.timeZone (stable since Kubernetes 1.27), otherwise the schedule follows the time zone of kube-controller-manager.

apiVersion: batch/v1
kind: CronJob
metadata:
  name: last-day-of-every-month
spec:
  schedule: "0 0 28-31 * *"
  timeZone: "Etc/UTC"
  concurrencyPolicy: Forbid
  startingDeadlineSeconds: 300
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: job
              image: debian:bookworm-slim
              command: ["/bin/sh", "-c", "if [ \"$(date -d tomorrow +%d)\" = \"01\" ]; then /app/run-job.sh; fi"]

GitHub Actions

Workflow schedules use five-field cron in UTC and run only from the default branch. Runs at minute 0 are the most likely to be delayed or dropped when GitHub is busy; an odd minute such as 7 or 23 starts more reliably.

on:
  schedule:
    - cron: '0 0 28-31 * *'   # UTC

jobs:
  run:
    runs-on: ubuntu-latest
    steps:
      - name: Only continue on the intended day
        id: gate
        run: [ "$(date -d tomorrow +%d)" = "01" ] && echo "go=1" >> "$GITHUB_OUTPUT" || true
      - run: ./run-job.sh
        if: steps.gate.outputs.go == '1'

systemd timer

The equivalent OnCalendar= value is *-*~01 00:00:00. Check it with systemd-analyze calendar '*-*~01 00:00:00', which prints the next elapse time. Persistent=true runs a missed job after downtime, which classic cron never does.

[Unit]
Description=Last day of every month

[Timer]
OnCalendar=*-*~01 00:00:00
Persistent=true

[Install]
WantedBy=timers.target

AWS EventBridge

EventBridge rules take six fields (minute, hour, day-of-month, month, day-of-week, year) in UTC; EventBridge Scheduler can use a time zone. One of day-of-month and day-of-week must be ?, and day-of-week runs 1-7 from Sunday, so names are used here.

cron(0 0 L * ? *)

Quartz Scheduler (Java)

Quartz adds a leading seconds field and needs ? in either day field; days of the week are 1 (SUN) to 7 (SAT).

0 0 0 L * ?

Spring's @Scheduled(cron = ...) also uses six fields, but numbers days of the week 0-7 with Sunday as 0 or 7, like Unix cron; the names shown above work in both.

Related schedules