PHP Cron Job: Schedule and Automate PHP Scripts (Complete Guide)

Table of Contents

Most PHP automation problems come down to the same issue – someone has to manually run the script. Backups, price checks, data scraping, email reports – if a human has to trigger it, it will eventually get missed.

Cron jobs fix this. They tell your server to run a PHP script automatically on a schedule – every hour, every day at midnight, every Monday morning – without anyone touching a keyboard.

This guide covers everything you need to set up a PHP cron job that actually works: correct syntax, absolute paths, environment variables, logging output, debugging when nothing happens, and running real automation tasks like scrapers and database cleanups on a schedule.

What is a Cron Job?

Cron is a time-based job scheduler built into Linux and Unix systems. It runs in the background constantly, checking a configuration file called the crontab for tasks to execute at specific times.

Each entry in the crontab has two parts: a schedule expression that defines when to run, and a command that defines what to run. The server handles the rest automatically – no manual intervention needed once it’s set up.

PHP cron jobs work by pointing the cron scheduler at your PHP binary and your script file:

/usr/bin/php /var/www/html/your-script.php

The PHP binary executes your script the same way it would if you ran it from the command line – just on an automatic schedule instead of manually.

What You Need

  • A Linux server or shared hosting with cron access
  • SSH access or a cPanel account
  • PHP installed on the server
  • A PHP script you want to automate

On shared hosting without SSH, most providers offer a cron job manager inside cPanel – covered in Step 3. On a VPS or dedicated server, you configure cron directly from the command line.

Cron Syntax – Reading and Writing Schedule Expressions

Every cron job entry follows the same format. Five time fields followed by the command to run:

* * * * * command-to-execute
| | | | |
| | | | +---- Day of week  (0-7, where 0 and 7 both mean Sunday)
| | | +------ Month        (1-12)
| | +-------- Day of month (1-31)
| +---------- Hour         (0-23)
+------------ Minute       (0-59)

An asterisk * in any field means “every” – every minute, every hour, every day. Replace it with a number to specify exactly when.

Common Schedule Patterns

These are the schedules you’ll actually use in real projects:

# Run every minute
* * * * * /usr/bin/php /var/www/html/script.php

# Run every 5 minutes
*/5 * * * * /usr/bin/php /var/www/html/script.php

# Run every hour at minute 0
0 * * * * /usr/bin/php /var/www/html/script.php

# Run every day at midnight
0 0 * * * /usr/bin/php /var/www/html/script.php

# Run every day at 6am
0 6 * * * /usr/bin/php /var/www/html/script.php

# Run every Monday at 9am
0 9 * * 1 /usr/bin/php /var/www/html/script.php

# Run on the 1st of every month at midnight
0 0 1 * * /usr/bin/php /var/www/html/script.php

# Run every weekday (Monday to Friday) at 8am
0 8 * * 1-5 /usr/bin/php /var/www/html/script.php

# Run twice a day - at midnight and noon
0 0,12 * * * /usr/bin/php /var/www/html/script.php

Reading the Syntax Step by Step

Take this expression: 0 9 * * 1

  • 0 – at minute 0 (the start of the hour)
  • 9 – at hour 9 (9am)
  • * – any day of the month
  • * – any month
  • 1 – only on Monday

Combined: run at 9:00am every Monday.

Take this one: */15 9-17 * * 1-5

  • */15 – every 15 minutes
  • 9-17 – between 9am and 5pm
  • * – any day of the month
  • * – any month
  • 1-5 – Monday through Friday

Combined: run every 15 minutes during business hours on weekdays.

Special Shorthand Expressions

Cron also supports shorthand strings that are easier to read than five-field expressions:

# Shorthand      Equivalent        Meaning
@yearly          0 0 1 1 *         Run once a year at midnight on January 1
@monthly         0 0 1 * *         Run once a month at midnight on the 1st
@weekly          0 0 * * 0         Run once a week at midnight on Sunday
@daily           0 0 * * *         Run once a day at midnight
@hourly          0 * * * *         Run once an hour at minute 0
@reboot          (no equivalent)   Run once at system startup

Use these when the schedule is simple – they’re easier to read and harder to get wrong.

Verifying Your Cron Expression Before Using It

A wrong cron expression either never runs or runs at the wrong time – and you won’t know until you notice something didn’t execute. Before adding any new cron job, test the expression at crontab.guru – it shows you in plain English exactly when your expression will fire, and lists the next five scheduled run times.

For example, paste 0 9 * * 1 and it tells you: “At 09:00 on Monday.” Paste */5 * * * * and it says: “Every 5 minutes.” One check before saving prevents hours of debugging later.

Creating Your First PHP Cron Job Script

A PHP script that runs fine in a browser can silently fail when run by cron. The environment is different – no web server, no browser, different user permissions, and a minimal PATH variable. Writing cron scripts correctly from the start saves hours of debugging.

A Basic Cron Script

<?php
// Set error reporting - cron won't show errors unless you capture them
error_reporting(E_ALL);
ini_set('display_errors', 0);      // don't echo errors to terminal output
ini_set('log_errors', 1);
ini_set('error_log', __DIR__ . '/cron_errors.log');

// Set time limit - 0 means no limit for long-running tasks
set_time_limit(0);

// Log that the script started
$logFile = __DIR__ . '/cron.log';
$started = date('Y-m-d H:i:s');

file_put_contents($logFile, "[$started] Script started." . PHP_EOL, FILE_APPEND);

// Your actual task goes here
// For now, just log the execution time
file_put_contents($logFile, "[$started] Task completed successfully." . PHP_EOL, FILE_APPEND);
?>

Save this as cron_task.php in your project directory. Run it manually from the command line first to confirm it works before scheduling it:

php /var/www/html/cron_task.php

Output in cron.log:

[2026-05-01 09:00:00] Script started.
[2026-05-01 09:00:00] Task completed successfully.

If it runs correctly from the command line, it will run correctly as a cron job. If it fails on the command line, fix it there before scheduling anything.

Why Absolute Paths Are Mandatory

This is the most common reason cron scripts fail silently. In a browser or terminal session your system knows where to find files relative to your current directory. Cron runs with a minimal environment – it has no concept of your current working directory and no idea where your project lives.

<?php
// Wrong - works in browser, silently fails in cron
$logFile = 'cron.log';
require 'config.php';
include 'helpers/functions.php';

// Right - always use absolute paths in cron scripts
$logFile = '/var/www/html/myproject/cron.log';
require '/var/www/html/myproject/config.php';
include '/var/www/html/myproject/helpers/functions.php';
?>

The cleanest way to handle this is with __DIR__ – it always returns the absolute path of the directory containing the current file, regardless of where the script is called from:

<?php
// __DIR__ gives you the absolute path automatically
// No need to hardcode /var/www/html/...
$logFile = __DIR__ . '/cron.log';
require __DIR__ . '/config.php';
include __DIR__ . '/helpers/functions.php';

echo __DIR__ . PHP_EOL; // /var/www/html/myproject
?>

Use __DIR__ for every file reference inside a cron script. It works regardless of the server environment and doesn’t break when you move the project to a different directory.

Handling Environment Variables in Cron

Cron runs with a stripped-down environment. Variables your normal shell session sets automatically – like database credentials in .env files loaded by your shell profile – are not available to cron scripts.

Load them explicitly inside the script:

<?php
// If you use a .env file, load it manually
$envFile = __DIR__ . '/.env';

if (file_exists($envFile)) {
    $lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);

    foreach ($lines as $line) {
        if (strpos(trim($line), '#') === 0) continue; // skip comments

        list($key, $value) = explode('=', $line, 2);
        $_ENV[trim($key)]  = trim($value);
        putenv(trim($key) . '=' . trim($value));
    }
}

// Now use them
$dbHost = $_ENV['DB_HOST'] ?? 'localhost';
$dbName = $_ENV['DB_NAME'] ?? '';
$dbUser = $_ENV['DB_USER'] ?? '';
$dbPass = $_ENV['DB_PASS'] ?? '';

echo "Connecting to $dbName on $dbHost" . PHP_EOL;
?>

A Real Cron Script – Automated Scraper

Here’s a complete cron script that scrapes books.toscrape.com daily and stores results to a log file:

<?php
error_reporting(E_ALL);
ini_set('log_errors', 1);
ini_set('error_log', __DIR__ . '/cron_errors.log');
set_time_limit(0);

$logFile  = __DIR__ . '/scraper.log';
$startTime = microtime(true);

function log_message($message) {
    global $logFile;
    $entry = '[' . date('Y-m-d H:i:s') . '] ' . $message . PHP_EOL;
    file_put_contents($logFile, $entry, FILE_APPEND);
    echo $entry;
}

function scrape_page($url) {
    $ch = curl_init();

    curl_setopt_array($ch, [
        CURLOPT_URL            => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_CONNECTTIMEOUT => 10,
        CURLOPT_TIMEOUT        => 30,
        CURLOPT_HTTPHEADER     => [
            'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
        ],
    ]);

    $response = curl_exec($ch);
    $errno    = curl_errno($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($errno || $httpCode !== 200) {
        log_message("Failed to fetch $url - HTTP $httpCode");
        return false;
    }

    return $response;
}

log_message("Scraper cron started.");

$url        = "https://books.toscrape.com/";
$page       = 1;
$totalBooks = 0;

while ($url) {
    $html = scrape_page($url);

    if (!$html) {
        log_message("Stopping at page $page - fetch failed.");
        break;
    }

    libxml_use_internal_errors(true);
    $dom = new DOMDocument();
    $dom->loadHTML($html);
    libxml_clear_errors();

    $xpath = new DOMXPath($dom);
    $books = $xpath->query('//article[contains(@class,"product_pod")]');

    $totalBooks += $books->length;
    log_message("Page $page - {$books->length} books found.");

    $nextNode = $xpath->query('//li[contains(@class,"next")]/a')->item(0);

    if ($nextNode) {
        $url = "https://books.toscrape.com/catalogue/" 
               . $nextNode->getAttribute('href');
        $page++;
        sleep(1);
    } else {
        $url = null;
    }

    unset($html, $books);
    $dom = null;
}

$duration = round(microtime(true) - $startTime, 2);
log_message("Scraper complete. Total books: $totalBooks | Duration: {$duration}s");
?>

Output in scraper.log:

[2026-05-01 00:00:01] Scraper cron started.
[2026-05-01 00:00:02] Page 1 - 20 books found.
[2026-05-01 00:00:03] Page 2 - 20 books found.
...
[2026-05-01 00:02:14] Page 50 - 20 books found.
[2026-05-01 00:02:14] Scraper complete. Total books: 1000 | Duration: 133.4s

Run this manually first:

php /var/www/html/myproject/cron_task.php

Confirm the log file is created and the output looks correct. Only then move to scheduling it.

Setting Up the Cron Job – Linux Command Line and cPanel

There are two ways to schedule a PHP cron job depending on your hosting environment. On a VPS or dedicated server you edit the crontab directly from the command line. On shared hosting you use cPanel’s cron job manager. Both end up doing the same thing – adding an entry to the crontab file.

Method 1: Linux Command Line

Open the crontab editor with:

crontab -e

This opens the crontab file in your default terminal editor (usually nano or vim). If it’s your first time, the file will be empty.

First, find the absolute path to your PHP binary:

which php

Output:

/usr/bin/php

This is the path you use in your cron entry. Don’t assume it’s /usr/bin/php – verify it on your actual server because it varies by hosting provider and PHP version.

Now add your cron entry. To run the scraper script every day at midnight:

0 0 * * * /usr/bin/php /var/www/html/myproject/cron_task.php >> /var/www/html/myproject/cron_output.log 2>&1

Break down of what each part does:

  • 0 0 * * * – schedule: daily at midnight
  • /usr/bin/php – absolute path to PHP binary
  • /var/www/html/myproject/cron_task.php – absolute path to your script
  • >> – append output to a log file
  • /var/www/html/myproject/cron_output.log – where terminal output goes
  • 2>&1 – redirect error output to the same log file

Save and exit the editor. In nano: Ctrl+X, then Y, then Enter. In vim: :wq.

Confirm the cron job was saved:

crontab -l

Output:

0 0 * * * /usr/bin/php /var/www/html/myproject/cron_task.php >> /var/www/html/myproject/cron_output.log 2>&1

Redirecting Output – Why It Matters

Without the >> log 2>&1 part, all output from your script disappears into a void – or gets emailed to the server’s local mail system where you’ll never see it. Always redirect output to a log file:

# Append output to log file (keeps history)
0 0 * * * /usr/bin/php /path/to/script.php >> /path/to/output.log 2>&1

# Overwrite log file each run (only keeps latest)
0 0 * * * /usr/bin/php /path/to/script.php > /path/to/output.log 2>&1

# Discard all output - use only if script has its own logging
0 0 * * * /usr/bin/php /path/to/script.php > /dev/null 2>&1

Use >> (append) for most scraping and automation jobs so you keep a history of every run. Use > (overwrite) for scripts that run frequently and would create very large log files.

Method 2: cPanel Cron Job Manager

Log into cPanel and find Cron Jobs under the Advanced section. You’ll see a form with fields for the schedule and the command.

In the Command field, enter the full command exactly as you would in the crontab – PHP binary path, script path, and output redirect:

/usr/local/bin/php /home/yourusername/public_html/myproject/cron_task.php >> /home/yourusername/public_html/myproject/cron_output.log 2>&1

On cPanel servers the PHP binary is often at /usr/local/bin/php instead of /usr/bin/php. Find the correct path by running this in cPanel’s Terminal (if available) or asking your hosting provider:

which php

Set the schedule using the dropdown fields cPanel provides – minute, hour, day, month, weekday – or type the cron expression directly if your cPanel version supports it.

Click Add New Cron Job and the entry is saved.

Multiple Cron Jobs

Each cron job gets its own line in the crontab. Add as many as you need:

# Daily scraper at midnight
0 0 * * * /usr/bin/php /var/www/html/scraper.php >> /var/www/html/scraper.log 2>&1

# Hourly price check
0 * * * * /usr/bin/php /var/www/html/price_check.php >> /var/www/html/price.log 2>&1

# Weekly database cleanup every Sunday at 2am
0 2 * * 0 /usr/bin/php /var/www/html/db_cleanup.php >> /var/www/html/cleanup.log 2>&1

# Monthly report on the 1st at 6am
0 6 1 * * /usr/bin/php /var/www/html/monthly_report.php >> /var/www/html/report.log 2>&1

Setting the Correct Timezone

Cron uses the server’s system timezone, not PHP’s timezone setting. If your server is UTC but you’re in a different timezone, your jobs will run at unexpected times.

Check the server timezone:

date

Output:

Thu May  1 00:00:01 UTC 2026

Two ways to handle this. Set the timezone at the start of your PHP script:

<?php
date_default_timezone_set('Asia/Kolkata'); // IST - UTC+5:30

echo "Server time: " . date('Y-m-d H:i:s') . PHP_EOL;
// Output: Server time: 2026-05-01 05:30:01
?>

Or account for the offset in your cron schedule. If you want a job to run at 9am IST (UTC+5:30) and your server is UTC, schedule it for 3:30am UTC:

30 3 * * * /usr/bin/php /var/www/html/script.php >> /var/www/html/script.log 2>&1

Debugging PHP Cron Jobs That Don’t Run

You set up the cron job, the schedule looks right, but nothing happens. No output, no log file, no errors. This is the most frustrating cron problem because there’s nothing obvious to fix – the silence itself is the symptom.

Work through these checks in order. Most cron failures are explained by one of them.

Check 1: Confirm the Cron Job Is Actually Saved

crontab -l

If your cron job doesn’t appear here it was never saved. Open the crontab editor again with crontab -e and add it.

Also confirm the cron service itself is running:

# On Ubuntu/Debian
sudo systemctl status cron

# On CentOS/RHEL
sudo systemctl status crond

Output when running correctly:

● cron.service - Regular background program processing daemon
     Loaded: loaded (/lib/systemd/system/cron.service; enabled)
     Active: active (running) since Thu 2026-05-01 00:00:01 UTC

If it shows inactive or stopped:

# Start it
sudo systemctl start cron

# Enable it to start on boot
sudo systemctl enable cron

Check 2: Test the Exact Command Cron Will Run

Copy the exact command from your crontab – not just the PHP script path, the full command including the PHP binary path – and run it manually in the terminal:

/usr/bin/php /var/www/html/myproject/cron_task.php >> /var/www/html/myproject/cron_output.log 2>&1

If this fails in the terminal it will fail in cron too. Fix it here first. Common failures at this stage:

  • Wrong PHP binary path – run which php to confirm
  • Script path has a typo – run ls -la /var/www/html/myproject/cron_task.php to verify the file exists
  • Permission denied – the file isn’t executable by the cron user

Check 3: Fix Permission Issues

Cron runs as the user who owns the crontab. If the script or its directory isn’t readable by that user, cron fails silently:

# Check who owns the script
ls -la /var/www/html/myproject/cron_task.php

# Output:
# -rw-r--r-- 1 www-data www-data 1842 May 01 00:00 cron_task.php

# Make the script readable and executable
chmod 644 /var/www/html/myproject/cron_task.php

# Make sure the directory is accessible
chmod 755 /var/www/html/myproject/

Also check that any files the script writes to – log files, output files – are writable by the cron user:

# Make the log file writable
touch /var/www/html/myproject/cron.log
chmod 664 /var/www/html/myproject/cron.log

Check 4: Verify the PHP Binary Path

The PHP binary path varies between servers. Using the wrong one means cron can’t find PHP and silently does nothing:

# Find the correct PHP binary path
which php

# If that doesn't work try these common locations
ls -la /usr/bin/php
ls -la /usr/local/bin/php
ls -la /usr/bin/php8.1
ls -la /usr/bin/php8.2

If you have multiple PHP versions installed, make sure the path points to the version you want:

# Check which version each binary runs
/usr/bin/php --version
/usr/local/bin/php --version

# Output example:
# PHP 8.2.0 (cli) (built: Nov 26 2023 09:15:00)
# PHP 7.4.33 (cli) (built: Oct 31 2022 10:03:01)

Check 5: Look at the Cron System Log

The system logs cron activity – every time a job fires and any errors it encounters:

# On Ubuntu/Debian
grep CRON /var/log/syslog | tail -20

# On CentOS/RHEL
grep CRON /var/log/cron | tail -20

Output when cron fires successfully:

May  1 00:00:01 server CRON[12483]: (www-data) CMD (/usr/bin/php /var/www/html/myproject/cron_task.php)

Output when cron can’t find the command:

May  1 00:00:01 server CRON[12483]: (www-data) CMD (/usr/bin/php /var/www/html/myproject/cron_task.php)
May  1 00:00:01 server CRON[12484]: (CRON) error (grandchild #12484 failed with exit status 127)

Exit status 127 means “command not found” – either the PHP binary path or the script path is wrong.

Check 6: Capture All Output Including Errors

If you’re discarding output with /dev/null you have no visibility into what the script is doing. Temporarily redirect everything to a file:

# Change this
0 0 * * * /usr/bin/php /var/www/html/script.php > /dev/null 2>&1

# To this - capture everything
0 0 * * * /usr/bin/php /var/www/html/script.php >> /var/www/html/debug.log 2>&1

Wait for the next scheduled run, then check the log:

cat /var/www/html/debug.log

Even a PHP fatal error will now appear in the log instead of disappearing silently.

Check 7: Test With a Minimal Script First

If you can’t tell whether the problem is cron itself or your script, create the simplest possible cron script and schedule it to run in the next minute:

<?php
file_put_contents('/var/www/html/cron_test.log', date('Y-m-d H:i:s') . " - cron ran\n", FILE_APPEND);
?>

Schedule it to run in 2 minutes from now. If your current time is 14:23, set it to run at 14:25:

25 14 * * * /usr/bin/php /var/www/html/cron_test.php >> /var/www/html/cron_test.log 2>&1

Wait two minutes and check the log:

cat /var/www/html/cron_test.log

Output if cron is working:

2026-05-01 14:25:01 - cron ran

If this appears, cron is working and the problem is in your actual script. If nothing appears, the problem is with cron configuration or permissions.

Check 8: Environment Variable Differences

Add this to your script temporarily to dump what environment cron is running in – it’s often very different from your normal shell session:

<?php
$envLog = '/var/www/html/cron_env.log';

file_put_contents($envLog, "=== Cron Environment ===" . PHP_EOL);
file_put_contents($envLog, "Date: " . date('Y-m-d H:i:s') . PHP_EOL, FILE_APPEND);
file_put_contents($envLog, "User: " . get_current_user() . PHP_EOL, FILE_APPEND);
file_put_contents($envLog, "PHP: " . PHP_VERSION . PHP_EOL, FILE_APPEND);
file_put_contents($envLog, "Working dir: " . getcwd() . PHP_EOL, FILE_APPEND);
file_put_contents($envLog, "PATH: " . getenv('PATH') . PHP_EOL, FILE_APPEND);
?>

Output in cron_env.log:

=== Cron Environment ===
Date: 2026-05-01 00:00:01
User: www-data
PHP: 8.2.0
Working dir: /
PATH: /usr/bin:/bin

Notice the working directory is / – not your project folder. This confirms why relative paths fail in cron. The PATH is also much shorter than your interactive shell, which is why commands that work in your terminal sometimes can’t be found by cron.

WordPress WP-Cron vs System Cron

If you’re running PHP automation on a WordPress site, you’ll encounter two different cron systems. Understanding which one to use – and when – prevents scheduled tasks from running late, running twice, or not running at all.

What is WP-Cron?

WP-Cron is WordPress’s built-in task scheduler. It handles things like scheduled posts, plugin updates, and email sending. Unlike a real cron job, WP-Cron doesn’t run on a time-based schedule – it runs when someone visits your site.

Every time a page loads on your WordPress site, WordPress checks a list of pending scheduled tasks and runs any that are overdue. This means:

  • If nobody visits your site for 6 hours, scheduled tasks don’t run for 6 hours
  • On high-traffic sites, the cron check runs on every single page load – adding overhead to every request
  • Tasks scheduled for “midnight” might actually run at 2am when the first visitor triggers it

For a simple blog this is fine. For automation tasks that need to run at specific times – scraping, price tracking, database cleanup – it’s unreliable.

Checking What WP-Cron Has Scheduled

<?php
// Add this to a temporary PHP file in your WordPress root
// Access it once via browser to see scheduled tasks
define('ABSPATH', dirname(__FILE__) . '/');
require_once ABSPATH . 'wp-load.php';

$crons = _get_cron_array();

foreach ($crons as $timestamp => $cronHooks) {
    $nextRun = date('Y-m-d H:i:s', $timestamp);

    foreach ($cronHooks as $hook => $events) {
        foreach ($events as $event) {
            echo "Hook: $hook" . PHP_EOL;
            echo "Next run: $nextRun" . PHP_EOL;
            echo "Schedule: " . ($event['schedule'] ?? 'single') . PHP_EOL;
            echo "---" . PHP_EOL;
        }
    }
}
?>

Output:

Hook: publish_future_post
Next run: 2026-05-02 09:00:00
Schedule: single
---
Hook: wp_update_plugins
Next run: 2026-05-01 12:00:00
Schedule: twicedaily
---
Hook: wp_scheduled_delete
Next run: 2026-05-02 00:00:00
Schedule: daily
---

Disabling WP-Cron and Replacing It With a Real Cron Job

For any serious WordPress automation, disable WP-Cron’s visitor-triggered behavior and replace it with a real system cron job. This makes scheduled tasks run on time and removes the overhead from page loads.

Step 1 – disable WP-Cron in wp-config.php:

<?php
// Add this line to wp-config.php before the "That's all" comment
define('DISABLE_WP_CRON', true);
?>

Step 2 – add a real cron job that triggers WP-Cron on a schedule. Every 5 minutes is the standard:

*/5 * * * * /usr/bin/php /var/www/html/wp-cron.php > /dev/null 2>&1

Or use wget/curl to trigger it via HTTP if PHP CLI isn’t available:

*/5 * * * * wget -q -O - https://yoursite.com/wp-cron.php?doing_wp_cron > /dev/null 2>&1

Now WordPress’s scheduled tasks run every 5 minutes on a reliable schedule regardless of site traffic, and the cron check no longer runs on every page load.

Adding Custom Scheduled Tasks to WordPress

You can add your own scheduled tasks that run through WordPress’s cron system – useful when your task needs access to WordPress functions, database, or plugins:

<?php
// Add to your theme's functions.php or a custom plugin

// Step 1: Register the task when plugin activates
register_activation_hook(__FILE__, 'schedule_scraper_task');

function schedule_scraper_task() {
    if (!wp_next_scheduled('run_daily_scraper')) {
        wp_schedule_event(time(), 'daily', 'run_daily_scraper');
    }
}

// Step 2: Hook the actual function to the scheduled event
add_action('run_daily_scraper', 'execute_daily_scraper');

function execute_daily_scraper() {
    $logFile = WP_CONTENT_DIR . '/scraper.log';
    $started = date('Y-m-d H:i:s');

    file_put_contents($logFile, "[$started] WordPress cron scraper started." . PHP_EOL, FILE_APPEND);

    // Your scraping logic here
    $response = wp_remote_get('https://books.toscrape.com/', [
        'timeout'    => 30,
        'user-agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    ]);

    if (is_wp_error($response)) {
        file_put_contents($logFile, "[$started] Error: " . $response->get_error_message() . PHP_EOL, FILE_APPEND);
        return;
    }

    $body = wp_remote_retrieve_body($response);
    file_put_contents($logFile, "[$started] Fetched " . strlen($body) . " bytes." . PHP_EOL, FILE_APPEND);
}

// Step 3: Clean up when plugin deactivates
register_deactivation_hook(__FILE__, 'remove_scraper_task');

function remove_scraper_task() {
    $timestamp = wp_next_scheduled('run_daily_scraper');
    wp_unschedule_event($timestamp, 'run_daily_scraper');
}
?>

Adding a Custom Schedule Interval

WordPress only has hourly, twicedaily, and daily built in. Add your own intervals for anything else:

<?php
// Add a custom "every 15 minutes" interval
add_filter('cron_schedules', 'add_custom_cron_intervals');

function add_custom_cron_intervals($schedules) {
    $schedules['every_15_minutes'] = [
        'interval' => 900, // seconds
        'display'  => 'Every 15 Minutes',
    ];

    $schedules['every_30_minutes'] = [
        'interval' => 1800,
        'display'  => 'Every 30 Minutes',
    ];

    $schedules['weekly'] = [
        'interval' => 604800,
        'display'  => 'Once Weekly',
    ];

    return $schedules;
}

// Now use it when scheduling
function schedule_frequent_task() {
    if (!wp_next_scheduled('run_price_check')) {
        wp_schedule_event(time(), 'every_15_minutes', 'run_price_check');
    }
}
register_activation_hook(__FILE__, 'schedule_frequent_task');

add_action('run_price_check', 'execute_price_check');

function execute_price_check() {
    // Price checking logic runs every 15 minutes
    $logFile = WP_CONTENT_DIR . '/price_check.log';
    file_put_contents(
        $logFile,
        '[' . date('Y-m-d H:i:s') . '] Price check ran.' . PHP_EOL,
        FILE_APPEND
    );
}
?>

When to Use WP-Cron vs System Cron

Use WP-Cron when your task needs WordPress functions, the database, or plugin integrations – and timing precision doesn’t matter much. Use a system cron job when you need tasks to run at exact times, when the task is independent of WordPress, or when your site has low traffic and WP-Cron would be unreliable.

For most scraping and automation tasks on a WordPress site, the best approach is both: disable WP-Cron’s visitor-triggered behavior with DISABLE_WP_CRON, then trigger wp-cron.php on a real system cron schedule. You get the reliability of system cron with access to all WordPress functions inside your scheduled tasks.

Frequently Asked Questions

How do I run a PHP script automatically every day?

Set up a system cron job with a daily schedule pointing at your PHP binary and script. Open the crontab with crontab -e and add:

0 0 * * * /usr/bin/php /var/www/html/your-script.php >> /var/www/html/script.log 2>&1

This runs your script every day at midnight. Change the first two fields to adjust the time – 0 6 for 6am, 0 9 for 9am. Always redirect output to a log file with >> log 2>&1 so you can verify the script is running.

Why is my PHP cron job not running?

Work through these checks in order. Confirm the job is saved with crontab -l. Run the exact command manually in the terminal to confirm it works outside of cron. Verify the PHP binary path with which php. Check file permissions – the script must be readable by the cron user. Look at the system log with grep CRON /var/log/syslog to see if cron is attempting to run the job and what error it returns. Most cron failures are caused by wrong paths or permission issues.

Can I run a PHP cron job on shared hosting?

Yes, most shared hosting providers support cron jobs through cPanel’s Cron Jobs manager. Log into cPanel, find Cron Jobs under the Advanced section, and add your command with the PHP binary path your host uses – usually /usr/local/bin/php on shared hosting. Some providers limit how frequently cron jobs can run – typically no more than once every 15 minutes. Check your hosting documentation or ask support if you’re not sure of the restrictions.

How do I stop a PHP cron job from running multiple times at once?

Long-running scripts can overlap if the previous run hasn’t finished when the next one starts. Use a lock file to prevent this:

<?php
$lockFile = __DIR__ . '/cron.lock';

// Check if already running
if (file_exists($lockFile)) {
    $pid = file_get_contents($lockFile);

    // Check if process is actually still running
    if (posix_kill((int)$pid, 0)) {
        exit("Script already running (PID: $pid). Exiting." . PHP_EOL);
    }

    // Process is dead but lock file remains - clean it up
    unlink($lockFile);
}

// Create lock file with current process ID
file_put_contents($lockFile, getmypid());

// Register cleanup on script exit
register_shutdown_function(function() use ($lockFile) {
    if (file_exists($lockFile)) {
        unlink($lockFile);
    }
});

// Your script logic here
echo "Script running with PID: " . getmypid() . PHP_EOL;
?>

What is the minimum interval for a PHP cron job?

On a server you control, cron jobs can run as frequently as every minute – that’s the minimum unit the cron scheduler supports. On shared hosting, many providers restrict this to every 5 or 15 minutes. If you need sub-minute scheduling – running a script every 30 seconds for example – cron isn’t the right tool. Use a long-running PHP daemon or a queue worker instead.

How do I pass arguments to a PHP cron job?

Pass arguments the same way you would on the command line – after the script path. Access them in PHP with the $argv array:

# Crontab entry with arguments
0 0 * * * /usr/bin/php /var/www/html/scraper.php production 50 >> /var/www/html/scraper.log 2>&1
<?php
// Access arguments in the script
$environment = $argv[1] ?? 'development'; // 'production'
$pageLimit   = (int)($argv[2] ?? 10);    // 50

echo "Environment: $environment" . PHP_EOL;
echo "Page limit: $pageLimit" . PHP_EOL;
?>

Output:

Environment: production
Page limit: 50

How do I get notified when a cron job fails?

The simplest approach is to send an email from within the PHP script when something goes wrong:

<?php
function notify_failure($message, $email = 'your@email.com') {
    $subject = '[CRON ALERT] Script failed on ' . date('Y-m-d H:i:s');
    $headers = 'From: cron@yoursite.com' . "\r\n" .
               'Content-Type: text/plain; charset=UTF-8';

    mail($email, $subject, $message, $headers);
}

// Use in your script
$html = scrape_with_retry("https://books.toscrape.com/", 3, 2);

if (!$html) {
    $error = "Scraper failed at " . date('Y-m-d H:i:s') . ". Could not fetch page after 3 retries.";
    notify_failure($error);
    exit(1);
}
?>

Summary

PHP cron jobs are the backbone of any serious automation setup. Once configured correctly they run without intervention – scraping data, sending reports, cleaning databases, checking prices – on whatever schedule you define.

The things that matter most:

  • Always use absolute paths – for the PHP binary, the script file, and every file reference inside the script. Use __DIR__ to build paths dynamically instead of hardcoding them.
  • Always redirect output to a log file>> script.log 2>&1 on every cron entry. Silent cron jobs are impossible to debug.
  • Test the command manually first – run the exact crontab command in your terminal before scheduling it. If it fails there, it will fail in cron.
  • Check the system log when things go wronggrep CRON /var/log/syslog shows exactly when cron fired and what error it returned.
  • Disable WP-Cron on WordPress sites – replace it with a real system cron job for reliable, time-accurate task scheduling.

The most common use case for PHP cron jobs on a scraping blog is running scrapers automatically on a daily schedule. The scraper script in Section 3 connects directly to the patterns in the PHP cURL web scraping guide – pagination, error handling, and MySQL storage all work the same way whether you trigger the script manually or through cron. The only difference is that cron does the triggering for you.

For handling the timeout and connection errors that scraper cron jobs commonly hit, the PHP cURL timeout guide covers retry logic and curl_getinfo() diagnostics in detail.

Next Step

Continue learning by reading our PHP cURL scraping guide.

New to scraping? Start with our PHP web scraper guide.

Build automation systems like PHP price tracker.

Store your data using PHP MySQL scraping guide.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top