What Is a Cron Job?
Because scheduling recurring system tasks manually would require constant human attention, Unix-like operating systems rely on a background service to run commands automatically at specified times. A cron job is a single entry in that system's schedule: one command, one time pattern, one recurring task executed without user interaction.
What is a cron job?
What distinguishes a cron job from other scheduled tasks
Cron jobs are time-based, not event-based: they fire at specific calendar moments, not in response to system events or user actions. Yet unlike operating system init scripts (which run once at boot) or inotify watchers (which react to file changes), cron jobs recur on a fixed schedule. Consequently, they are the standard tool for maintenance tasks that must happen regularly: backups, cache expiration, report generation, log rotation, and health checks.
Why recurring intent matters
Before choosing cron, ask whether the work repeats on a predictable calendar pattern. If the answer is yes, cron gives you a compact expression and a daemon that keeps checking it without keeping your application process alive. If the answer depends on another job finishing first, a queue, or an external event, a workflow scheduler is a better fit. Cron shines when the timing is simple and the task can run independently. That simplicity is why it remains useful even when more advanced schedulers are available. It keeps the schedule visible and easy to inspect during routine maintenance. That visibility is often more valuable than a more complex scheduler for small recurring jobs.
Consider also how cron handles failure: it does not retry, alert, or track history on its own, so the task needs to be safe to run independently and acceptable to lose if the host is offline during its window. That limitation is part of the simplicity, not a bug, and it is why cron suits stateless maintenance rather than sensitive transactional work. Reaching for a heavier scheduler only pays off once your job needs the guarantees cron deliberately omits.
How cron jobs are stored and executed
Cron jobs live in crontab files: one per user, plus system-wide files in /etc/cron.d/ and /etc/crontab. Once per minute, the cron daemon reads all active crontab files, compares every schedule to the current time, and spawns a subprocess for each matching job. This minute-by-minute polling model is simple and predictable, but it means cron cannot provide sub-minute precision or react to events between polling intervals.1 Inside that subprocess, only a minimal environment is available (no login shell, minimal PATH), and any output is either mailed to the user or discarded. For most maintenance tasks, the one-minute granularity is more than sufficient, but event-driven workloads that need an immediate response to a file change or a message arriving in a queue are better served by inotify watchers or service-specific triggers.2
When to use cron jobs vs alternative schedulers
Cron is appropriate for simple, stateless recurring tasks that do not require retry logic, dependency management, or execution history. For tasks that need orchestration (running only if a previous job succeeded), distributed scheduling, or sub-minute precision, purpose-built tools are better choices: systemd timers for DST-safe local scheduling, Airflow or Prefect for data pipeline orchestration, and at for one-time future execution.3
Matching the job to the scheduler
Choose cron when the schedule itself is the main requirement and the command can stand alone. Choose a workflow scheduler when timing, retries, and dependencies form one decision. The distinction keeps routine maintenance simple without forcing every job into a heavier orchestration model. A useful heuristic is to count the failure modes you need to handle: if the job either succeeds or fails with no follow-up required, cron is sufficient, but if a failed run must trigger an alert, retry, or rollback, a workflow scheduler gives you that capability without custom scripting.
Cron jobs in cloud and container environments
In containerized environments, cron still works, but the daemon must run inside the container, and the container must stay alive between scheduled runs. Serverless platforms handle this differently: Vercel Cron Jobs trigger serverless functions via HTTP, AWS EventBridge invokes Lambda functions, and GitHub Actions fires workflow runs on schedule. Each of these platform-specific schedulers trades the simplicity of a local crond for built-in observability, retry logic, and execution history that a raw cron daemon does not provide.
Platform-native scheduling vs running crond in a container
For Kubernetes deployments, the native CronJob resource handles scheduling at the orchestrator level, removing the need to run a cron daemon inside each pod.4 This approach is more reliable than embedding crond in a container because the cluster controller handles missed runs, concurrency policy, and job history automatically. Running crond inside an application container is generally the wrong pattern in a Kubernetes environment.
When a cron job is the wrong tool
Cron works well for independent, stateless tasks that succeed or fail without affecting subsequent runs. It becomes the wrong choice when you need retry logic after a failure, dependency ordering between jobs, or monitoring of intermediate steps. For those scenarios, a workflow orchestration system (Airflow, Prefect, or Temporal) tracks run history, enforces dependencies, and retries failed steps automatically.
For one-time future execution, the at command on Linux is purpose-built and cleaner than writing a cron job that deletes itself after running once.3 For sub-minute execution, a long-running process with a sleep loop or a language-level scheduler is simpler and more precise than cron's one-minute resolution limit.5
Try in the tool
What to look for
- Minimum interval once per minute (* * * * *)
Open the Cron Expression Parser & Next-Run Preview tool to try this yourself.
Open the tool →- 1.
The Open Group, "crontab — schedule periodic background work," opengroup.org, 2018. https://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html
- 2.
Linux man7, "cron(8) — Linux manual page," man7.org, accessed June 2026. https://www.man7.org/linux/man-pages/man8/cron.8.html
- 3.
Linux man7, "crontab(5) — Linux manual page," man7.org, accessed June 2026. https://www.man7.org/linux/man-pages/man5/crontab.5.html
- 4.
Kubernetes, "CronJob," kubernetes.io, accessed June 2026. https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/
- 5.
Red Hat, "Managing scheduled tasks with crontab," access.redhat.com, accessed June 2026. https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/managing_system_services_and_units/assembly_managing-scheduled-tasks-with-crontab
A cron job can run any shell command or executable script. Common uses include database backups, syncing files to remote storage, sending scheduled emails or reports, clearing caches, rotating log files, and running data import/export pipelines.
The minimum interval for standard Unix cron is once per minute. A cron expression of * * * * * runs every minute. For sub-minute scheduling, use systemd timers or a purpose-built job scheduler, as standard cron does not support seconds-level precision.
Cron jobs persist until explicitly removed with crontab -r or by editing the crontab file. They survive reboots because the cron daemon reads the crontab file at startup. A job scheduled with @reboot runs only on daemon startup; all other expressions fire indefinitely on the recurring schedule.
Any user on a Unix-like system with access to the crontab command can create personal cron jobs using crontab -e. Root can create system-wide jobs in /etc/cron.d/ or /etc/crontab. Some systems restrict crontab access with /etc/cron.allow and /etc/cron.deny files.
Yes. If a cron job fails (non-zero exit code), the cron daemon does not automatically retry it or alert the user unless MAILTO is configured and a mail server is set up. For critical jobs, add explicit error handling inside the script and route output to a log file. CapyToolkit's parser helps confirm the schedule before you add the failure-handling details.
What Is Crontab?
Although cron (the scheduling daemon) and crontab (the table it reads) are often mentioned together, they are distinct things: cron is the background process, crontab is the data it reads. Understanding the difference matters when troubleshooting; a job that is not running is either a cron daemon problem or a crontab entry problem, and the diagnosis path differs for each.
What is crontab?
/var/spool/cron/crontabs/<username>. The file contains one job per line, each consisting of a 5-field cron expression followed by the command to execute. The crontab -e command opens the file for editing, and the cron daemon reads it to schedule jobs.1The crontab file format
A crontab file contains two types of lines: environment variable assignments (like SHELL=/bin/bash) and job lines. Each job line follows the format: minute hour dom month dow command. Comment lines begin with #. The system-wide crontab (/etc/crontab) adds a sixth column (the username) between the schedule and the command, allowing jobs to run as different users. Yet user crontabs edited with crontab -e do not include this username column.
Reading the line before the command
Start at the left edge of each job line and name each field before you read the command. If the minute, hour, day, month, or weekday field is unclear, the command itself will not reveal the timing mistake. This left-to-right reading habit catches misplaced values before the job is installed, and it also makes handoffs easier because the timing and command remain visibly separated for anyone reviewing the file. That separation helps reviewers spot a misplaced field before the daemon reads it, especially when the command line grows long and complex enough to push the schedule off the visible portion of the screen.
Practicing this field-by-field read on paper before editing a live crontab builds the muscle memory that prevents off-by-one errors in production. A team that adopts the habit as part of its code-review checklist catches timing slips that would otherwise survive automated tests, because no test suite verifies that a cron expression means what its author intended.
Crontab file locations and system cron directories
User crontabs are stored in /var/spool/cron/crontabs/<username> (Linux) or /var/cron/tabs/<username> (macOS).23 System cron files live in /etc/crontab, /etc/cron.d/ (individual files, one per application), and /etc/cron.{hourly,daily,weekly,monthly}/ (scripts placed directly in directories). Files in /etc/cron.d/ follow the system crontab format with a user column. Files dropped in the time-based directories are executed by run-parts on the appropriate schedule. Knowing which location to use is important because the file path determines whether the daemon treats the entry as a user job or a system job, and getting it wrong can lead to ownership mismatches or silent scheduling failures.
Choosing the right crontab location
Use crontab -e for user jobs because it installs the file atomically and preserves the expected ownership. Use /etc/cron.d/<name> for system or application jobs that need a user field and package-managed deployment. The location determines who owns the schedule, who can edit it, and how the daemon interprets the command. A common mistake is editing /var/spool/cron/crontabs/<username> directly with a text editor, which bypasses the atomic install and can leave the daemon reading a partially written file; always use the crontab command to avoid this race condition.
Managing crontab files safely
Use crontab -l > ~/crontab.bak to export a backup before making changes. The crontab -r command removes the entire crontab without confirmation: one typo can delete all scheduled jobs.4 Consequently, always back up before removing. Configuration management tools (Ansible, Chef, Puppet) manage crontab entries using dedicated modules that avoid the -r risk and support idempotent updates. A backup also gives you a rollback point when a new schedule behaves unexpectedly. It is a small step that prevents a typo from becoming an outage.
The most dangerous moment in crontab management is the edit itself, because saving a malformed file can either break an existing job or install a new one that fires at the wrong time. Treating every crontab edit as a code change, complete with a backup and a post-edit verification step, is the habit that separates reliable scheduling from silent failures that go unnoticed until a downstream team reports a missed job.
Crontab file permissions and security
The cron daemon enforces ownership-based access control: it only executes jobs from a crontab file owned by the user that file represents. A crontab file with incorrect ownership is silently ignored. Running crontab -e as the correct user handles ownership automatically; manually copying a file to /var/spool/cron/crontabs/ without setting the correct owner produces a file the daemon skips without error.5
Access control with cron.allow and cron.deny
Some systems restrict crontab access further with /etc/cron.allow and /etc/cron.deny. If /etc/cron.allow exists, only users listed in it may run crontab -e. If only /etc/cron.deny exists, any user not listed in it may use crontab. If neither file exists, access policy depends on the distribution cron package defaults. Check man 1 crontab on your system to confirm the active access-control behavior. The allow-file takes precedence when both files exist: if /etc/cron.allow is present, /etc/cron.deny is ignored entirely, so never mix the two approaches on the same host. A common hardening pattern is to create /etc/cron.allow with only the root username, which locks out all other users regardless of whether /etc/cron.deny also exists, and this single-file approach is easier to audit during security reviews than maintaining a deny list that must be updated every time a new service account is provisioned.
Crontab vs scheduled units in modern environments
On servers managed with systemd, choosing between crontab and a systemd timer affects reliability and observability. Crontab is simpler to set up, but systemd timers are DST-safe, support persistent scheduling after downtime (with Persistent=true), and produce run history queryable via journalctl. For a new job on a modern systemd system, a .timer unit is the stronger choice for anything that requires auditability.
In containers and Kubernetes, the crontab model does not translate directly. Kubernetes CronJob resources handle scheduling at the cluster level; Alpine-based containers use busybox cron rather than vixie-cron, with more limited syntax support. Recognizing that crontab is a Unix-specific concept, distinct from platform-native schedulers, prevents confusion when moving jobs between environments.
Try in the tool
What to look for
- Linux crontab location /var/spool/cron/crontabs/username
- macOS crontab location /var/cron/tabs/username
Open the Cron Expression Parser & Next-Run Preview tool to try this yourself.
Open the tool →- 1.
The Open Group, "crontab — schedule periodic background work," opengroup.org, 2018. https://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html
- 2.
Linux man7, "crontab(5) — Linux manual page," man7.org, accessed June 2026. https://www.man7.org/linux/man-pages/man5/crontab.5.html
- 3.
Apple, "Scheduled Jobs," developer.apple.com, accessed June 2026. https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/ScheduledJobs.html
- 4.
Red Hat, "Automate your Linux system tasks with cron," redhat.com, September 2019. https://www.redhat.com/en/blog/automate-linux-tasks-cron
- 5.
Linux man7, "cron(8) — Linux manual page," man7.org, accessed June 2026. https://www.man7.org/linux/man-pages/man8/cron.8.html
No. Cron is the daemon (background process) that runs scheduled jobs. Crontab is the configuration file (the table) that cron reads to know what to run and when. CapyToolkit's parser checks the schedule part of each line before you save it in the table. The crontab command is the tool used to manage the table.
User crontabs are typically in /var/spool/cron/crontabs/<username> on Linux and /var/cron/tabs/<username> on macOS. You should not edit these files directly; use crontab -e instead, which validates and installs the file atomically.
Each user has one personal crontab. However, system-wide cron configuration spans multiple files: /etc/crontab, individual files in /etc/cron.d/, and scripts in the time-based directories. Applications often install their cron jobs in /etc/cron.d/<app-name> to keep schedules separate and manageable.
Behavior varies by implementation. Some cron daemons (like vixie-cron) reject the entire file on crontab -e save if any line is invalid. Others silently skip bad lines. Use a cron expression validator before deploying to catch errors before they reach the daemon.
Crontab files are plain UTF-8 text. Non-ASCII characters in comments are generally safe, but command paths and arguments must use ASCII-compatible encoding. Avoid non-ASCII characters in environment variable values or command arguments as the behavior is implementation-dependent.
What Is a Cron Expression?
Before understanding what a cron expression contains, it helps to understand what it replaces: a verbose, error-prone description like "run at minute 0 of every third hour on weekdays." A cron expression compresses that description into five fields, each with a specific position and valid range, that the cron daemon reads directly without parsing natural language.1
What is a cron expression?
*), a range (1-5), a comma-separated list (1,3,5), or a step expression (*/15).2 The cron daemon evaluates the expression every minute and executes the associated command when all five fields match the current time.3Structure and evaluation
The five fields must appear in a fixed order, separated by spaces: minute hour day-of-month month day-of-week. The daemon reads the expression once per minute3 and fires the job only when the current time satisfies all five fields simultaneously. A wildcard (*) in any field matches every valid value for that field. Consequently, 0 9 * * * matches minute 0, hour 9, any day, any month, any weekday; it runs every day at 9:00 AM.
Translating intent into field order
Write the human schedule as five answers before you type the expression: minute, hour, day-of-month, month, and day-of-week. This prevents the common mistake of putting the hour where the minute belongs. Once the five values are in order, add wildcards and operators only to the fields that need them. Practicing this translation on paper or in a comment before writing the actual expression builds the habit of thinking in field positions, which becomes second nature after a handful of expressions and dramatically reduces off-by-one field errors.
When you translate a verbal schedule into field positions, say each field out loud as you write it: "minute zero, hour nine, any day, any month, any weekday." The spoken sequence forces the correct order and reveals ambiguity before it reaches the crontab file. Teams that adopt this verbalization step during code review catch field-order mistakes that static analysis misses, because the error is semantic, not syntactic.
Operators that extend a single field
Four operators modify what a field matches. A hyphen (-) creates a range: 1-5 in the weekday field matches Monday through Friday. Understanding these operators is what lets a five-field expression encode complex schedules that would otherwise require multiple separate job lines to express the same trigger pattern in full.
Reading ranges, lists, and steps
A comma (,) creates a list: 1,15 in the day-of-month field matches the 1st and 15th. A slash (/) creates a step: */5 in the minute field matches every 5 minutes. Combining them in a single field is valid: 1,10-20,30 is a union of a value, a range, and another value.2 Yet no operator changes the left-to-right field order: position always determines meaning, so the first value you read is always the minute field regardless of which operators appear in it.
Platform extensions and compatibility
Standard Unix cron uses exactly 5 fields. AWS EventBridge adds a 6th year field and requires ? to leave one of day-of-month or day-of-week unused4. Quartz scheduler adds a seconds field as position 1, making it a 6-field or 7-field expression5. systemd OnCalendar uses a different syntax entirely. Consequently, a cron expression written for one platform may not parse correctly on another. Always check which format your target system expects before copying an expression across environments.
The most common portability mistake is assuming that a cron expression is universally understood, when in fact the number of fields and the meaning of each position depend on the platform. An expression that works on a Linux server can produce a parse error or an unintended schedule on AWS EventBridge or Kubernetes CronJob. The safest approach is to treat each platform as a separate target and verify the expression against that platform's documentation before deploying.
Reading an unfamiliar expression in four steps
When you encounter an expression you did not write, read it left to right with the field positions as your guide. The first value is the minute, the second the hour, the third the day-of-month, the fourth the month, and the fifth the day-of-week. An asterisk in any position means that field does not constrain the schedule. A step like */5 means "every 5 values within this field's valid range."
Identifying 6-field and 7-field variants
For a 6-field expression that starts with cron() or contains a ?, you are looking at AWS EventBridge format: read the year as the last field and treat ? as "this field is unused." Quartz scheduler expressions place a seconds field at position 1, shifting all other fields one position to the right. Identifying the format before reading the values prevents misinterpretation that would produce an off-by-one-field error. A quick heuristic is to count the fields first: if you see exactly five space-separated tokens, you are dealing with standard Unix cron, but six or more tokens signal a platform extension that changes the meaning of every position.
Generating expressions from a plain-English description
Writing a new expression starts with isolating the time components of your description. "Every weekday at 8:30 AM UTC" breaks into: minute=30, hour=8, day-of-month=*, month=*, day-of-week=1-5, giving 30 8 * * 1-5. "The first of every month at midnight" breaks into: minute=0, hour=0, day-of-month=1, month=*, day-of-week=*, giving 0 0 1 * *.
After writing the expression, paste it into a parser and read the plain-English output. If the description matches your intent, the expression is correct. If the parser returns more trigger times than you expected, one field is more permissive than intended. Fix the most specific field first: if you intended "weekdays" and the parser shows all days, the day-of-week field is the problem.
Try in the tool
What to look for
- Standard Unix cron 5 fields
- AWS EventBridge 6 fields, adds year
- Quartz scheduler 6 to 7 fields, prepends seconds
Open the Cron Expression Parser & Next-Run Preview tool to try this yourself.
Open the tool →- 1.
Linux man7, "crontab(5) — Linux manual page," man7.org, accessed June 2026. https://www.man7.org/linux/man-pages/man5/crontab.5.html
- 2.
The Open Group, "crontab — schedule periodic background work," opengroup.org, 2018. https://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html
- 3.
Linux man7, "cron(8) — Linux manual page," man7.org, accessed June 2026. https://www.man7.org/linux/man-pages/man8/cron.8.html
- 4.
Amazon Web Services, "Cron expressions," docs.aws.amazon.com, accessed June 2026. https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-scheduled-rule-pattern.html
- 5.
Quartz Scheduler, "CronTrigger Tutorial," quartz-scheduler.org, accessed June 2026. https://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html
Standard Unix cron uses 5 fields: minute, hour, day-of-month, month, day-of-week. AWS EventBridge adds a 6th year field. Quartz scheduler prepends a seconds field, creating 6 or 7 fields. Always check which format your target platform expects before writing an expression.
A wildcard matches every valid value for that field. * in the minute field means "every minute." * in the month field means "every month." A completely wildcarded expression (* * * * *) fires every minute.
All five fields must match simultaneously. There is no left-to-right priority; the daemon checks all fields at once each minute. If any field does not match the current time, the job does not fire. CapyToolkit's parser shows this as a plain-English schedule so you can compare the interpretation with your intent.
Not directly in standard 5-field cron. The closest approach is a very specific expression like 30 14 5 3 * (2:30 PM on March 5th, every year). For a true one-time job, use the at command or a scheduler that supports one-shot execution.
Different Unix implementations chose different numbering schemes: some started weekdays at 0 (Sunday), others at 1 (Monday). To accommodate both conventions, most modern cron implementations accept both 0 and 7 as Sunday in the day-of-week field.
What Is a Cron Daemon?
Without a process that continuously monitors the clock and launches jobs, cron expressions would be inert strings. The cron daemon is that process: a background service that wakes up once per minute, reads all active crontab files, and starts a subprocess for each job whose schedule matches the current time.1
What is a cron daemon?
How the daemon reads and executes jobs
At startup, the cron daemon reads all user crontab files from /var/spool/cron/crontabs/ and system crontab files from /etc/crontab and /etc/cron.d/.4 It then wakes up once per minute, compares all active schedules to the current time, and forks a subprocess for each match. Each subprocess inherits a minimal environment and runs the command as the user who owns the job. The daemon itself never terminates between jobs; it is designed to run indefinitely.
Why the daemon only checks once per minute
The one-minute polling interval defines cron's resolution. A job scheduled for 09:00 will not start at 09:00:30 if the daemon wakes at 09:01. This is why cron is ideal for minute-level recurring work but not for sub-minute precision. If the schedule must fire within seconds, choose a tool designed for tighter timing. The polling design is a deliberate trade-off: checking every second would increase system load on every machine running cron, while one-minute granularity is sufficient for the vast majority of maintenance and automation tasks that cron was built to handle.
When you deploy a job that requires sub-minute timing, you are not choosing a different cron configuration; you are choosing a different scheduling paradigm. Systemd timers with OnCalendar support second-level precision; language-level schedulers like APScheduler run inside your application process; distributed schedulers like Celery Beat coordinate across workers. Understanding that the one-minute poll is a hard boundary, not a tunable parameter, saves you from trying to hack around it with sleep loops or wrapper scripts that only add complexity without improving accuracy.
What the daemon does not manage
The daemon launches matching commands, but it does not orchestrate retries, dependencies, or run history. For a backup script that must alert, retry, or wait for another task, the cron entry can still be the trigger, but the script or a workflow scheduler should own that logic. Understanding this boundary prevents you from expecting cron to behave like a full workflow orchestrator when it was designed solely as a time-based command launcher.
Common daemon implementations and their differences
Vixie-cron (Paul Vixie, 1988) is the original implementation and the basis for most modern variants. As a maintained fork targeting Fedora, RHEL, and CentOS, cronie adds inotify support (reloading crontabs on file change without polling) and per-user access restrictions. Fcron extends the format further with directives for job persistence, retry behavior, and per-job resource limits.5 Yet all three share the same core parsing rules for standard 5-field expressions, so crontab files are portable across them.
The choice of daemon rarely affects how you write a crontab file, because the 5-field expression format is universal. Where the implementations differ is in their operational behavior: cronie reloads crontabs instantly via inotify, while vixie-cron waits for the next minute tick. For most use cases, this difference is irrelevant, but when you need a new schedule to take effect immediately without restarting the daemon, cronie is the better choice.
How to check and manage the cron daemon
systemctl status cron or systemctl status crond shows whether the daemon is running on systemd-based Linux. service cron status works on older init systems. Log output appears in journalctl -u cron (systemd) or /var/log/syslog (older systems). Restarting the daemon (systemctl restart cron) forces it to re-read all crontab files immediately, which is useful after making manual edits to system crontab files in /etc/cron.d/.
The difference between reloading and restarting the daemon is important for production systems. A reload re-reads crontab files without interrupting currently running jobs, while a restart terminates in-flight jobs before reloading. For routine crontab edits, prefer systemctl reload cron to avoid killing a long-running backup or report job mid-execution. Reserve systemctl restart cron for situations where the daemon itself is unresponsive or has stopped picking up changes.
Starting, stopping, and diagnosing a stopped daemon
Before investigating why a cron job is not running, confirm the daemon itself is active. On Debian and Ubuntu, systemctl status cron shows the daemon's running state and the most recent log lines. On Fedora, RHEL, and CentOS, the binary name is crond: run systemctl status crond instead. A daemon showing "active (running)" means the problem is in the crontab entry, not the scheduler. Skipping this first check is how administrators spend an hour debugging a crontab expression that was correct all along, when in fact the daemon had stopped during a previous deployment.
If the daemon has stopped, start it with sudo systemctl start cron (or crond). Check journalctl -u cron -S "1 hour ago" for messages explaining why it stopped. A daemon that stops repeatedly may have a corrupted system crontab in /etc/cron.d/ that causes a parse error on startup; check each file in that directory for syntax issues. After fixing the file, reload the daemon and confirm the next scheduled run appears in the logs. A clean reload proves the daemon can parse the repaired system crontab.
How the daemon handles crontab file changes
After you edit your crontab with crontab -e, the daemon detects the change without requiring a restart. Modern implementations (cronie in particular) use inotify to watch /var/spool/cron/crontabs/ for file modifications and reload immediately. That instant reload is the reason most administrators prefer cronie on production machines. Older vixie-cron variants check the crontab modification timestamp on each minute tick and reload only if the file has changed since the previous check.
Reloading system crontab files in /etc/cron.d/
For system crontab files in /etc/cron.d/, send a systemctl reload cron signal to trigger the daemon to re-read all files in that directory. This is the correct approach after deploying a new package that installs a file there, since the daemon may not reload automatically on all distributions. A systemctl restart cron also works but terminates any currently running jobs before reloading.
Try in the tool
What to look for
- Polling interval once per minute, a hard boundary not a tunable parameter
- User crontab location /var/spool/cron/crontabs/
- System crontab locations /etc/crontab and /etc/cron.d/
- Status check systemctl status cron (Debian/Ubuntu) or systemctl status crond (Fedora/RHEL/CentOS)
Prefer systemctl reload cron for routine crontab edits; a restart terminates in-flight jobs before reloading.
Open the Cron Expression Parser & Next-Run Preview tool to try this yourself.
Open the tool →- 1.
Linux man7, "cron(8) — Linux manual page," man7.org, accessed June 2026. https://www.man7.org/linux/man-pages/man8/cron.8.html
- 2.
Cronie Project, "cronie — Cronie cron daemon project," github.com, accessed June 2026. https://github.com/cronie-crond/cronie
- 3.
Apple, "Scheduled Jobs," developer.apple.com, accessed June 2026. https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/ScheduledJobs.html
- 4.
Linux man7, "crontab(5) — Linux manual page," man7.org, accessed June 2026. https://www.man7.org/linux/man-pages/man5/crontab.5.html
- 5.
Arch Linux, "fcrontab(5) — Arch manual pages," man.archlinux.org, accessed June 2026. https://man.archlinux.org/man/extra/fcron/fcrontab.5.en
Run systemctl status cron or systemctl status crond on systemd-based systems. On older init-based systems, use service cron status or ps aux | grep cron. CapyToolkit's parser helps verify the schedule after the daemon is active. If the daemon is not running, cron jobs will not execute until it is started.
On systemd systems: sudo systemctl restart cron (Debian/Ubuntu) or sudo systemctl restart crond (Fedora/RHEL). On macOS, cron is managed by launchd and does not require manual restarts. Restarting forces the daemon to reload all crontab files immediately.
Both refer to the cron daemon, but the binary name varies by distribution. Debian and Ubuntu use cron (binary /usr/sbin/cron); Fedora, RHEL, and CentOS use crond (from the cronie package). The underlying behavior is the same.
No. Standard cron daemons wake up once per minute. Sub-minute scheduling requires a different tool: systemd timers (with OnCalendar using seconds precision), a language-level scheduler (like APScheduler in Python), or purpose-built tools like Celery Beat.
Any job scheduled to run while the daemon is stopped is missed permanently; standard cron does not backfill missed jobs by default. Use Persistent=true in systemd timers or the catchup feature in schedulers like Airflow if you need missed-run recovery.
What Are Cron @-Shortcuts?
For routine schedules, a short alias can make a crontab easier to read. Cron @-shortcuts replace field-position memorization with intent-bearing aliases: write @daily instead of 0 0 * * *, or @weekly instead of 0 0 * * 0. The daemon maps each supported shortcut to the same 5-field schedule it would otherwise parse directly.1 Seven shortcuts cover the most common scheduling intervals.
What is cron @-shortcuts?
@reboot (run once at daemon startup), @yearly/@annually (0 0 1 1 *), @monthly (0 0 1 * *), @weekly (0 0 * * 0), @daily/@midnight (0 0 * * *), and @hourly (0 * * * *).2 Each appears in a crontab line where the 5-field expression would normally go.All seven shortcuts and their equivalents
@reboot has no 5-field equivalent and runs once when the cron daemon starts.3 @yearly and @annually are interchangeable aliases for 0 0 1 1 *: midnight on January 1st. @monthly maps to 0 0 1 * *. @weekly maps to 0 0 * * 0: midnight every Sunday. @daily and @midnight are aliases for 0 0 * * *. @hourly maps to 0 * * * *. No shortcut exists for sub-hourly intervals; use step expressions like */15 * * * *.
Memorizing these seven mappings is straightforward because the alias names map directly to common English scheduling terms, and using them consistently makes crontabs more readable for anyone familiar with cron. For portable automation, confirm each shortcut against its 5-field equivalent in a parser before deploying to production. That quick check avoids silent failures on platforms that silently skip aliases they do not recognize.
Converting shortcuts before moving platforms
When a shortcut must move to a platform that does not support it, replace the shortcut with its 5-field equivalent before changing the file. @daily becomes 0 0 * * *, @weekly becomes 0 0 * * 0, and @hourly becomes 0 * * * *. This keeps the intent visible and avoids relying on implementation-specific aliases.
It also gives reviewers a familiar expression to compare against the target platform. The conversion is especially useful when moving from a Linux crontab to a cloud scheduler. It also makes the expression easier to validate in a browser parser. The 5-field form is the safest common denominator across cron tools.
Practical use cases
@reboot is used for startup initialization: starting background services, mounting shares, or sending a boot-time notification. @daily is the most common production shortcut, appearing in backup scripts, log rotation, and cache-clearing jobs. Each shortcut maps to a specific maintenance cadence, and choosing the right one depends on how often the underlying work needs to run and whether the schedule must survive a platform migration.
Matching shortcuts to maintenance cadence
Building on this, @weekly covers full-system maintenance and package update reports. @monthly fits billing scripts and monthly report generation. @hourly suits polling jobs and queue flushes where a one-minute interval is too aggressive and a day is too coarse. The key is to match the shortcut to the natural rhythm of the work: log rotation aligns with daily cycles, database backups often follow weekly retention policies, and billing pipelines typically run on monthly closing dates.
Portability and when not to use shortcuts
@-shortcuts are supported by vixie-cron, cronie, and most Linux cron implementations. Yet AWS EventBridge, Kubernetes CronJob, and GitHub Actions do not support them.4 Cloudflare Workers requires 5-field cron expressions in wrangler.toml. systemd OnCalendar uses different named presets (daily, weekly) that look similar but use different syntax. These divergent implementations are why portable automation demands a common denominator.
Consequently, when writing cron expressions for portability across platforms, the 5-field equivalent is safer before you trust it in production. The risk of using shortcuts in a portable crontab is that the job silently fails on platforms that do not recognize the alias, leaving you with no scheduled runs and no error message. Before relying on @-shortcuts in a multi-platform environment, verify each target platform's documentation for shortcut support. The safest pattern is to reserve @-shortcuts for crontabs that will only ever run on a known Linux server, and to use 5-field expressions for any schedule that might be copied to a cloud scheduler or a container image.
Testing @-shortcut portability before deploying
Before using an @-shortcut in a new cron environment, confirm the target implementation supports it. On Linux, a quick test is adding @reboot echo test to a throwaway crontab file and verifying crontab -l displays it without error. A minimal Docker image based on Alpine Linux uses busybox cron, which does not support @-shortcuts.5
Busybox cron in container images
A crontab entry using @daily inside an Alpine container silently drops the job rather than reporting an error. Container crontabs should always use 5-field expressions for safety, because the cron implementation inside the container image is not always predictable from the base image name alone. Check the container image documentation to identify the implementation before relying on @-shortcut syntax. This silent failure mode is particularly dangerous in production because the container builds and deploys successfully, giving no indication that the scheduled job will never fire until you inspect the logs or miss an expected run.
What @reboot actually triggers in practice
The @reboot shortcut fires when the cron daemon starts, not when the system kernel finishes booting.2 On a systemd-managed system, the cron service starts after most network and disk services are ready, so @reboot jobs usually have access to the network and filesystem without a sleep delay. Understanding this distinction prevents the common mistake of assuming that a @reboot job can depend on application services that start later in the boot sequence.
If your @reboot job depends on a specific service (like a database or a message broker), add an explicit sleep or use a systemd unit with After=postgresql.service instead. Relying on @reboot for service-dependent startup tasks produces intermittent failures on slower systems where the dependency is not yet ready when the cron daemon launches.
Try in the tool
What to look for
- @daily / @midnight 0 0 * * *
- @weekly 0 0 * * 0
- @yearly / @annually 0 0 1 1 *
Open the Cron Expression Parser & Next-Run Preview tool to try this yourself.
Open the tool →- 1.
Linux man-pages project, "crontab(5)," man7.org, accessed June 2026. https://www.man7.org/linux-man-pages/man5/crontab.5.html
- 2.
Debian Project, "crontab(5) — Debian manual pages," manpages.debian.org, accessed June 2026. https://manpages.debian.org/trixie/cron/crontab.5.en.html
- 3.
The Open Group, "crontab," pubs.opengroup.org, accessed June 2026. https://pubs.opengroup.org/onlinepubs/9799919799/utilities/crontab.html
- 4.
Kubernetes, "CronJob," kubernetes.io, accessed June 2026. https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/
- 5.
OpenWrt, "Consider enabling crontab special time extension," github.com, accessed June 2026. https://github.com/openwrt/openwrt/issues/9200
@daily is a shortcut alias for 0 0 * * *, which runs the command at midnight (00:00) every day. It is also available as @midnight, and the two are identical.
@weekly maps to 0 0 * * 0, which fires at midnight on Sunday (day-of-week 0). If you need a weekly job on Monday, use 0 0 * * 1 or specify the day explicitly rather than relying on @weekly.
@reboot runs when the cron daemon starts after a system boot, not directly at kernel boot. If the cron daemon is restarted manually without a system reboot, @reboot entries are typically skipped.
No. Vixie-cron and cronie on Linux support all seven. Some minimal implementations (busybox cron) do not support any. Cloud platforms like GitHub Actions and AWS EventBridge do not support @-shortcuts in their cron fields. Use the 5-field equivalent for maximum portability. CapyToolkit's parser works from the standard 5-field model after you convert the shortcut.
They are identical aliases; both map to 0 0 1 1 *. Use either based on your preference. @yearly is more commonly seen in documentation; @annually is less common but valid on any cron implementation that supports either.
What Are Cron Operators?
Because each cron field needs to express more than a single value, four operators extend a field's matching power beyond a literal number. Together, the wildcard, range, list, and step operators cover every recurring schedule a cron expression can represent, from "every minute" to "at minutes 5, 15, 25, 35, and 45 during business hours on weekdays."1
What is cron operators?
* matches every valid value in the field. The range operator - defines a continuous span (e.g., 1-5 in day-of-week matches Monday through Friday). The list operator , combines discrete values (e.g., 1,15 in day-of-month matches the 1st and 15th). The step operator / selects every Nth value within a range or wildcard (e.g., */5 in the minute field matches minutes 0, 5, 10, 15, …, 55).2 All four operators can be combined within a single field.Wildcard (*) and step (/): the interval operators
The wildcard (*) matches every valid value for a field: it is the most permissive operator. When combined with the step operator (*/N), it matches every Nth value starting from the field's minimum. */15 in the minute field expands to 0-59/15, firing at 0, 15, 30, and 45. */6 in the hour field fires at hours 0, 6, 12, and 18. The step denominator must be a positive integer within the field's valid range.2 Understanding how the wildcard and step interact is essential for writing expressions that fire at predictable intervals, because the step always counts from the field's minimum value rather than from an arbitrary starting point.
Starting from a permissive expression
A useful audit method is to replace every operator with its literal matches. */15 in minutes becomes 0, 15, 30, and 45; 9-17/2 becomes 9, 11, 13, 15, and 17. This expansion reveals whether the expression is broader than the schedule you intended before you test it in the daemon. When the expanded set contains values you did not expect, narrow the range or adjust the step until the literal matches align with your business requirement, then convert back to the compact operator form for the final crontab entry.
Writing out the expanded values by hand forces you to confront each trigger time individually. A developer who reads */10 and mentally converts it to "every 10 minutes" misses the edge cases: minute 0 fires at the top of the hour, minute 50 fires ten minutes before the hour, and minute 60 does not exist so the cycle resets. Expanding the expression makes these boundaries visible and prevents the off-by-one surprises that only appear in production logs.
Range (-) and list (,): the selection operators
The range operator (-) creates a contiguous span between two values, inclusive. 1-5 in day-of-week matches Monday through Friday; 9-17 in the hour field matches 9 AM through 5 PM. The list operator (,) selects discrete, non-contiguous values. 0,6,12,18 in the hour field fires at midnight, 6 AM, noon, and 6 PM. Building on this, operators compose freely within a field: 1,5-10,15 is a union of a single value, a range, and another value, all evaluated in the same field.3
The choice between a range and a list depends on whether the target values are contiguous. A range is more compact for spans like weekdays or business hours, while a list is clearer for non-contiguous values like specific hours of the day. When a list grows long enough that a range plus a step could express the same set more compactly, prefer the range-step combination for readability.
Combining operators and platform-specific extensions
All four standard operators work in all five fields of a 5-field cron expression. AWS EventBridge adds two additional operators for its 6-field format: L (the last-day operator: L in day-of-month means the last day of the month; 6L means the last Saturday) and # (the ordinal operator: 2#1 means the first Monday of the month).4
Keeping EventBridge extensions isolated
The W operator (nearest weekday) is also available in EventBridge expressions. Consequently, expressions using L, W, or # are not portable to standard Unix cron. Treat these three operators as EventBridge-only features and never mix them into expressions that might be copied to a Linux crontab, a Kubernetes CronJob manifest, or a GitHub Actions workflow file. A practical rule is to add a comment above any EventBridge expression that uses L, W, or # stating that the expression is platform-specific and should not be used as a template for other environments.
When to combine operators in one field
When your schedule requires non-uniform trigger times within a single field, combining operators gives you precise control in one expression rather than multiple crontab entries. A list with a range like 0,30 9-17 * * 1-5 fires at minutes 0 and 30 during every hour from 9 AM to 5 PM on weekdays. Combining a list with a range-qualified step in the minute field, such as 0,5-55/10, fires at minute 0 and then at minutes 5, 15, 25, 35, 45, and 55.
Before combining operators, verify the resulting trigger set with a parser. Complex combinations are syntactically valid but often fire more or less frequently than you intended. Paste the expression and read the next 10 trigger times; if the output matches your design, the expression is correct. Isolate each operator change one at a time until the trigger set matches your intent.
Portability of operators across cron implementations
Across all five-field cron implementations (Linux crontab, GitHub Actions, Kubernetes CronJob, Cloudflare Workers, and Render), the four standard operators (*, -, ,, /) are universally supported. An expression using only these four operators is portable without modification across any platform that accepts 5-field cron syntax.5 This universality makes the four standard operators the safest choice when you manage crontab files that might be deployed to multiple environments or shared across a team with different target platforms.
AWS EventBridge-specific extensions
EventBridge adds three non-standard operators: L (last day), W (nearest weekday), and # (nth weekday of the month). These operators are not recognized by standard cron parsers and produce an error or unexpected behavior if you copy an EventBridge expression into a Linux crontab or a GitHub Actions workflow. For teams managing expressions across platforms, restrict to the four standard operators as the safe default; add EventBridge-specific operators only in EventBridge-targeted expressions, and document that restriction in a comment above the expression.
Try in the tool
What to look for
- * (wildcard) matches all values, e.g. 0-59 in the minute field
- */5 (step) fires at 0, 5, 10, ..., 55
Open the Cron Expression Parser & Next-Run Preview tool to try this yourself.
Open the tool →- 1.
Debian Project, "crontab(5) — Debian manual pages," manpages.debian.org, accessed June 2026. https://manpages.debian.org/trixie/cron/crontab.5.en.html
- 2.
Linux man-pages project, "crontab(5)," man7.org, accessed June 2026. https://www.man7.org/linux-man-pages/man5/crontab.5.html
- 3.
The Open Group, "crontab," pubs.opengroup.org, accessed June 2026. https://pubs.opengroup.org/onlinepubs/9799919799/utilities/crontab.html
- 4.
Amazon Web Services, "Schedule Types," docs.aws.amazon.com, accessed June 2026. https://docs.aws.amazon.com/scheduler/latest/UserGuide/schedule-types.html
- 5.
Kubernetes, "CronJob," kubernetes.io, accessed June 2026. https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/
An asterisk is the wildcard operator and matches every valid value for that field. * in the minute field matches minutes 0 through 59. CapyToolkit's parser can show the expanded timing before you deploy it. In the month field, it matches all 12 months. A fully wildcarded expression (* * * * *) matches every minute.
A comma (,) creates a list of discrete, non-contiguous values: 1,3,5 matches only those values. A hyphen (-) creates a continuous range: 1-5 matches 1, 2, 3, 4, and 5, meaning every value in between. Use a range for contiguous spans and a list for scattered values.
The slash creates a step: it selects every Nth value within a range or wildcard. */5 in the minute field fires at 0, 5, 10, ..., 55. 10-50/10 fires at 10, 20, 30, 40, and 50. The denominator specifies the interval size.
Yes. All four operators can appear in the same field and are evaluated as a union. 0,30 9-17/2 * * 1-5 fires at minutes 0 and 30, during hours 9, 11, 13, 15, and 17, on weekdays.
These are non-standard extensions supported by AWS EventBridge and Quartz scheduler, but not by standard Unix cron. L in day-of-month means the last day of the month. W means the nearest weekday. # selects the Nth occurrence of a weekday in a month (e.g., 2#1 = first Monday). Avoid these in standard crontabs.
What Is the Day-of-Week Field in Cron?
The fifth field in a cron expression controls which days of the week a job runs, and it behaves differently from every other field when combined with day-of-week. Sunday carries two valid numbers (0 and 7), named shortcuts like MON and FRI work on most platforms, and a hidden OR interaction with the day-of-week field catches many developers off guard.1 Understanding these quirks before writing an expression saves you from schedules that fire twice as often as intended.
What is the day-of-week field?
Numbering and named shortcuts
Day numbers follow the week starting Sunday: 0 (Sunday), 1 (Monday), 2 (Tuesday), 3 (Wednesday), 4 (Thursday), 5 (Friday), 6 (Saturday), 7 (Sunday again). The duplication of Sunday as both 0 and 7 is a compatibility artifact from different historical implementations. Named shortcuts (MON, TUE, WED, THU, FRI, SAT, SUN) are supported by vixie-cron and most Linux implementations.2 Use them to make expressions self-documenting: 0 9 * * MON-FRI is clearer than 0 9 * * 1-5.
Choosing numeric days for portability
Numeric values are safest when an expression may move between systems. Named shortcuts are readable, but not every platform supports them. If you choose numbers, add a short comment such as # 1-5 means Monday through Friday so the intent remains clear to the next maintainer. That small comment prevents future edits from treating the values as arbitrary numbers. AWS EventBridge in particular requires numeric values in the 1 to 7 range with Sunday as 1, so an expression using 0 for Sunday on Unix must be shifted by one when ported to EventBridge.
When you standardize on numeric day values across your team, you eliminate the silent failures that occur when an expression with named shortcuts lands on a platform that does not recognize them. A crontab that works perfectly on Linux because MON through FRI are understood will silently do nothing on a minimal container image that only knows numbers. The comment convention creates a self-documenting expression that remains valid and readable regardless of the underlying cron implementation.
OR behavior when combined with day-of-month
Cron fires a job if the day-of-month matches OR the day-of-week matches: not both simultaneously. This surprises developers who expect 0 0 1 * 1 to mean "midnight on Mondays that fall on the 1st."1 Understanding this OR behavior is one of the most important things to learn before writing a cron expression that constrains both the day-of-month and the day-of-week, because the interaction between these two fields is the single most common source of unintended trigger frequency.
Handling OR logic in scripts
Instead, it fires at midnight on the 1st of every month AND at midnight every Monday, regardless of the date. Consequently, to target only Mondays that fall on the 1st, you must perform the date intersection check inside the script itself. The standard pattern is to leave day-of-week as the only scheduling constraint and add a date guard at the top of the script that exits early when the day-of-month does not match, giving you true AND behavior through a simple two-line check.
Day-of-week across platform variations
Standard Unix cron (Linux, macOS) uses 0–7 with both 0 and 7 as Sunday, and named shortcuts. AWS EventBridge uses 1–7 with Sunday as 1 (not 0) and also supports SUN–SAT shortcuts; EventBridge day-of-week numbers are shifted by 1 compared to Unix.3 Kubernetes CronJob uses standard Unix numbering. GitHub Actions uses standard Unix numbering. Always verify which numbering scheme your platform uses when writing day-of-week values numerically.
The numbering difference between Unix and EventBridge is a common source of off-by-one errors when porting expressions. An expression written for Linux that uses 1 for Monday will fire on Sunday when copied directly into EventBridge, because EventBridge maps 1 to Sunday. The safest approach is to test the expression on the target platform before deploying, rather than assuming the numbering is identical.
Testing OR logic before deploying
Given that cron's OR behavior surprises most developers, verifying day-of-month and day-of-week combinations before deployment prevents unintended run frequency. Paste your expression into a cron parser and count the trigger times across a full month, then spot-check the following month. An expression intended to run once a week that fires 8 or 9 times in a 30-day window is combining a day-of-month with a day-of-week.
The script-based intersection pattern
The reliable workaround for true day intersection is to use only the day-of-week field in the cron expression, then check the date inside your script: in bash, [ $(date +%-d) -eq 1 ] || exit 0 exits immediately unless today is the 1st. This gives you precise calendar control without relying on cron's OR logic, and the intent is explicit and auditable.
Named shortcuts across cron implementations
Named day shortcuts improve expression readability. Most platforms including Linux crontab, GitHub Actions, and Kubernetes CronJob accept them without configuration changes. The inconsistency between platforms is why many teams standardize on numeric values for day of week fields. Relying on named shortcuts can cause silent failures when an expression is copied to a platform that does not recognize them. This is especially important for teams that manage cron expressions across multiple environments. AWS EventBridge accepts the full set but maps them to a 1-based numbering.4 This means MON in EventBridge corresponds to value 2 not 1 as in Unix cron. This numbering difference is a common source of off-by-one errors when porting expressions between platforms and should be documented in team runbooks.
For maximum portability, use numeric values and document the day names in a comment above the crontab entry. A comment line reading # Weekdays: 1-5 = Mon-Fri above 0 9 * * 1-5 preserves readability without relying on named shortcut support in environments where it may be absent. CapyToolkit's cron parser shows the exact fields your expression targets, so you can verify the range resolves to the intended weekdays before the entry reaches a production server.
Try in the tool
What to look for
- Unix cron Sunday 0 (or 7)
- AWS EventBridge Sunday 1
Open the Cron Expression Parser & Next-Run Preview tool to try this yourself.
Open the tool →- 1.
Debian Project, "crontab(5) — Debian manual pages," manpages.debian.org, accessed June 2026. https://manpages.debian.org/trixie/cron/crontab.5.en.html
- 2.
Linux man-pages project, "crontab(5)," man7.org, accessed June 2026. https://www.man7.org/linux-man-pages/man5/crontab.5.html
- 3.
Amazon Web Services, "EventBridge Scheduled Rule Pattern," docs.aws.amazon.com, accessed June 2026. https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-scheduled-rule-pattern.html
- 4.
Kubernetes, "CronJob," kubernetes.io, accessed June 2026. https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/
0 = Sunday, 1 = Monday, 2 = Tuesday, 3 = Wednesday, 4 = Thursday, 5 = Friday, 6 = Saturday, 7 = Sunday (same as 0). CapyToolkit's parser uses this standard Unix numbering for standard 5-field expressions. Both 0 and 7 represent Sunday for backward compatibility.
Use 1-5 in the day-of-week field: 0 9 * * 1-5 fires at 9 AM Monday through Friday. Alternatively, named shortcuts work on most systems: 0 9 * * MON-FRI.
This is a design decision from the original Unix cron specification. Both fields must be non-wildcards for OR behavior to apply; if either is a wildcard, only the non-wildcard field is checked. The original rationale was to allow expressions like "run on the 1st of the month, or on Mondays" without requiring two separate job entries.
No. EventBridge uses 1–7 where Sunday = 1, Monday = 2, ..., Saturday = 7. Unix cron uses 0–7 where Sunday = 0 (or 7). An expression like 0 9 ? * 2 * means 9 AM Monday in EventBridge, but 0 9 * * 2 means 9 AM Tuesday in Unix cron.
Yes, on most Linux cron implementations. MON-FRI is equivalent to 1-5. MON,WED,FRI is equivalent to 1,3,5. Case sensitivity varies by implementation; uppercase is safest.