Blog · Guides

Automation Recipes: Scheduled Tasks Every Site Should Run

WordPress ships with its own internal scheduler, WP-Cron, but it has a well-known weakness: it only fires when someone visits the site. On a low-traffic page, that “nightly” task might run at 4 a.m., or it might run at 11 a.m. when the first visitor shows up, or not at all if traffic is thin enough. For anything time-sensitive, real system cron paired with wp-cli is the more reliable tool. Below are four recipes worth putting on every site: a nightly database optimize, a weekly link check, cache warming after purges, and a daily report email.

Step one: hand scheduling over to real cron

Start by disabling WP-Cron’s page-load trigger in wp-config.php:

define('DISABLE_WP_CRON', true);

Then let system cron run WordPress’s own scheduled hooks on a fixed interval, so nothing that depends on wp_schedule_event() (post publishing, plugin housekeeping, and so on) stops working:

*/5 * * * * cd /var/www/html && wp cron event run --due-now --allow-root >> /var/log/wp-cron.log 2>&1

Adjust the path to your WordPress install and drop --allow-root if your cron user isn’t root. With that in place, WP-Cron events fire on a predictable clock instead of a traffic gamble, and you can layer the recipes below on top of it.

Nightly database optimization

Post revisions, spam comments, expired transients, and orphaned metadata accumulate in the database over time. Left alone, they bloat table sizes and slow down queries. wp-cli makes cleanup a one-line job:

# Clear expired transients, then optimize tables
30 2 * * * cd /var/www/html && wp transient delete-expired --allow-root && wp db optimize --allow-root >> /var/log/db-maintenance.log 2>&1

Running this at 2:30 a.m. keeps it off peak hours. wp db optimize runs the equivalent of an OPTIMIZE TABLE pass across your WordPress tables, reclaiming space and rebuilding indexes. It’s a light job, not a substitute for a proper backup or a database review if you’re seeing real performance problems, but it’s cheap insurance to run every night.

Weekly link checks

Broken internal links quietly hurt both user experience and SEO, and they tend to appear after content edits, plugin removals, or permalink changes nobody remembers making. A simple weekly script using wp-cli to list permalinks and curl to check their status codes catches most of them:

#!/bin/bash
# check-links.sh
cd /var/www/html
wp post list --post_status=publish --field=url --allow-root | while read -r url; do
  status=$(curl -o /dev/null -s -w \"%{http_code}\" \"$url\")
  if [ \"$status\" -ge 400 ]; then
    echo \"BROKEN ($status): $url\"
  fi
done

Schedule it for a quiet Sunday morning:

0 3 * * 0 /usr/local/bin/check-links.sh >> /var/log/link-check.log 2>&1

This only checks your own published URLs, which catches internal 404s from renamed slugs or deleted pages. If you want outbound link checking too, extend the loop to pull hrefs from post content and test those as well, though be mindful of hammering third-party sites with too many requests at once.

Cache warming after a purge

A full-page cache (LiteSpeed Cache is the natural fit on a LiteSpeed server) makes the first visitor after any cache purge pay the full page-generation cost. On a busy site that’s a bad first impression; on a slow one it can look like downtime. Warming the cache immediately after a scheduled purge avoids that cold-start hit.

If you’re running LiteSpeed Cache, its wp-cli integration can purge and you can follow it with a crawl of key URLs:

#!/bin/bash
# warm-cache.sh
cd /var/www/html
wp litespeed-purge all --allow-root
sleep 5
curl -s -o /dev/null https://example.com/
curl -s -o /dev/null https://example.com/shop/
curl -s -o /dev/null https://example.com/blog/
# or crawl the sitemap for full coverage
wget --quiet --spider --recursive --level=1 --no-clobber https://example.com/sitemap.xml
45 2 * * * /usr/local/bin/warm-cache.sh >> /var/log/cache-warm.log 2>&1

Run it right after your nightly optimize job so the cache is warm before morning traffic. If Cloudflare sits in front of the site as your edge cache, the same warming script doubles as a way to repopulate Cloudflare’s cache after a purge, since the first request after a purge is what populates the edge again. Keeping your origin IP unpublished behind Cloudflare doesn’t change this behavior, but it’s worth remembering the warm-up requests still need to route through the edge, not straight to origin, or you’ll warm the wrong cache.

Daily report emails

A short daily digest, delivered quietly to your inbox, is often the first sign something’s drifting before it becomes a real problem. Combine a few wp-cli checks into one script and pipe the output through mail:

#!/bin/bash
# daily-report.sh
cd /var/www/html
echo \"WordPress core:\"
wp core version --allow-root
echo
echo \"Plugins needing updates:\"
wp plugin list --update=available --allow-root
echo
echo \"Themes needing updates:\"
wp theme list --update=available --allow-root
echo
echo \"Disk usage for uploads:\"
du -sh wp-content/uploads
0 7 * * * /usr/local/bin/daily-report.sh | mail -s \"Daily Site Report: example.com\" [email protected]

This is intentionally lightweight: version numbers, pending updates, and a disk usage sanity check. It’s not a substitute for a proper monitoring stack, but a plain text email you actually read every morning beats a dashboard you never open.

Putting the crontab together

A full night’s schedule for one site might look like this:

*/5 * * * * cd /var/www/html && wp cron event run --due-now --allow-root >> /var/log/wp-cron.log 2>&1
30 2 * * * cd /var/www/html && wp transient delete-expired --allow-root && wp db optimize --allow-root >> /var/log/db-maintenance.log 2>&1
45 2 * * * /usr/local/bin/warm-cache.sh >> /var/log/cache-warm.log 2>&1
0 3 * * 0 /usr/local/bin/check-links.sh >> /var/log/link-check.log 2>&1
0 7 * * * /usr/local/bin/daily-report.sh | mail -s \"Daily Site Report: example.com\" [email protected]

Stagger the times so jobs don’t collide, and always redirect output to a log file at first so you can debug quietly before trusting a job to run unattended. If you’re on managed hosting like ServerBorn, some of this (WP-Cron handling, object caching via Redis, edge caching through Cloudflare) is already configured server-side, so check what your host provides before duplicating it yourself.

The takeaway

None of these jobs are glamorous, and that’s the point. A nightly database optimize, a weekly link sweep, a cache warm after every purge, and a short daily email cost almost nothing to run and catch small problems (bloated tables, dead links, cold caches, forgotten updates) long before they turn into real incidents. Set them up once with real cron and wp-cli, and your site stays quietly healthy in the background.