When you are experiencing lagging in your Odoo installation, buying additional memory will rarely be the solution. Instead, it would help to check whether you have set the number of workers appropriately – too many, too few, or an incorrect ratio between HTTP and cron workers.
Odoo 19 works in a multi-process environment. For each incoming request, a new process spawns, meaning that when there are no free workers, other processes are left waiting. Here is a great blog post about handling multiple workers.
The formula Odoo actually recommends
Odoo's own documentation gives a starting formula: (CPU cores × 2) + 1 worker processes for HTTP, with one or two dedicated cron workers on top.
Example:
Workers - 9 - (4×2)+1
Max_cron_threads - 2 - dedicated schedulers
Limit_memory_hard - 2684354560 - 2.5 GB per worker
Limit_time_cpu - 60 - seconds CPU per req
These go in your odoo.conf:
# /etc/odoo/odoo.conf
workers = 9
max_cron_threads = 2
limit_memory_hard = 2684354560
limit_memory_soft = 2147483648
limit_time_cpu = 60
limit_time_real = 120
limit_request = 8192
Note: Total worker memory must fit in RAM. 9 workers × 2.5 GB = ~22.5 GB. If your server only has 16 GB, you'll thrash. Either reduce the number of workers or reduce the per-worker memory limit.
Why cron workers deserve their own tuning
These cron workers perform scheduled tasks such as invoicing, email queueing, and inventory management. They do not process HTTP requests; including them among the normal workers is a widespread error.
If max_cron_threads is 0, Odoo will not schedule any jobs in the multiprocessing environment at all. If it is too large, an intensive batch task might consume all CPU resources allocated to your HTTP workers.
In almost all cases, 1 or 2 cron workers suffice. The only exception would be when performing intense batch jobs on a regular basis in business hours.
Odoo 19's new worker improvements
In Odoo 19, there is enhanced worker recycling. The earlier versions would retain zombie workers even after memory limit violations. This problem has been fixed in Odoo 19, where the worker management process becomes more aggressive in harvesting zombies and creating new workers, thereby keeping the configured worker count closer to the active worker count.
Additionally, there are better logs for the state of the worker. When you use the command --log-level=debug, the logs will now show when workers were recycled because of memory limits. This helps you find silent out-of-memory errors that did not appear until you encountered slow server performance in earlier versions.
Diagnosis
The fastest check: look at worker logs during peak load. In /var/log/odoo/odoo.log, you want to see something like:
# Healthy — workers cycling normally
WorkerHTTP (4) lives on pid=31204
WorkerHTTP (5) lives on pid=31205
# Problem — worker killed on memory
Worker (4) max memory reached, killing (pid=31204)
Memory death happens when your limit_memory_hard is too small relative to the jobs that are being done. Or you may have a memory leak somewhere. Fewer workers will sometimes solve the problem (because fewer workers have the same amount of RAM available to them).
If you are not getting the "lives on" messages, you are running in thread mode instead of multiprocess mode. This is OK for development, but it does not allow any parallel execution or security among requests. Do not use this mode in production.
Nginx in front of Odoo
One thing that genuinely helps more than tuning worker count is to put Nginx in front with proper timeouts. Without it, a slow client connection holds an Odoo worker open for the entire transfer. With Nginx as a reverse proxy, the worker finishes and Nginx handles the slow client.
# nginx.conf snippet
proxy_read_timeout 720s;
proxy_connect_timeout 720s;
proxy_send_timeout 720s;
proxy_buffer_size 64k;
proxy_buffers 8 64k;
Match proxy_read_timeout to your limit_time_real in Odoo config. If Nginx gives up before Odoo does, users get 502 errors on long operations. If Odoo gives up first, Nginx gets an empty response. Neither is great.
Mistakes to be noted:
- Setting workers to a very high number like 30 or 40 doesn't improve throughput on an 8-core machine. You'll get more context switching, more memory pressure, and roughly the same requests-per-second. Workers waiting on the CPU don't help anyone.
- Setting limit_time_real very high (like 3600 seconds) to "allow long operations" usually just means slow queries hold workers hostage. The real fix there is query optimization, not patience.
Worker tuning isn't a one-time task. It shifts as your dataset grows, as you add modules, and as your user count changes. Schedule a check every few months, not just when something breaks.
To read more about How to Set Up Workers in Odoo 19 for Better Performance, refer to our blog, How to Set Up Workers in Odoo 19 for Better Performance.