Quick Answer
Odoo workers enable multiprocessing to handle concurrent requests efficiently. The problem: Server CPU at 20% but site timing out = workers = 0 (threaded mode) = single process handling everything = site can't scale. Upgrading to 64GB RAM doesn't help because Odoo uses 1 CPU only. The solution: Switch to multiprocessing mode by configuring workers. This spawns multiple processes, each handling requests simultaneously, utilizing entire server. Worker formula: Workers = (CPU Cores × 2) + 1. Why: CPU Cores = physical power, × 2 = processes wait for I/O (database, files) so double handles I/O wait time, + 1 = dedicated Cron worker for background tasks. Example: 4-core server = (4 × 2) + 1 = 9 workers. Memory check: 9 workers × 300MB = 2.7GB RAM required. If 4GB server = safe. If 2GB server = reduce workers or upgrade RAM. Configuration: Edit /etc/odoo/odoo.conf: workers = 9, limit_memory_soft = 671088640 (640MB - recycle worker after finishing request), limit_memory_hard = 805306368 (768MB - kill worker instantly), limit_time_cpu = 60, limit_time_real = 120. Cron worker: One worker dedicated to scheduled actions. If workers = 2 and 1 is Cron = only 1 for users = bottleneck. Best practice: Separate servers (Frontend: workers = 9, max_cron_threads = 0; Backend: workers = 2, max_cron_threads = 1). Common issues: Longpolling broken (proxy port 8072 in Nginx), worker killed (hit limit_time_real), MemoryError (limit_memory_hard too high for physical RAM). Impact: Proper worker config = handle 100+ concurrent users, prevent timeouts, utilize full server capacity.
The Worker Configuration Problem
Your D2C brand launches a new collection. Emails go out. Users flood the site.
Suddenly, your server CPU is at 20%, but the site is timing out.
You upgrade the server to 64GB RAM. The site is still timing out.
The Problem:
You are running Odoo in Threaded Mode (the default), which is essentially single-process. It doesn't matter how many cores you have; Odoo is using one CPU to handle everything.
The Solution:
Switch to Multiprocessing Mode by configuring workers. This allows Odoo to spawn multiple processes, each handling a request simultaneously. It utilizes your entire server.
We've implemented 150+ Odoo systems. The most common configuration mistake is simply setting workers = 0 (threaded) or guessing a random number like workers = 100 (which crashes the server).
The Worker Formula
There is a golden rule for calculating the optimal number of workers based on your CPU cores.
Workers = (CPU Cores × 2) + 1
Why This Formula?
CPU Cores: The physical brain power
× 2: Because Odoo processes often wait for I/O (Database queries, File reads). While one worker waits for SQL, another can use the CPU
+ 1: For the Cron worker (background tasks)
Example: 4-Core Server
(4 × 2) + 1 = 9 Workers
The "Memory Limit" Check
You cannot just set 9 workers if you only have 2GB of RAM. Each worker consumes RAM.
| Component | Value |
|---|---|
| Average Worker RAM | 150MB - 300MB (depending on complexity) |
| Calculation | 9 Workers × 300MB = 2.7GB RAM required |
| 4GB Server | ✓ Safe |
| 2GB Server | ✗ Reduce workers or upgrade RAM |
Configuring odoo.conf
Production-ready configuration for a 4-Core, 8GB RAM server:
[options]
# 1. Enable Multiprocessing
workers = 9
# 2. Memory Limits (Prevent Memory Leaks)
# Soft Limit: Worker is recycled after finishing current request
limit_memory_soft = 671088640 ; 640 MB
# Hard Limit: Worker is killed instantly (Panic mode)
limit_memory_hard = 805306368 ; 768 MB
# 3. Time Limits (Prevent Infinite Loops)
# Maximum time a request can take (e.g., heavy report)
limit_time_cpu = 60
limit_time_real = 120
Why Memory Limits Matter
Python is notorious for not releasing memory back to the OS immediately. Over time, a long-running worker bloats.
The limit_memory_soft tells Odoo: "If you exceed 640MB, finish what you are doing, then restart yourself."
This keeps your RAM usage a flat and predictable graph line, rather than an endless upward slope until crash.
The Cron Worker Dilemma
In Multiprocessing mode, one worker is dedicated to Cron jobs (Scheduled Actions).
The Risk:
If you have workers = 2, and one is the Cron worker, you only have 1 worker left for users. If two users click a button at the same time, the second user waits.
Best Practice for Heavy Crons
If you have heavy background tasks (e.g., Syncing 10,000 products with Shopify), do not run them on the user-facing server.
| Server | Configuration | Purpose |
|---|---|---|
| Server A (Frontend) | workers = 9, max_cron_threads = 0 | Handles Users only |
| Server B (Backend) | workers = 2, max_cron_threads = 1 | Handles Crons only |
This ensures that a massive inventory sync never slows down the checkout page.
Troubleshooting Worker Issues
1. Longpolling/Livechat is Broken
When you switch to workers, the default port 8069 no longer handles the WebSocket/Bus connection properly (in older Odoo versions).
Fix: Odoo opens a separate port (default 8072) for the Longpolling worker. You must configure Nginx to proxy /longpolling traffic to port 8072.
location /longpolling {
proxy_pass http://127.0.0.1:8072;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
2. "Worker (pid: 12345) killed" in Logs
This means a request hit the limit_time_real.
Cause: A user tried to print a PDF of 5,000 Sales Orders
Fix: Increase the limit (risky) or tell the user to batch their export
3. "MemoryError" or System OOM
Your limit_memory_hard is too high for your physical RAM. The OS killed Odoo to save the kernel.
Fix: Lower the hard limit or reduce the number of workers
Action Items: Configure Workers
Check Hardware
❏ How many CPU cores? Run: nproc
❏ How much RAM? Run: free -h
Calculate
❏ Apply the formula: (Cores × 2) + 1
❏ Multiply by 300MB. Does it fit in RAM?
Configure
❏ Edit /etc/odoo/odoo.conf
❏ Set workers, limit_memory_soft, and limit_memory_hard
❏ Restart Odoo: sudo service odoo restart
❏ Check logs: tail -f /var/log/odoo/odoo-server.log to see workers spawning
Frequently Asked Questions
What is the optimal number of Odoo workers?
Use the formula: Workers = (CPU Cores × 2) + 1. Why: CPU cores = physical processing power, × 2 = Odoo processes wait for I/O (database queries, file reads) so while one worker waits for SQL, another uses CPU, + 1 = dedicated Cron worker for scheduled actions. Example calculations: 2-core server = (2 × 2) + 1 = 5 workers, 4-core server = (4 × 2) + 1 = 9 workers, 8-core server = (8 × 2) + 1 = 17 workers. Memory verification: Multiply workers by 300MB (average RAM per worker). 9 workers × 300MB = 2.7GB RAM required. If server has 4GB = safe. If 2GB = reduce workers or upgrade RAM. Critical: Don't guess random numbers. workers = 100 will crash server. workers = 0 (default threaded mode) won't scale.
What are limit_memory_soft and limit_memory_hard in Odoo?
Memory limits prevent workers from consuming unlimited RAM. limit_memory_soft (640MB recommended): When worker exceeds this limit, it finishes current request then restarts itself (graceful recycle). Prevents gradual memory bloat as Python doesn't release memory to OS immediately. limit_memory_hard (768MB recommended): When worker exceeds this limit, it is killed instantly (panic mode). Prevents single runaway process from crashing entire server. Configuration: limit_memory_soft = 671088640 (640MB in bytes), limit_memory_hard = 805306368 (768MB in bytes). Why it matters: Without limits, long-running workers bloat over time = RAM usage endless upward slope until crash. With limits = flat predictable RAM graph. Tuning: Set soft limit to 70-80% of hard limit. Monitor with htop or top. If workers constantly restarting = limits too low.
How do I separate Cron workers from user-facing workers?
Use two separate Odoo servers: Frontend (users) and Backend (Crons). Server A (Frontend): workers = 9, max_cron_threads = 0 (disables Cron processing). Handles all user requests, checkout, product browsing. Server B (Backend): workers = 2, max_cron_threads = 1 (enables 1 Cron thread). Handles scheduled actions, inventory sync, report generation, email campaigns. Database: Both connect to same PostgreSQL database. Why separate: Heavy Cron job (sync 10,000 products from Shopify) = CPU spike + memory usage. If running on user-facing server = checkout page slows down = abandoned carts = lost revenue. With separation = Crons never impact user experience. Setup: Same odoo.conf, different workers/max_cron_threads settings. Point both to same DB. Use load balancer for frontend, internal network for backend.
Why is my Odoo longpolling/live chat broken after enabling workers?
Multiprocessing mode uses separate port (8072) for longpolling. Nginx must proxy it correctly. Problem: When workers > 0, Odoo opens port 8072 for WebSocket/bus connections (longpolling). Default Nginx config only proxies port 8069 = longpolling/live chat breaks. Solution: Add Nginx location block: location /longpolling { proxy_pass http://127.0.0.1:8072; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; }. Verification: Check Odoo logs for "Listening on 0.0.0.0:8072". Test live chat or discuss module. Browser console should not show WebSocket errors. Alternative: Use gevent mode (workers = 1, but with async I/O) if you can't configure multiple ports, though multiprocessing is better for CPU-bound tasks.
Free Server Tuning Audit
Is your server crashing under load? We'll calculate the exact worker configuration for your specific hardware, configure Nginx to handle the Longpolling split correctly, stress-test your memory limits to prevent OOM crashes, and segregate your Cron jobs to a dedicated worker process. Stop guessing. Configure for stability.
