FUTIA
OTOMASYON8 min read

Automatic Content Publishing with WordPress Cron: Real Code Guide

I completely automated content production using cron jobs in WordPress. Real code examples and practical mistakes from my 6 years of experience.

Automatic Content Publishing with WordPress Cron: Real Code Guide
Miraç Eroğlu
May 15, 2026

In the last 2 years, I've set up cron-based automation for over 40 WordPress sites. 618 recipes for italyanmutfagi.com, daily 50+ job postings for kamupersonelhaber.com, 79,000 profiles for doktorbul.com—all published via cron jobs. In this article, I'll explain how to use WordPress's own cron system with real production code. Not theoretical knowledge, but systems I've been running in production for 2 years, where I've encountered and fixed errors.

Most guides just show the wp_schedule_event() function and move on. I'll show you real scenarios: fetching data from APIs and creating content, error handling, rate limiting, duplicate control, memory optimization. You'll find every problem I've encountered and its solution while serving Turkish brands from the Netherlands.

How the WordPress Cron System Works

WordPress's wp-cron.php file isn't a real cron job, it's pseudo-cron. It triggers on every page visit and runs scheduled tasks. This method works on shared hosting but has serious disadvantages.

I use two methods in FUTIA projects:

1. Low-traffic sites (under 500 visits per day): WordPress's own wp-cron 2. High-traffic or critical tasks: System cron + wp-cron.php disabled

In the second method, I add this to wp-config.php:

define('DISABLE_WP_CRON', true);

Then I add a real task to the server crontab:

*/15 * * * * curl https://siteadi.com/wp-cron.php?doing_wp_cron > /dev/null 2>&1

This code triggers wp-cron.php every 15 minutes. It runs reliably, independent of page load. I use this method on kamupersonelhaber.com because I need to fetch data from the ilan.gov.tr API 3 times a day—I can't depend on visitor traffic.

Creating a Basic Cron Job

First example: Running a function every day at 09:00. In theme functions.php or custom plugin:

function futia_schedule_daily_task() {
    if (!wp_next_scheduled('futia_daily_content')) {
        wp_schedule_event(strtotime('09:00:00'), 'daily', 'futia_daily_content');
    }
}
add_action('wp', 'futia_schedule_daily_task');

function futia_run_daily_content() {
    // Your content generation code goes here
    error_log('FUTIA: Daily content task ran at ' . current_time('mysql'));
}
add_action('futia_daily_content', 'futia_run_daily_content');

This code hooks into wp, checks with wp_next_scheduled() on every page load. If the task is already scheduled, it doesn't create a duplicate.

But note: strtotime('09:00:00') works according to server time. To use WordPress's own timezone:

$timezone = wp_timezone();
$next_run = new DateTime('tomorrow 09:00', $timezone);
wp_schedule_event($next_run->getTimestamp(), 'daily', 'futia_daily_content');

I learned this on doktorbul.com. In the initial setup, the server was on UTC, the task ran at 02:00 at night. That's 05:00 Turkey time. The code above solved the problem.

Fetching Data from API and Creating Content

A real scenario: For kamupersonelhaber.com, I fetch data from the ilan.gov.tr API every 8 hours and publish it as WordPress posts. Here's a simplified version of the production code:

function futia_schedule_ilan_fetch() {
    if (!wp_next_scheduled('futia_fetch_ilanlar')) {
        wp_schedule_event(time(), 'futia_8hours', 'futia_fetch_ilanlar');
    }
}
add_action('wp', 'futia_schedule_ilan_fetch');

function futia_custom_cron_schedules($schedules) {
    $schedules['futia_8hours'] = array(
        'interval' => 28800,
        'display' => 'Every 8 Hours'
    );
    return $schedules;
}
add_filter('cron_schedules', 'futia_custom_cron_schedules');

function futia_fetch_and_create_ilanlar() {
    $api_url = 'https://ilan.gov.tr/api/v1/ilanlar';
    $response = wp_remote_get($api_url, array(
        'timeout' => 30,
        'headers' => array(
            'Authorization' => 'Bearer ' . get_option('ilan_api_key')
        )
    ));

    if (is_wp_error($response)) {
        error_log('FUTIA: API error - ' . $response->get_error_message());
        return;
    }

    $body = json_decode(wp_remote_retrieve_body($response), true);
    
    if (empty($body['data'])) {
        return;
    }

    foreach ($body['data'] as $ilan) {
        // Duplicate check
        $existing = get_posts(array(
            'post_type' => 'ilan',
            'meta_key' => 'ilan_id',
            'meta_value' => $ilan['id'],
            'posts_per_page' => 1
        ));

        if (!empty($existing)) {
            continue;
        }

        // Create post
        $post_id = wp_insert_post(array(
            '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']);
        }
    }
}
add_action('futia_fetch_ilanlar', 'futia_fetch_and_create_ilanlar');

Points to note in this code:

1. wp_remote_get() timeout is 30 seconds. If the API is slow, PHP won't timeout. 2. Duplicate check with meta_key. If the same ilan_id exists, skip it. 3. sanitize_text_field() and wp_kses_post() are mandatory for security. 4. error_log() for error tracking. I use Sentry but logging is sufficient for beginners.

This system has been running on kamupersonelhaber.com for 8 months, publishing an average of 50 job postings per day. I've never had to manually intervene.

Memory and Performance Optimization

When creating 618 recipes for italyanmutfagi.com, I got a memory error in the first version. I tried to create 618 posts in a single cron job, and the 256MB PHP memory limit wasn't enough.

Solution: Batch processing. Create 20 posts in each run, do the next 20 in the next run:

function futia_batch_recipe_creation() {
    $batch_size = 20;
    $processed = get_option('futia_processed_recipes', 0);
    $total = 618;

    if ($processed >= $total) {
        return; // All recipes created
    }

    $recipes = futia_get_recipe_data($processed, $batch_size);

    foreach ($recipes as $recipe) {
        $post_id = wp_insert_post(array(
            'post_title' => $recipe['title'],
            'post_content' => $recipe['content'],
            'post_status' => 'publish',
            'post_type' => 'recipe'
        ));

        if ($post_id) {
            // Schema.org Recipe markup
            update_post_meta($post_id, 'recipe_schema', json_encode(array(
                '@context' => 'https://schema.org/',
                '@type' => 'Recipe',
                'name' => $recipe['title'],
                'recipeIngredient' => $recipe['ingredients'],
                'recipeInstructions' => $recipe['instructions']
            )));
        }
    }

    update_option('futia_processed_recipes', $processed + $batch_size);
}
add_action('futia_batch_recipes', 'futia_batch_recipe_creation');

With this approach, 618 recipes were created in 31 runs (618/20). One batch every 15 minutes, 7.75 hours total. Memory usage never exceeded 128MB.

Another optimization: wp_suspend_cache_addition(true). When processing large data, WordPress object cache bloats, which uses memory:

function futia_batch_recipe_creation() {
    wp_suspend_cache_addition(true);
    
    // Your batch operations
    
    wp_suspend_cache_addition(false);
}

Rate Limiting and API Compliance

When creating 79,000 doctor profiles on doktorbul.com, I used an external API. The API's rate limit was 60 requests per minute. If I exceeded this, I'd get an IP ban.

Solution: Add sleep() after wp_remote_get():

function futia_fetch_doctor_profiles() {
    $batch_size = 50; // 60 limit per minute, safe margin
    $doctors = futia_get_pending_doctors($batch_size);

    foreach ($doctors as $doctor) {
        $response = wp_remote_get('https://api.example.com/doctors/' . $doctor['id']);
        
        if (!is_wp_error($response)) {
            $data = json_decode(wp_remote_retrieve_body($response), true);
            futia_create_doctor_post($data);
        }

        sleep(1); // Wait 1 second, max 60 requests per minute
    }
}

This code waits 1 second between each request. It takes 50 seconds for 50 profiles but doesn't violate API rules. I ran this for 79,000 profiles in 1,580 batches without any issues.

Another method: Rate limit tracking with Transient API:

function futia_api_call_with_limit($url) {
    $calls = get_transient('futia_api_calls');
    
    if ($calls === false) {
        $calls = 0;
    }

    if ($calls >= 60) {
        error_log('FUTIA: Rate limit reached, waiting');
        return false;
    }

    $response = wp_remote_get($url);
    set_transient('futia_api_calls', $calls + 1, 60); // 60 second TTL
    
    return $response;
}

The transient resets after 60 seconds, starting a new cycle. This method is more efficient than sleep() because there's no unnecessary waiting.

Error Handling and Logging

In production, everything can go wrong. APIs go down, database connections drop, PHP times out. I add try-catch and detailed logging to every cron job:

function futia_safe_cron_execution() {
    try {
        $start_time = microtime(true);
        
        // Your main operation
        futia_fetch_and_create_content();
        
        $end_time = microtime(true);
        $duration = round($end_time - $start_time, 2);
        
        error_log(sprintf(
            'FUTIA: Cron completed in %s seconds, memory: %s MB',
            $duration,
            round(memory_get_peak_usage(true) / 1024 / 1024, 2)
        ));
        
    } catch (Exception $e) {
        error_log('FUTIA: Cron failed - ' . $e->getMessage());
        
        // Send Slack or email notification
        wp_mail(
            'info@futia.net',
            'Cron Job Failed',
            $e->getMessage() . "\n\n" . $e->getTraceAsString()
        );
    }
}
add_action('futia_daily_content', 'futia_safe_cron_execution');

This code logs duration and memory usage on every run. If there's an error, it sends an email. I use Slack webhooks in production but wp_mail() is sufficient for beginners.

Another important point: Checking if the cron job is running. I do this with wp_get_scheduled_event():

function futia_check_cron_health() {
    $event = wp_get_scheduled_event('futia_daily_content');
    
    if (!$event) {
        error_log('FUTIA: Cron job is not scheduled!');
        // Reschedule
        wp_schedule_event(time(), 'daily', 'futia_daily_content');
    }
}
add_action('admin_init', 'futia_check_cron_health');

This code checks if the cron job is scheduled every time you enter the admin panel. If it's been deleted, it recreates it.

Content Generation Integration with Claude API

On memuratamalari.com, I generate daily analysis articles using the Claude Haiku API. The cron job triggers every day at 08:00, sends that day's news to Claude, and publishes the returned analysis as a post:

function futia_generate_daily_analysis() {
    $today_news = futia_get_today_news(); // That day's news
    
    $prompt = "Analyze the following civil servant news, summarize general trends:\n\n";
    foreach ($today_news as $news) {
        $prompt .= "- " . $news['title'] . "\n";
    }

    $response = wp_remote_post('https://api.anthropic.com/v1/messages', array(
        'timeout' => 60,
        'headers' => array(
            'x-api-key' => get_option('claude_api_key'),
            'anthropic-version' => '2023-06-01',
            'content-type' => 'application/json'
        ),
        'body' => json_encode(array(
            'model' => 'claude-3-haiku-20240307',
            'max_tokens' => 1024,
            'messages' => array(
                array(
                    'role' => 'user',
                    'content' => $prompt
                )
            )
        ))
    ));

    if (is_wp_error($response)) {
        error_log('FUTIA: Claude API error - ' . $response->get_error_message());
        return;
    }

    $body = json_decode(wp_remote_retrieve_body($response), true);
    $analysis = $body['content'][0]['text'];

    wp_insert_post(array(
        'post_title' => 'Daily Analysis: ' . date('d.m.Y'),
        'post_content' => wpautop($analysis),
        'post_status' => 'publish',
        'post_type' => 'post',
        'post_category' => array(get_cat_ID('Analyses'))
    ));
}
add_action('futia_daily_analysis', 'futia_generate_daily_analysis');

This system has been running for 6 months and played a major role in reaching 40,400 daily organic searches. Claude Haiku is fast and cheap (1M tokens $0.25), daily cost is around $0.02.

Debugging and Testing

Waiting for wp-cron.php to run when testing cron jobs is a waste of time. I manually trigger with do_action():

// functions.php or custom plugin
if (isset($_GET['test_cron']) && current_user_can('manage_options')) {
    do_action('futia_daily_content');
    die('Cron manually triggered');
}

Then I go to https://siteadi.com/?test_cron in the browser, the cron job runs immediately. Of course, I remove this code in production.

Another method: Using WP-CLI. If you have SSH access:

wp cron event run futia_daily_content

This command runs the cron job immediately and shows the output in the terminal. I use this when testing server-side.

To see the list of cron jobs:

wp cron event list

This command shows all scheduled tasks and their next run times. Very useful for debugging.

Real Production Scenarios

On diolivo.com.tr, I integrated the CartBounty cart recovery system with a cron job. It checks abandoned carts every 2 hours, and if 24 hours have passed, it sends an email:

function futia_abandoned_cart_reminder() {
    global $wpdb;
    
    $carts = $wpdb->get_results(
        "SELECT * FROM {$wpdb->prefix}cartbounty 
        WHERE cart_time < DATE_SUB(NOW(), INTERVAL 24 HOUR) 
        AND email_sent = 0 
        LIMIT 50"
    );

    foreach ($carts as $cart) {
        $user_data = maybe_unserialize($cart->cart_contents);
        
        wp_mail(
            $cart->email,
            'You have items left in your cart',
            futia_generate_cart_email($user_data)
        );
        
        $wpdb->update(
            $wpdb->prefix . 'cartbounty',
            array('email_sent' => 1),
            array('id' => $cart->id)
        );
    }
}
add_action('futia_cart_reminder', 'futia_abandoned_cart_reminder');

This system contributed to 340% traffic growth in 6 months. Cart recovery rate is 18%, each email generates an average of ₺47 in revenue.

On futia.net, I set up a multi-step cron system to produce 2,000+ videos in 3 months:

1. Every day at 01:00: Generate scripts (Claude API) 2. Every day at 03:00: Create voiceovers (ElevenLabs API) 3. Every day at 05:00: Render videos (Remotion) 4. Every day at 07:00: Upload to YouTube

Each step is a separate cron job, using the output of the previous step. This pipeline has been running uninterrupted for 3 months.

If you want to set up such automations, you can contact me. WhatsApp: +90 532 491 17 05 or info@futia.net. I have 2 years of production experience in WordPress + cron + API integrations, and I can develop custom solutions for your project too.

Frequently Asked Questions

How are WordPress cron jobs different from real cron jobs?

WordPress uses the wp-cron.php pseudo-cron system, triggered on page visits. Real cron jobs are scheduled server-side and run independently of visitor traffic. For high-traffic or critical tasks, I recommend setting DISABLE_WP_CRON to true in wp-config.php and adding a real task to the server crontab. I use this method in FUTIA projects when traffic is over 500/day.

My cron job isn't working, how do I debug it?

First, check if the task is scheduled with wp_get_scheduled_event(). Then add error_log() to log when it runs. For manual testing, use do_action() or run 'wp cron event run task_name' in WP-CLI. In production, I add try-catch and email notifications to every cron job so I'm immediately notified when there's an error. Error tracking services like Sentry are also very useful.

I'm getting memory errors when processing large data, what's the solution?

Use batch processing. Instead of processing all data at once, process in groups of 20-50, track progress with get_option() in each batch. Disable WordPress object cache with wp_suspend_cache_addition(true). You can increase PHP memory_limit but the real solution is batching. On italyanmutfagi.com, I created 618 recipes in batches of 20 and never had memory issues.

What should I do to avoid exceeding API rate limits?

Add sleep(1) between each API request or track call count with Transient API. On doktorbul.com, I set a limit of 50 requests per minute for 79,000 profiles and waited 1 second after each request. The transient method is more efficient: set_transient('api_calls', $count, 60) with 60 second TTL, return false when limit is reached. This approach is faster than sleep() because there's no unnecessary waiting.

How do I integrate Claude API into a cron job?

Make a request to the Anthropic API with wp_remote_post(), set timeout to 60 seconds because responses can be slow. Use claude-3-haiku-20240307 as the model, it's fast and cheap (1M tokens $0.25). Create the prompt dynamically, parse the response and publish with wp_insert_post(). On memuratamalari.com, I generate daily analyses and it's been running smoothly for 6 months. Manage the API key with get_option(), don't hardcode it.

ABOUT THE AUTHOR
Miraç Eroğlu

Hacettepe mezunu, 6 yıldır sosyal medya, 2 yıldır AI otomasyon.

Learn more →

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.