Cron Scheduling Guide: Automating Recurring Tasks
Start the bot at 8:50 every morning, produce a report after the close, tidy the logs overnight. Do that by hand and you will eventually forget. cron is Linux's built-in scheduler for "run this command at this time." Thirty-plus years of use have made it simple and dependable.
That said, it almost never works the first time you use it. Why that happens is the heart of this article.
The Five Fields
A cron schedule is five space-separated columns.
┌───── minute (0-59)
│ ┌─── hour (0-23)
│ │ ┌─ day of month (1-31)
│ │ │ ┌───── month (1-12)
│ │ │ │ ┌─── day of week (0-7, 0 and 7 are Sunday)
│ │ │ │ │
* * * * * command to run
You only need four symbols.
*— every value,— a list.0,30= minute 0 and minute 30-— a range.1-5= Monday through Friday/— a step.*/10= every 10 minutes
Common examples:
0 9 * * 1-5 9:00 AM on weekdays
50 8 * * 1-5 8:50 AM on weekdays (10 minutes before the open)
*/5 * * * * every 5 minutes
0 */2 * * * every 2 hours, on the hour
30 3 * * 0 Sunday at 3:30 AM
0 0 1 * * midnight on the 1st of every month
One easily confused point — specifying both day-of-month and day-of-week makes them an OR. 0 9 1 * 1 runs "if it's the 1st or it's a Monday." Not AND.
Register entries by opening the editor with crontab -e and writing one per line. Check them with crontab -l.
The Four Traps Every Beginner Hits
"I added it to cron and nothing happens" is almost always one of these four.
① There's No PATH
Cron does not inherit your shell environment. It doesn't read .bashrc, and PATH is nearly empty. The python that worked in your terminal becomes "command not found" under cron.
The fix is absolute paths everywhere.
# Bad
0 9 * * 1-5 python bot.py
# Good — absolute path for both the interpreter and the script
0 9 * * 1-5 /opt/bot/.venv/bin/python /opt/bot/bot.py
If you use a virtual environment, don't go through activate—point directly at the Python inside the venv. That's the most reliable form.
② The Working Directory Is Different
Cron typically runs commands from your home directory. Use a relative path in your code, like open("config.ini"), and the file won't be found.
# Change directory before running
0 9 * * 1-5 cd /opt/bot && /opt/bot/.venv/bin/python bot.py
The better approach is anchoring the base path in the code itself.
from pathlib import Path
BASE = Path(__file__).resolve().parent # relative to the script's own location
config_path = BASE / "config.ini"
③ The Output Goes Nowhere
A cron job's output is mailed by default, and with no mail configured it simply vanishes. Failures leave no trace and you can't find the cause. Always write to a log.
# Append both stdout and stderr to a file
0 9 * * 1-5 cd /opt/bot && ./run.sh >> /var/log/bot/cron.log 2>&1
2>&1 is what sends error output to the same file. Without it, the very error message you need disappears.
④ Timezone
If the server runs UTC, so does cron. 0 9 * * * fires at 6 PM Korean time. Check the server clock with date first, and declare it at the top of the crontab if needed.
CRON_TZ=Asia/Seoul
0 9 * * 1-5 /opt/bot/.venv/bin/python /opt/bot/bot.py
Preventing Overlap — flock
What happens when a job scheduled every 5 minutes takes 6? Cron doesn't check whether the previous run finished. Processes pile up until the server falls over. If the script places orders, you get duplicate orders.
flock solves it in one line.
# -n : if the lock can't be acquired, exit immediately rather than wait
*/5 * * * * /usr/bin/flock -n /tmp/report.lock /opt/bot/report.sh >> /var/log/bot/report.log 2>&1
If the previous run is still going, this round is quietly skipped. Put it on essentially every recurring cron job.
On Windows — Task Scheduler
Windows has no cron; Task Scheduler fills the role. The GUI works, but registering by command is reproducible, which is better.
REM Start the bot at 8:50 AM on weekdays
schtasks /create /tn "TradingBotStart" /tr "C:\bot\run.bat" /sc weekly /d MON,TUE,WED,THU,FRI /st 08:50
REM Verify registration / run it immediately as a test
schtasks /query /tn "TradingBotStart"
schtasks /run /tn "TradingBotStart"
Two places people commonly get stuck on Windows. If "Run only when user is logged on" is enabled, nothing runs after a reboot until someone logs in — switch it to "Run whether user is logged on or not." And if you don't set "Start in", relative paths break in exactly the same way as cron's trap ②.
A Real Combination — One Day's Schedule
In practice you bundle it like this.
CRON_TZ=Asia/Seoul
# 08:50 start the bot before the open (weekdays)
50 8 * * 1-5 /usr/bin/flock -n /tmp/bot.lock /opt/bot/start.sh >> /var/log/bot/start.log 2>&1
# 15:40 stop the bot after the close + send the daily report to Telegram
40 15 * * 1-5 /opt/bot/stop_and_report.sh >> /var/log/bot/report.log 2>&1
# every 30 minutes, liveness check — alert if it's dead
*/30 * * * * /usr/bin/flock -n /tmp/hc.lock /opt/bot/healthcheck.sh >> /var/log/bot/hc.log 2>&1
# daily at 3 AM, delete logs older than 30 days
0 3 * * * find /var/log/bot -name "*.log" -mtime +30 -delete
Don't skip that last line. Without log cleanup, the disk fills a few months later and kills the bot. It's a genuinely common incident.
When Cron Isn't the Right Tool
Cron's smallest unit is the minute, and every run spawns a fresh process. So don't use it for these.
- Second-scale repetition — handle it with a loop or timer inside the bot
- Work that must retain state — a new process each time means in-memory state is gone
- Restart immediately on crash — that's the domain of systemd or Docker's
restartpolicy, not cron
Draw the line like this: "a job done once at a set time" is cron; "a job that must keep running" is a process manager.
Summary
- The expression is five fields — minute, hour, day-of-month, month, day-of-week — and day-of-month with day-of-week is an OR
- Cron doesn't inherit your shell environment → absolute paths for everything
- The working directory differs →
cdfirst, or anchor paths on__file__in code - Output vanishes →
>> logfile 2>&1is mandatory - Check the server timezone; declare
CRON_TZif needed - Block overlapping runs on recurring jobs with
flock -n - On Windows use
schtasks, watching "run whether logged on or not" and "Start in" - Always include a log-cleanup job — the disk is what kills the bot
Cron takes 20 minutes to learn and 2 hours to get trapped by. Know these four traps going in and you skip those 2 hours.
댓글
댓글 쓰기