A cron job runs a command on a schedule. On a hosting account the usual reasons are three: making an application's scheduled tasks run reliably, taking a backup on a timer, and running a maintenance script that clears something out.
The setup is one screen. The parts that catch people are the schedule syntax, the path to the interpreter, and the flood of email that a working cron job produces if you leave the notification address at its default.
Creating one
In cPanel, open Cron Jobs. Set the email address at the top, more on that shortly, then use Common Settings to pick a schedule, or fill the five time fields yourself.
Put the command in the command box and add it. It begins running on the next matching minute.
The five fields
The schedule is five values, in this order: minute, hour, day of month, month, day of week. An asterisk means every.
* * * * * every minute 0 * * * * at the top of every hour 30 2 * * * at 02:30 every day 0 3 * * 0 at 03:00 on Sundays */15 * * * * every fifteen minutes
Use Common Settings until the syntax is familiar. It fills the fields correctly and removes the most common source of a cron job that never runs.
Times are the server's timezone, not yours. A job set for 02:00 to avoid your busy period may be running at midday. Check what the server thinks the time is before assuming.
Silence the email before anything else
Cron emails the output of every run to the address on that screen. A job producing any output at all, and most do, running every five minutes sends 288 messages a day.
What happens next is predictable: you filter those messages, and the one that reports a genuine failure gets filtered with them.
Two options. Leave the address set and make the command silent, so mail arrives only on a real error. Or clear the address entirely, which sends nothing at all.
To discard normal output but still hear about errors, append this to the command:
>/dev/null 2>&1
That silences everything, including errors. To keep errors and discard only normal output, use >/dev/null alone. The second is usually what you want.
Writing the command
Two rules cover almost every failure.
Use full paths. Cron does not start in your website directory, so a relative path finds nothing. Write /home/username/public_html/script.php, not script.php.
Name the interpreter. A PHP file is not executable by itself. The command must start with the PHP binary:
/usr/local/bin/php -q /home/username/public_html/script.php
The path to PHP differs between accounts and PHP versions, and using the wrong one is a common cause of a job that appears to run and does nothing.
The WordPress case
WordPress schedules its own tasks, publishing scheduled posts, checking for updates, running plugin jobs, but it only checks whether anything is due when someone visits the site. On a quiet site, scheduled posts publish late or not at all.
The standard fix is to disable the visitor-triggered version and use a real cron job instead. Add to wp-config.php:
define( 'DISABLE_WP_CRON', true );
Then create a cron job that calls it on a schedule, typically every fifteen minutes. Do not do only half of this: disabling the built-in version without adding the replacement stops scheduled tasks entirely, and the symptom, posts not publishing, backups not running: appears days later with no obvious cause.
Do not run things every minute
Every-minute schedules are tempting and rarely necessary. Each run consumes a PHP process from the pool your plan allows, and a heavy script on a one-minute schedule can slow the site it is supposed to be maintaining.
Choose the longest interval that meets the need. Fifteen minutes covers most maintenance work, and hourly covers most of the rest.
Also stagger jobs. Three tasks all set for exactly 02:00 start together and compete; 02:00, 02:10 and 02:20 do the same work without the spike.
Checking whether it ran
Cron does not keep a history you can browse in cPanel, so build your own signal.
The simplest is to have the script write a line to a file with the date each time it runs. Then a glance at that file answers the question directly, rather than inferring from whether the work seems to have happened.
If a job is not running, work through: is the schedule right, is the path absolute, is the interpreter named, and does the command work when you run it yourself. That order finds it nearly every time.
If the command runs but errors, the output goes to the notification address, which is a reason to set it while testing and silence it afterwards. Understanding cPanel error logs explains where else to look.
For what is actually worth scheduling on a normal hosting account, How to Use Cron Jobs to Automate Tasks goes over the common cases.
The environment is not the one you are used to
A command that works when you type it and fails on a schedule is almost always this, and it is not the command that is wrong.
* * * * * env > /home/user/cron-env.txt # calistiktan sonra: diff <(env | sort) <(sort /home/user/cron-env.txt)
Running that once and comparing shows exactly what is missing. The path variable is usually the difference, which is why a bare command name is not found even though it works in a shell session.
Two habits remove the whole class of problem. Use absolute paths for the program, the script and any file it touches. And set the path explicitly at the top of the schedule rather than relying on whatever the system supplies.
Stop the same job running twice
A job that occasionally takes longer than its interval will eventually be running while the next copy starts, and two copies of an import or a mailing is a visible problem.
* * * * * /usr/bin/flock -n /tmp/myjob.lock /usr/local/bin/myjob.sh * * * * * /usr/bin/flock -n /tmp/myjob.lock -c "php /home/user/bin/job.php"
The lock makes the second attempt exit immediately instead of running alongside the first. It costs nothing and it is the single most useful thing to add to any schedule that touches data.
Without it, the failure mode is not an error. It is duplicate records, doubled emails, or two processes writing the same file, and it happens only on the day the job runs slowly.
Know that it ran, not only that it was scheduled
Silencing the mail is right, and silence then becomes indistinguishable from a job that stopped running months ago.
* * * * * /path/job.sh >> /home/user/logs/job.log 2>&1 && date > /home/user/logs/job.last # kontrol: find /home/user/logs/job.last -mmin +90 && echo "UYARI: is calismadi"
Writing a timestamp on success turns the question into a file age, which is something monitoring can check. A stamp older than the interval means the job has stopped, whatever the schedule says.
That is the difference between finding out in an hour and finding out when somebody asks why the report has not arrived since last month. Using cron jobs to automate tasks covers the wider setup.