Cron expression to human readable, in one paste
A cron expression packs a whole schedule into a handful of characters, which is exactly why mistakes slip through. A job meant to run on weekdays at 9 AM quietly runs every minute of the 9 o'clock hour instead, or a backup that should run on the first of the month skips every month that is too short. You only find out when something fails, or when nothing runs at all. Turning a cron expression to human readable text before you deploy takes seconds and catches most of these mistakes.
This explainer reads the expression the way a scheduler would and tells you the result in words, for example “Every 15 minutes, between 9:00 AM and 5:59 PM, on Monday through Friday.” Under the sentence you get each field on its own, so you can see which part produced which effect, and the next five run times, so the schedule is checked against a real calendar rather than your memory of the syntax. Like our other free developer tools, it runs entirely in your browser.
How to read the five fields of a cron expression
Standard crontab uses five fields separated by spaces: minute (0 to 59), hour (0 to 23), day of month (1 to 31), month (1 to 12 or JAN to DEC) and day of week (0 to 7, where both 0 and 7 mean Sunday, or SUN to SAT). Reading left to right goes from the smallest unit to the largest, and the last field, the day of week, is checked alongside the day of month rather than after it.
Four symbols cover most schedules. An asterisk means every value. A comma makes a list, so 9,17 in the hour field means 9 AM and 5 PM. A hyphen makes a range, so 1-5 in the day-of-week field is Monday through Friday. A slash sets a step, so */15 in the minute field means every 15 minutes. They combine: */15 9-17 * * 1-5 runs every 15 minutes during the working day from Monday to Friday, and the tool will read it back to you with the exact first and last minute.
Standard crontab, Quartz, Spring and AWS EventBridge: what changes
“Cron” is not one format. Standard crontab has five fields. Quartz, the Java scheduler, has six or seven: a seconds field comes first and an optional year comes last. Spring uses six fields with seconds first. AWS EventBridge uses six fields too, but with no seconds and a year at the end, and the whole expression is wrapped as cron(...). The same six-field string can therefore mean three different things depending on where it is pasted, which is why this tool lets you choose the format instead of guessing silently.
The day-of-week field is the classic trap. In Quartz and AWS EventBridge it runs from 1 to 7 with Sunday as 1. Standard crontab and Spring count from 0 for Sunday (7 is accepted too). Spring's own documentation points this out: it follows common cron conventions, while Quartz deviates with 1 to 7 for Sunday to Saturday. A job written as 0 0 9 ? * 1 means Sunday in Quartz, and the same digit means Monday in standard crontab. Using day names such as MON-FRI sidesteps the problem on every platform that accepts them.
Quartz, Spring and AWS also add a question mark and a few special characters. A question mark in the day-of-month or day-of-week field means “no specific value”, and Quartz and AWS require it in exactly one of the two. L means the last day of the month, or with a weekday the last such weekday (6L is the last Friday). W finds the weekday nearest a date (15W), and # picks the nth weekday of the month (6#3 is the third Friday). Standard crontab has none of these.
The “Friday the 13th” trap: day of month and day of week together
Set both the day of month and the day of week in standard crontab and the job does not run only on Friday the 13th. When neither field starts with an asterisk, cron runs the job when either field matches, so 0 0 13 * 5 fires on every 13th and on every Friday. The result is a job that runs far more often than intended.
If you really need both conditions, schedule the job for the days you want to allow, such as every Friday, and let the script itself check the date and exit early. Quartz and AWS EventBridge avoid the confusion by refusing to accept two restricted day fields at all: one of them must be a question mark. The explainer warns you whenever the either-or rule applies. If an AI coding assistant writes your scheduled jobs, put this rule in its instructions too; the CLAUDE.md and AGENTS.md generator makes that rules file in a minute.
Why */7 does not mean every 7 minutes
A step counts from the start of the field's cycle and then starts again, so it only gives even gaps when the step divides the cycle. */5 in the minute field runs at 0, 5, 10 and so on up to 55, then at 0 again, always 5 minutes apart. */7 runs at 0, 7, 14 and so on up to 56, and the next run is at minute 0 of the following hour, only 4 minutes later. Hours work the same way: */5 in the hour field runs at 0, 5, 10, 15 and 20, then again at 0, four hours after the last run.
The tool points this out as soon as a step does not divide 60 (minutes and seconds), 24 (hours) or 12 (months). For a true gap that does not fit the cycle, such as every 7 minutes around the clock, a cron expression is the wrong tool: use a scheduler that supports an interval, or run the job every minute and let it decide whether enough time has passed.
Day 31, February 29 and schedules that skip months
A cron expression never moves a date for you. 0 0 31 * * runs only in the seven months that have a 31st, and the job is simply skipped in February, April, June, September and November. 0 0 29 2 * runs only in leap years. 0 0 30 2 * never runs at all, which the tool reports instead of showing an empty list.
For “the last day of every month” in Quartz, Spring or AWS EventBridge, use L in the day-of-month field. Standard crontab has no L, so the usual workaround is to run on the 28th to the 31st (0 0 28-31 * *) and have the script continue only when tomorrow is the first of the month. The next-run list shows you the real dates, which is the quickest way to see that a schedule behaves as intended in February.
Time zones, UTC and daylight saving
A cron expression has no time zone of its own. The scheduler that runs it decides, and many hosted schedulers use UTC. Vercel's documentation says its cron jobs always run in UTC, AWS EventBridge's own examples are written in UTC, and GitHub Actions scheduled workflows run in UTC by default. A job set for 9 AM on a US East Coast laptop and deployed to a UTC server will therefore run at 9 AM UTC, which is 5 AM or 4 AM Eastern depending on the time of year.
That is why the next-run list has a time zone menu. It starts in US Eastern time; pick Central, Mountain or Pacific to see what people in those zones will experience, and UTC to see what the server will do. Local-time schedules can also shift when clocks change: a run set for a time that does not exist on the day the clocks spring forward may be skipped or moved, depending on the scheduler. Expiry times work the same way: a token's expiry is a single point in time that looks different in each zone, and the JWT decoder shows it with a live countdown.
Common schedules, ready to copy
Every 5 minutes is */5 * * * *. Every hour on the hour is 0 * * * *. Every day at midnight is 0 0 * * *, and every weekday at 9 AM is 0 9 * * 1-5. Twice a day, at 9 AM and 5 PM, is 0 9,17 * * *. The first of every month at midnight is 0 0 1 * *, and every Monday at 8:30 AM is 30 8 * * 1. The example buttons above load these, and the builder can produce them without any syntax at all.
Frequency has a floor. Standard crontab cannot run more often than once a minute. Quartz and Spring add a seconds field for faster jobs. GitHub's documentation says the shortest interval for scheduled workflows is once every 5 minutes, and AWS EventBridge says cron expressions that lead to rates faster than one minute are not supported. Frequency also has a price: a job that runs every 15 minutes runs 96 times a day, or 2,880 times in a 30-day month. If each run calls a paid AI model, the AI API cost calculator turns that number into a monthly bill, and our post on the Gemini API price increase is a reminder that those prices move.
Cron on GitHub Actions, Vercel, Kubernetes and Deno
GitHub Actions uses POSIX cron syntax in the schedule trigger. According to GitHub's documentation, scheduled workflows run on the default branch, can be delayed when load is high, and in a public repository are disabled automatically after 60 days without repository activity. Vercel Cron Jobs take a five-field expression in vercel.json; its documentation says day names such as MON and month names such as JAN are not supported, that day of month and day of week cannot both be set, and that the time zone is always UTC. Kubernetes CronJobs also use the five-field syntax.
Vercel triggers a cron job with an HTTP GET request to a path in your project, so on a Next.js app it is worth knowing which routes your middleware covers; the middleware matcher tester shows that for any path. Deno has its own scheduler, Deno.cron(), which matters if you are planning a move: our guide to the Deno Deploy shutdown date lists what to check for scheduled jobs, and the post asking is Deno dead explains the wider picture. Whichever platform you use, paste the expression here first and compare the sentence with what you meant.
Scheduled jobs usually need secrets and API calls
Most cron jobs call something: an API, a database, a payment provider. If a nightly job fails only in production, the first thing to check is whether the environment variables it needs exist there, and the .env diff checker compares your template with the real file by variable name without showing the values.
When you have a working curl command for the request, the cURL to fetch converter turns it into code for the job, and the JSON to TypeScript converter writes the types for the response, so a changed field shows up as a type error instead of a failed run at 3 AM.
Storing schedules in a database
Many apps store the schedule itself in a table, for example a reminder that runs at a time each user picks. If you are designing that table, the SQL to Drizzle and Prisma schema generator turns your CREATE TABLE statements into typed schemas, and the next-run list here is a quick way to confirm that the expression you store means what you think it means.
More tools built the same way are collected in every free tool and guide on Blogs Byte, which lists each guide and tool in one place.
Private by design
Schedules can reveal how a system works, so nothing here is uploaded or stored. The expression is parsed, explained and converted by code running in your browser tab, and the next run times come from your own device's clock. You can confirm it yourself by opening the Network tab in your browser's developer tools while you use the page: there are no requests.
