jekcms doesn't use a cron daemon by default for scheduled posts — it falls back to a lightweight, throttled visitor-triggered check (PseudoCron) that adds no perceptible delay to any request. High-traffic sites publish within a minute; quiet sites may see a short delay. This post covers the real mechanism and how to wire up a traditional cron job for tighter guarantees.
Scheduled publishing in jekcms doesn't depend on a database check running inline before every page renders. The mechanism — called PseudoCron — hooks into the same request but does its work after the response has already been sent to the visitor, using fastcgi_finish_request() or litespeed_finish_request() where the server supports it, or a self-loopback request as a fallback on setups that don't (mod_php, mostly). A visitor never waits on it. The check itself is throttled to once every 60 seconds using small marker files in the cache directory, not a database row, so most requests don't touch the database for this at all.
The Trade-Off: Traffic-Dependent Accuracy
The trade-off is the same one any visitor-triggered scheduler has: if nobody visits the site for a stretch of time, nothing checks whether a post is due. In practice this floor is 60 seconds on a site with steady traffic — the throttle, not the traffic, becomes the limit. On a quiet site, jekcms also periodically re-checks on its own even without new visits (at least once every 5 minutes when something is scheduled soon), so the worst case on a genuinely idle site is a delay in that range rather than an indefinite one. This is a documented trade-off, not a bug — and it's exactly why jekcms also supports a real cron for anyone who wants tighter guarantees.
Setting Up Real Cron: cron.php
jekcms ships a single, real cron entry point at the root of every installation: cron.php. It handles scheduled post publishing along with a handful of other periodic jobs (AI bulk-job resumption, license heartbeat). Point your host's crontab at it once a minute:
# Open crontab editor
crontab -e
# Add this line (every minute)
* * * * * php /var/www/html/jekcms/cron.php >/dev/null 2>&1
# Verify the entry
crontab -l
When cron.php runs — whether from CLI or triggered externally — it leaves a freshness marker behind. While that marker is recent (within about 20 minutes), PseudoCron's visitor-triggered fallback disables itself entirely: zero extra work gets added to page requests on a site with working cron. The two mechanisms are two ways of triggering the exact same underlying scheduler, not two competing systems.
Measuring Your Real-World Delay Window
You can estimate your actual delay risk from server access logs. Count unique requests per hour over the past 30 days. If every hour shows at least one request, your maximum scheduling delay — without a real cron — is 60 seconds, the same as it would be on a busy VPS.
If your quietest hours — typically 02:00 to 06:00 local time — show long visitor gaps, those hours represent your exposure window; the periodic self-recheck caps it at a few minutes rather than letting it stretch indefinitely, but if even a few minutes of delay overnight matters for your content, that's the case for setting up real cron rather than relying on the fallback.
How the Mechanism Works Internally
The simplified flow, from includes/PseudoCron.php:
// Called from bootstrap on every request
public static function maybeSchedule(): void {
if (PHP_SAPI === 'cli') return;
// Real cron ran recently? Do nothing at all.
$real = filemtime(CACHE_PATH . '/.cron-real.flag');
if ($real && time() - $real < 1200) return;
// Something not due yet? Do nothing.
$next = (int) file_get_contents(CACHE_PATH . '/.cron-next-due');
if (time() < $next) return;
// Checked too recently? Do nothing (60s floor).
$last = filemtime(CACHE_PATH . '/.pseudo-cron.last');
if ($last && time() - $last < 60) return;
// Otherwise: do the real work only AFTER the visitor has their response.
register_shutdown_function([self::class, 'runAfterResponse']);
}
Everything is guarded with a file lock (flock) so two simultaneous visitors can't both trigger the publish pass at once — the second one simply finds the lock held and returns immediately.
Cache Behaviour After Publishing
jekcms has a single page cache layer, not separate page/object/query caches — Cache::flush() takes no arguments and clears everything when called. In practice you rarely need to think about this: PAGE_CACHE_LIFETIME defaults to 300 seconds, so a cached homepage self-expires within five minutes even in the worst case, which is well inside the delay window discussed above.
Monitoring Cron Execution
cron.php doesn't write its own persistent log file. When run from the CLI it prints a short status line to stdout; redirect that yourself if you want a history:
* * * * * php /var/www/html/jekcms/cron.php >> /home/username/logs/jekcms-cron.log 2>&1
If that log stops growing, the cron job has stopped running — check it with your host's cron monitoring or a simple daily script that compares the file's modification time.
Shared Hosting Without Cron Access
Most of the time, you don't need to do anything here — PseudoCron is the default and it's already handling scheduling automatically, as described above. For sites that want the tighter accuracy of real cron but whose host doesn't expose crontab at all, cron.php also accepts an authenticated HTTP trigger: set a CRON_TOKEN value in your environment configuration, and an external HTTP request that includes a matching X-Cron-Secret header (checked with a constant-time comparison) is allowed to run it, exactly like a real cron invocation would. Requests without a valid token are rejected outright.
This is a separate thing from the ?tick=1 endpoint you may notice in server logs — that one is PseudoCron's own internal self-loopback call, unauthenticated by design but harmless: it's rate-limited to the same 60-second floor and does nothing at all while a real cron marker is fresh. It's not meant to be called from outside and doesn't need to be.
External Cron Service Options
- cron-job.org — free tier supports custom HTTP headers down to 60-second intervals. Point a job at your
cron.phpURL with theX-Cron-Secretheader set. - Any uptime/HTTP monitoring tool that supports custom request headers — several popular ones do on their paid tiers, which lets you piggyback scheduling on a check you'd be running anyway.
- An automation platform you already use (n8n, Zapier, etc.) — a scheduled HTTP request node pointed at
cron.phpwith the right header works just as well and keeps everything in one place if you're already running other jekcms automation through it.
Practical Tips for Reliable Scheduling
- Schedule posts at least a couple of minutes into the future to give the 60-second throttle room to work
- Stagger multiple posts by a few minutes rather than scheduling them at the exact same minute
- Use the admin dashboard's scheduled posts view to verify upcoming publications at a glance
- Test your setup with a private post before relying on it for time-sensitive content
- Each jekcms site on the same server needs its own cron entry — they don't share the scheduling mechanism
- The system respects the timezone configured via the
SITE_TIMEZONEconstant inconfig/config.php