Automatic Content Publishing with WordPress Cron: Code Examples and Implementation
If you're using WP-Cron for scheduled tasks in WordPress, you're doing 80% of content automation wrong. Here are real code examples and a working system.

You want to automatically publish content in WordPress every day at 09:00. You install a "Schedule Post" plugin, it doesn't work. You fiddle with WP-Cron settings, you get inconsistent results. The problem is simple: WordPress's cron system isn't a real cron, it's a pseudo-cron dependent on visitor traffic. When I built a system that publishes 4 pieces of content per hour for a news site in 2019, I made the same mistake. For 3 days, content was published at random times, some never published at all. The solution was to bypass WordPress's default logic and build a structure that works integrated with real server cron.
In this article, I'll explain the correct architecture for scheduled content publishing in WordPress, working code examples, and the technical details of the daily 50+ announcement system we built for kamupersonelhaber.com at FUTIA. You'll see code that works in production, not theoretical knowledge.
Why Is WordPress WP-Cron Unreliable?
WordPress's wp-cron.php file is triggered every time a page loads. So if no one visits your site, scheduled tasks don't run. This logic isn't a problem for small blogs, but it's a disaster for automatic content systems.
We experienced this situation on kamupersonelhaber.com: New announcements pulled from the ilan.gov.tr API at 02:00 AM needed to be published. Because site traffic was low at night, some announcements could wait until 08:00 AM. In Google's eyes, content freshness delay means loss of indexing priority.
WP-Cron's second problem is concurrency. If 10 people visit the site at the same time, wp-cron.php is triggered 10 times. Each one checks "are there any due tasks?" Database locks, duplicate operations, server load. On a site publishing 500 posts, this creates chaos.
Solution: Disable WP-Cron and use real server cron. Add this to your wp-config.php file:
define('DISABLE_WP_CRON', true);
Now WordPress will only run at the times you specify, regardless of visitor traffic.
Server Cron Integration with WP-Cron
After disabling WP-Cron, you need to set up a real cron job on the server side. If you have cPanel, Plesk, or SSH access, the process is simple.
Go to the Cron Jobs section in cPanel and add this command:
*/15 * * * * wget -q -O - https://yourdomain.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1
This command triggers wp-cron.php every 15 minutes. The system works even without traffic. Alternatively, you can use curl:
*/15 * * * * curl -s https://yourdomain.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1
I generally prefer curl because timeout control is better. On kamupersonelhaber.com, we trigger it every 10 minutes because announcements need to be published quickly.
If you have SSH access, there's a more powerful method: running WordPress commands directly using wp-cli.
*/10 * * * * cd /home/username/public_html && wp cron event run --due-now >/dev/null 2>&1
This method eliminates HTTP overhead, works directly at the PHP level. In large content systems, the performance difference can be 30-40%.
Writing a Custom Content Publishing Function
WordPress's default scheduled post system is sufficient for simple scenarios. But if you're pulling data from an API, processing it, and publishing it, you need to write your own cron function.
Here's the basic structure I wrote for kamupersonelhaber.com. This code checks the ilan.gov.tr API every day at 02:00, pulls new announcements, and publishes them:
// In functions.php or custom plugin
function futia_schedule_content_sync() {
if (!wp_next_scheduled('futia_daily_content_import')) {
wp_schedule_event(strtotime('02:00:00'), 'daily', 'futia_daily_content_import');
}
}
add_action('wp', 'futia_schedule_content_sync');
function futia_import_and_publish_content() {
// Fetch data from API
$response = wp_remote_get('https://ilan.gov.tr/api/v1/ilanlar?limit=50');
if (is_wp_error($response)) {
error_log('FUTIA Content Import: API error - '. $response->get_error_message());
return;
}
$data = json_decode(wp_remote_retrieve_body($response), true);
if (empty($data['ilanlar'])) {
return;
}
foreach ($data['ilanlar'] as $ilan) {
// Duplicate check
$existing = get_posts([
'post_type' => 'ilan',
'meta_key' => 'ilan_id',
'meta_value' => $ilan['id'],
'posts_per_page' => 1
]);
if (!empty($existing)) {
continue; // Already exists, skip
}
// Create new post
$post_id = wp_insert_post([
'post_title' => sanitize_text_field($ilan['baslik']),
'post_content' => wp_kses_post($ilan['icerik']),
'post_status' => 'publish',
'post_type' => 'ilan',
'post_author' => 1
]);
if ($post_id) {
update_post_meta($post_id, 'ilan_id', $ilan['id']);
update_post_meta($post_id, 'basvuru_tarihi', $ilan['son_tarih']);
// Assign category
wp_set_object_terms($post_id, $ilan['kategori_slug'], 'ilan_kategorisi');
}
}
// Keep log
update_option('futia_last_import', current_time('mysql'));
update_option('futia_last_import_count', count($data['ilanlar']));
}
add_action('futia_daily_content_import', 'futia_import_and_publish_content');
This code has been running on kamupersonelhaber.com for 6 months. It publishes an average of 50-70 announcements per day, never created a duplicate.
Error Handling and Logging
Logging is critical in production systems. What happens if the API crashes, the server slows down, the database locks? It will fail silently and you won't notice.
I use a simple logging system in every cron operation:
function futia_log_cron_event($message, $type = 'info') {
$log_file = WP_CONTENT_DIR. '/futia-cron.log';
$timestamp = current_time('Y-m-d H:i:s');
$log_entry = "[{$timestamp}] [{$type}] {$message}\n";
file_put_contents($log_file, $log_entry, FILE_APPEND);
// Email for critical errors
if ($type === 'error') {
wp_mail('info@futia.net', 'Cron Error', $message);
}
}
// Usage:
futia_log_cron_event('Content import started', 'info');
futia_log_cron_event('API connection error: timeout', 'error');
On kamupersonelhaber.com, I checked logs every day for 3 months. Now I only look at error emails. The system runs on its own.
Bulk Content Publishing and Performance
If you're publishing 100+ posts at once, WordPress's wp_insert_post() function becomes slow. Dozens of hooks are triggered for each post, cache is cleared, database queries run.
When publishing 79,000 doctor profiles on doktorbul.com, we experienced this problem: With the normal method, it took an average of 2.3 seconds per profile. Total 182,000 seconds, or 50 hours. Unacceptable.
Solution: Temporarily disable hooks and do bulk processing.
function futia_bulk_insert_posts($posts_data) {
global $wpdb;
// Disable hooks
remove_action('save_post', 'wp_transition_post_status', 10);
remove_action('wp_insert_post', 'wp_insert_post', 10);
// Also disable hooks from plugins like Yoast SEO, Rank Math
remove_all_actions('save_post');
foreach ($posts_data as $post) {
// Manual SQL insert (faster)
$wpdb->insert(
$wpdb->posts,
[
'post_author' => 1,
'post_date' => current_time('mysql'),
'post_date_gmt' => current_time('mysql', 1),
'post_content' => $post['content'],
'post_title' => $post['title'],
'post_status' => 'publish',
'post_name' => sanitize_title($post['title']),
'post_type' => 'post'
],
['%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s']
);
$post_id = $wpdb->insert_id;
// Meta data
if (!empty($post['meta'])) {
foreach ($post['meta'] as $key => $value) {
add_post_meta($post_id, $key, $value, true);
}
}
}
// Clear cache
wp_cache_flush();
}
With this method, the time per profile on doktorbul.com dropped to 0.4 seconds. 79,000 profiles were completed in 31,600 seconds, or 8.7 hours. 83% performance increase.
Note: This method skips automatic features of SEO plugins like meta descriptions, schema generation. If you need these features, update them with a separate cron after bulk processing.
Scheduled Publishing and Sequencing Strategy
You may want to publish your content at specific intervals rather than all at once. For example, on italyanmutfagi.com, there were 618 recipes, instead of publishing them all on the same day, we published 10 recipes per day over 2 months.
For this, we manipulate WordPress's post_date field:
function futia_schedule_staggered_posts($posts_data, $start_date, $posts_per_day) {
$current_date = strtotime($start_date);
$daily_count = 0;
foreach ($posts_data as $index => $post) {
if ($daily_count >= $posts_per_day) {
$current_date = strtotime('+1 day', $current_date);
$daily_count = 0;
}
// Random hour for each post (between 09:00 - 18:00)
$random_hour = rand(9, 18);
$random_minute = rand(0, 59);
$publish_time = date('Y-m-d H:i:s', strtotime("+{$random_hour} hours +{$random_minute} minutes", $current_date));
wp_insert_post([
'post_title' => $post['title'],
'post_content' => $post['content'],
'post_status' => 'publish',
'post_date' => $publish_time,
'post_date_gmt' => get_gmt_from_date($publish_time)
]);
$daily_count++;
}
}
// Usage:
futia_schedule_staggered_posts($tarifler, '2024-01-01', 10);
This strategy appears to Google as natural content production. Publishing 600 posts at once can give a spam signal.
Cron Monitoring and Debugging
Are your cron jobs running? When did they last run? Did they get errors? I created a simple admin panel to check this:
function futia_cron_status_page() {
add_menu_page(
'Cron Status',
'Cron Monitoring',
'manage_options',
'futia-cron-status',
'futia_render_cron_status'
);
}
add_action('admin_menu', 'futia_cron_status_page');
function futia_render_cron_status() {
$cron_jobs = _get_cron_array();
$now = time();
echo '<div class="wrap">';
echo '<h1>Scheduled Tasks</h1>';
echo '<table class="wp-list-table widefat fixed striped">';
echo '<thead><tr><th>Task</th><th>Next Run</th><th>Status</th></tr></thead><tbody>';
foreach ($cron_jobs as $timestamp => $cron) {
foreach ($cron as $hook => $details) {
$time_diff = $timestamp - $now;
$status = $time_diff < 0? 'Delayed' : 'On Time';
$next_run = date('Y-m-d H:i:s', $timestamp);
echo "<tr>";
echo "<td>{$hook}</td>";
echo "<td>{$next_run}</td>";
echo "<td>{$status}</td>";
echo "</tr>";
}
}
echo '</tbody></table>';
// Last import information
$last_import = get_option('futia_last_import');
$last_count = get_option('futia_last_import_count');
echo '<h2>Last Content Import</h2>';
echo '<p>Date: '. $last_import. '</p>';
echo '<p>Content Count: '. $last_count. '</p>';
echo '</div>';
}
This page appears as a "Cron Monitoring" menu in the WordPress admin panel. It shows all scheduled tasks, their next run times, and delayed tasks.
Real Case: Kamupersonelhaber.com Announcement System
The system I built on kamupersonelhaber.com works as follows:
1. The ilan.gov.tr API is checked every day at 02:00 2. New announcements are detected (from the last 24 hours) 3. Duplicate check is performed for each announcement (ilan_id meta key) 4. New announcements are published as "ilan" custom post type 5. Announcement categories are automatically assigned (education, health, security, etc.) 6. Application deadline is saved as meta 7. Announcements past the application deadline are automatically archived (separate cron)
The system has been running for 6 months. A total of 9,200+ announcements have been published, no manual intervention was ever needed. Monthly organic search traffic reached 40,400.
Technical detail: Announcement content is enriched with Claude Haiku API. Raw API data is 150-200 words, we expand it to 600-800 words. Cost per announcement is ₺0.02.
function futia_enrich_ilan_content($raw_content, $title) {
$api_key = get_option('anthropic_api_key');
$prompt = "Elaborate on this public announcement, explain application requirements, expand job description. Keep original information, add extra context.\n\nAnnouncement: {$title}\n\nContent: {$raw_content}";
$response = wp_remote_post('https://api.anthropic.com/v1/messages', [
'headers' => [
'x-api-key' => $api_key,
'anthropic-version' => '2023-06-01',
'content-type' => 'application/json'
],
'body' => json_encode([
'model' => 'claude-3-haiku-20240307',
'max_tokens' => 1024,
'messages' => [[
'role' => 'user',
'content' => $prompt
]]
]),
'timeout' => 30
]);
if (is_wp_error($response)) {
return $raw_content; // Use original content if error
}
$data = json_decode(wp_remote_retrieve_body($response), true);
return $data['content'][0]['text']?? $raw_content;
}
This function runs for every new announcement. API cost is around ₺120-150 per month, but ad revenue from organic traffic covers this 10 times over.
Alternative: Using Action Scheduler
The Action Scheduler library used by WooCommerce is much more reliable than WP-Cron. It works database-based, queues operations, retries on error.
Installation:
composer require woocommerce/action-scheduler
Usage:
require_once 'vendor/autoload.php';
function futia_schedule_with_action_scheduler() {
if (function_exists('as_schedule_recurring_action')) {
as_schedule_recurring_action(
strtotime('tomorrow 2am'),
DAY_IN_SECONDS,
'futia_daily_import',
[],
'futia-content'
);
}
}
add_action('init', 'futia_schedule_with_action_scheduler');
function futia_process_scheduled_import() {
// Content import code
}
add_action('futia_daily_import', 'futia_process_scheduled_import');
Advantages of Action Scheduler:
- Database-based, traffic independent
- Automatic retry mechanism
- Visual tracking in admin panel
- Parallel processing support
Disadvantage: Extra database tables (actionscheduler_actions, actionscheduler_logs). Requires cleanup on large sites.
I use WP-Cron + server cron on small projects, I prefer Action Scheduler on large projects.
Automatic content publishing with cron in WordPress is technical work, but when set up correctly, your system runs on its own. If you want to build a structure that publishes 50+ pieces of content per day like kamupersonelhaber.com, you can contact me. Reach out email info@futia.net. or write to info@futia.net. As FUTIA, we manage the entire process from site setup to API integration, cron configuration to monthly maintenance.
Frequently Asked Questions
What happens to my existing scheduled posts if I disable WP-Cron?
Your existing scheduled posts are not affected, only the triggering mechanism changes. When you disable WP-Cron and set up server cron, WordPress's scheduled post system continues to work the same way. The difference: Previously it depended on visitor traffic, now it's checked at fixed intervals you set (for example, every 15 minutes). It actually becomes more reliable because it publishes on time even during low-traffic hours.
Can I set up a real cron job on shared hosting?
Yes, most shared hosting providers offer the ability to define cron jobs through cPanel or Plesk. You can go to the 'Cron Jobs' section in cPanel and add a command. Even if you don't have SSH access, you can use web-based cron services (for example, EasyCron, cron-job.org). These services send HTTP requests to your site's wp-cron.php file at the intervals you specify. Free plans are usually sufficient for 5-15 minute intervals.
How do I prevent duplicates in content pulled from APIs?
The most reliable method is to store a unique ID for each content from the API as WordPress meta data. For example, each announcement from ilan.gov.tr has an 'ilan_id' value. Before adding new content, I query this meta value with get_posts(). If a post with the same ilan_id exists, I skip the addition. Alternatively, you can check via post_title or post_name, but since these can change, the meta key method is safer. No duplicates have occurred on kamupersonelhaber.com for 6 months.
Do SEO plugins (Yoast, Rank Math) cause problems when adding bulk content?
Yes, especially if you're doing bulk insertion by disabling hooks, the automatic features of SEO plugins (meta description, schema, XML sitemap update) won't work. The solution is two-stage: First add raw content quickly, then update SEO meta data with a separate cron job. For example, for Yoast you can manually fill meta keys like wpseo_title, wpseo_metadesc. Or after bulk insertion, you can generate meta data using Yoast's own API. We used this method when adding 79,000 profiles on doktorbul.com.
My cron jobs aren't working, how do I debug?
First step: In WordPress admin panel, go to Tools > Site Health > Info > Scheduled Events and check if your cron jobs are listed. Second step: Examine server cron logs (usually /var/log/cron or Cron Job Logs in cPanel). Third step: Open debug.log in the wp-content folder (set WP_DEBUG_LOG to true in wp-config.php) and add error_log() to your cron functions. I log the start and end time of each cron operation. If you don't see any records in the log, your cron job isn't being triggered at all, check server-side settings.
Want to apply one of the techniques from this post? Fill out a short form and we'll email you a free preview audit within 48 hours.