Skip to content

How to Set Up Odoo Scheduled Actions (Cron Jobs)

DeployMonkey Team · March 11, 2026 7 min read

Odoo's scheduled actions — its internal cron system — run tasks automatically on a schedule: sending payment reminders, updating stock forecasts, generating reports, syncing data with external systems. Getting them right means your Odoo keeps itself maintained without manual intervention. Getting them wrong means silent failures and stale data.

How Odoo's Scheduler Works

Odoo does not use the operating system's cron daemon. It has its own scheduler implemented in Python, running inside dedicated worker processes (configured by max_cron_threads in odoo.conf).

The scheduler wakes up periodically (by default every minute) and checks the database for scheduled actions that are due to run. It then executes them in dedicated cron worker processes, separate from the HTTP workers that handle web requests.

This architecture means:

  • Cron jobs can run even when no users are active
  • Cron jobs don't compete with HTTP workers for resources (unless you set max_cron_threads = 0)
  • All cron job configuration is in the database, not on the filesystem

Viewing Built-In Scheduled Actions

To see all scheduled actions, you need Developer Mode enabled. Go to:

Settings → Technical → Automation → Scheduled Actions

Common built-in actions include:

  • Send payment reminders — overdue invoice follow-ups
  • Fetch emails — IMAP/POP3 polling
  • Update reorder rules — inventory replenishment
  • Generate recurring entries — accounting automation
  • Clean old sessions — maintenance

Configuring max_cron_threads

In your odoo.conf:

[options]
workers = 4           ; HTTP workers
max_cron_threads = 1  ; dedicated cron workers

Key rules:

  • Set max_cron_threads = 0 to disable the scheduler entirely (rarely appropriate)
  • For most deployments, max_cron_threads = 1 is sufficient
  • Increase to 2 if you have many heavy cron jobs that need to run concurrently
  • Cron workers count against your total memory budget — each uses the same memory limits as HTTP workers

With workers = 0 (single-threaded mode), cron jobs still run, handled by a background thread in the main process. This is not suitable for production.

Creating a Custom Scheduled Action

Step 1: Enable Developer Mode

Scheduled actions require Developer Mode. See our guide on enabling Odoo Developer Mode.

Step 2: Create the Action

  1. Go to Settings → Technical → Automation → Scheduled Actions
  2. Click New
  3. Fill in the form:
Name: Sync External Inventory
Model Name: product.product (the model this action operates on)
Execute Every: 1 Hour
Next Execution Date: [set to when you want first run]
Number of Calls: -1 (unlimited, runs forever)
Code: (see below)

Step 3: Write the Action Code

The "Code" field contains Python code that runs with env available as the Odoo environment:

# Simple example: log all expired subscriptions
records = env['product.product'].search([
('active', '=', True),
('type', '=', 'service'),
])
for product in records:
if product.list_price == 0:
    product.message_post(body="Zero-price service product detected")

For complex logic, define a method on the model and call it from the scheduled action:

# In your custom module's model:
class ProductProduct(models.Model):
_inherit = 'product.product'

def _cron_sync_inventory(self):
    products = self.search([('active', '=', True)])
    for product in products:
        # your sync logic here
        pass
# In the scheduled action's Code field:
env['product.product']._cron_sync_inventory()

This is the recommended pattern — keeps the business logic testable and in the module where it belongs.

Interval Settings Explained

FieldDescriptionExample
Execute EveryHow often the action runs1 Hour, 30 Minutes, 1 Day
Next Execution DateWhen the next run is scheduledAuto-updated after each run
Number of Calls-1 = unlimited, N = run N more timesSet to 1 for one-time actions
PriorityLower number = higher priority when multiple actions are dueDefault: 5

Odoo does not guarantee exact timing — if a cron worker is busy when the scheduled time arrives, the action runs as soon as a worker is available. For time-sensitive operations, reduce max_cron_threads latency by increasing the worker count or reducing the interval.

Debugging Failed Cron Jobs

Check the Scheduled Action Log

After each run, Odoo records whether it succeeded or failed. On the scheduled action form, look for a log field or check the action's last execution status. In older Odoo versions, errors are logged to the Odoo log file.

# Filter Odoo logs for cron errors
grep -i "cron\|scheduler\|ir.cron" /var/log/odoo/odoo.log | grep -i "error\|exception"

# Docker
docker compose logs odoo | grep -i "ir.cron" | grep -i "error"

Common Cron Problems and Fixes

ProblemCauseFix
Action never runsmax_cron_threads = 0, or workers = 0 with threading disabledSet max_cron_threads = 1 in odoo.conf, restart
Action runs but does nothingDomain filter returns no recordsTest the search domain in the Odoo shell
Action fails with AccessErrorCron runs as low-privilege userUse env(user=SUPERUSER_ID) in the action code
Action takes too long and times outlimit_time_real_cron too lowIncrease limit_time_real_cron in odoo.conf
Action runs too infrequentlyInterval set too longEdit the scheduled action interval setting

Manually Triggering a Cron Job

To test a scheduled action without waiting for the interval:

  1. Open the scheduled action in Settings → Technical → Automation → Scheduled Actions
  2. Click Run Manually (the button may be labeled "Execute Manually" in some versions)
  3. Check logs immediately after
# Or run it from the Odoo shell:
docker compose exec odoo odoo shell -d your_db
# Then in the shell:
env['ir.cron'].browse(ACTION_ID).method_direct_trigger()

How DeployMonkey Monitors Cron Jobs

DeployMonkey monitors cron worker health as part of instance monitoring. If cron workers stop processing (a common symptom of memory exhaustion or deadlocks), our monitoring detects the stall and alerts you — and can automatically restart the affected workers.

Cron job logs are available directly in the DeployMonkey control panel, so you don't need SSH access to debug a failing scheduled action.

Start a free instance with built-in cron monitoring included.

Frequently Asked Questions

Can I run Odoo cron jobs on a schedule defined outside Odoo (like system cron)?

Yes, using the Odoo shell: odoo shell -d mydb --no-http then call the model method. You can wrap this in a bash script and trigger it from system cron. This gives you more precise scheduling control but bypasses Odoo's built-in scheduler management.

Why do my cron jobs stop running after a few hours?

Usually caused by a cron worker crash due to memory exhaustion. The worker process dies but the main Odoo process does not restart it automatically. Check your memory limits and consider reducing max_cron_threads if your cron jobs are memory-intensive.

Can cron jobs run concurrently?

Yes, if max_cron_threads is greater than 1. Multiple cron workers can each run a different scheduled action simultaneously. However, a single scheduled action only runs in one worker at a time — Odoo prevents the same action from running concurrently with itself.

How do I pass parameters to a scheduled action?

Scheduled actions don't natively support parameters. The standard pattern is to store configuration in ir.config_parameter (system parameters), read them in your cron method, or use model-level settings. If you need parameterized scheduling, create multiple scheduled actions calling the same method with different hardcoded arguments.