Menu

How Many Odoo Workers Do You Actually Need? Sizing Workers and Tuning PostgreSQL the Right Way

A practical, no-nonsense guide to calculating Odoo workers from your CPU and RAM, writing a correct odoo.conf, and tuning PostgreSQL for the way Odoo really works
Short on time? Jump to the free Odoo Workers & PostgreSQL calculator at the bottom. Enter your CPU and RAM, copy the generated odoo.conf and postgresql.conf.

Every Odoo administrator eventually copies the same line from a forum: workers = (CPU x 2) + 1. It is not wrong, but it is only half of the story, and the missing half is exactly what fills your logs with MemoryError and "too many connections".

Worker count is not a CPU problem. It is a RAM problem with a CPU ceiling. And once your workers are right, the next bottleneck is almost always PostgreSQL, still running defaults from 2005 for a workload that looks nothing like Odoo. This guide walks through both, with the exact values, the math behind them, and the traps that the usual blog posts skip.

What an Odoo Worker Actually Is

In multiprocessing mode (the only mode you should run in production), Odoo forks several independent OS processes. Each one loads the full Python environment and all installed modules, then keeps that memory resident, even while idle. There are three kinds:

workers
HTTP workers
Handle every user click, RPC call and web request. This is the number people mean when they say "workers".
max_cron_threads
Cron workers
Run scheduled actions in the background. They are separate processes and they also eat RAM and a CPU slice.
gevent / longpolling
Real-time worker
One dedicated process (port 8072) for live chat, Discuss and bus notifications. Always reserve it.

The takeaway most guides forget to say out loud: cron and gevent workers count against your RAM budget too. If you only plan for HTTP workers, you have already under-sized the box.

The Formula Everyone Quotes, and What It Leaves Out

The well-known rule gives you a ceiling, not an answer:

theoretical max workers = (CPU cores x 2) + 1

Two more rules of thumb help you size from demand instead of hardware:

  • One HTTP worker comfortably serves about 6 concurrent users (people actively clicking, not just logged in).
  • You need a minimum of 2 HTTP workers in production. PDF/report generation calls Odoo back over HTTP through wkhtmltopdf, and with a single worker it deadlocks waiting on itself.
The rule that actually keeps you alive: (workers + cron + gevent) x limit_memory_soft must stay below the RAM you reserve for Odoo, after leaving room for PostgreSQL and the OS. CPU sets the ceiling; RAM sets the real number. Whichever is lower wins.

This is why the same "(CPU x 2) + 1" can be perfect on one server and an OOM machine on another. An 8-core box says "up to 17 workers", but with the default limit_memory_soft of 2 GB, 17 workers would demand 34 GB of RAM for Odoo alone.

A Correct odoo.conf, Line by Line

Here is a production-ready block with the values that match how Odoo 16+ actually behaves. The comments explain what each limit does and what its real default is.

/etc/odoo/odoo.conf INI
[options]
# --- Process model ---
workers              = 5      # HTTP workers (the real tuning knob)
max_cron_threads     = 1      # default is 2; raise only for heavy background jobs
proxy_mode           = True   # required when behind nginx/HAProxy

# --- Memory limits (bytes) ---
# soft = worker finishes its request, then is recycled (default 2048 MB)
# hard = worker is killed immediately            (default 2560 MB)
limit_memory_soft    = 805306368    # 768 MB
limit_memory_hard    = 1006632960   # 960 MB

# --- Time limits (seconds) ---
limit_time_cpu       = 60     # CPU time per request   (default 60)
limit_time_real      = 120    # wall-clock per request (default 120, ~2x cpu)
limit_time_real_cron = 300    # longer leash for cron jobs
limit_request        = 65536  # requests before a worker recycles (default 2^16)

# --- Database connection pool ---
db_maxconn           = 32     # max PG connections PER worker (default 64) -- see trap below
A myth to drop: some guides write limit_memory_soft = 2684354560 "= 2.5 GB". That byte value is actually the default for limit_memory_hard. The real soft default is 2147483648 (2 GB). Always keep soft below hard, never equal or above.

The Connection Trap Nobody Mentions

Here is the one that breaks production under load. Each Odoo worker keeps its own pool of up to db_maxconn PostgreSQL connections. So the real ceiling Odoo can open is:

max PG connections ≈ (workers + cron + gevent) x db_maxconn

With the defaults (say 8 workers and db_maxconn = 64), that is over 500 possible connections, while PostgreSQL ships with max_connections = 100. Under a traffic spike you hit FATAL: sorry, too many clients already and the site stalls. Two ways to fix it, ideally both:

  • Lower db_maxconn to 16–32 so the worst case fits inside Postgres max_connections.
  • For larger fleets, put PgBouncer in transaction mode in front of Postgres and size max_connections to the pool, not to the workers.

Tuning PostgreSQL for the Way Odoo Works

Odoo's workload is OLTP: a flood of small, indexed, ORM-generated queries and short transactions, not a few giant analytical scans. That shapes every value below. These assume Postgres has the box (or a known slice of it) to itself.

Parameter
Value
Why
shared_buffers
25% of RAM
PG's own page cache. Drop to ~15% if Postgres shares the box with Odoo.
effective_cache_size
50–75% of RAM
A planner hint, not an allocation. Tells PG how much OS+PG cache it can assume, so it favours index scans.
work_mem
16–32 MB
Per sort/hash, per operation. Handle with care; see the box below.
maintenance_work_mem
256 MB–1 GB
Speeds up VACUUM, CREATE INDEX and migrations. Used by few processes at once, so it can be generous.
random_page_cost
1.1
Default 4.0 assumes spinning disks. On SSD/NVMe, random reads are nearly as cheap as sequential.
effective_io_concurrency
200
For NVMe (use ~64 for SATA SSD, 1–2 for HDD). Lets PG prefetch in parallel.
checkpoint_completion_target
0.9
Spreads checkpoint writes over time instead of one I/O spike that freezes the UI.
max_wal_size
2–4 GB
Fewer, larger checkpoints. Pair with min_wal_size = 1GB.
jit
off
JIT only pays off on long analytical queries. On Odoo's many tiny queries it just adds overhead.
autovacuum_vacuum_scale_factor
0.05
Odoo churns mail.message, bus.bus, logs. Vacuum sooner to stop table bloat.
Why we are conservative on work_mem: it is allocated per sort or hash operation, and a single query can run several at once. Worst-case memory is roughly active_connections x work_mem x operations_per_query. The blogs that recommend 64–256 MB are assuming a pooled, low-connection setup; copy that onto a 150-connection Odoo box and one busy moment can swap the server to death. Start at 16–32 MB and raise it only for specific slow reports, ideally with a per-session SET work_mem.

Don't Forget the Reverse Proxy

Workers and Postgres are tuned, but if live chat and notifications "don't work", it is almost always the proxy. Real-time traffic uses the gevent/longpolling port (8072) and must be routed separately from normal requests (8069). Here is the minimal nginx that gets it right.

/etc/nginx/sites-available/odoo nginx
upstream odoo      { server 127.0.0.1:8069; }
upstream odoochat  { server 127.0.0.1:8072; }

server {
    listen 443 ssl;
    server_name erp.example.com;

    # real-time bus / longpolling -> gevent worker
    location /websocket          { proxy_pass http://odoochat; }
    location /longpolling        { proxy_pass http://odoochat; }

    # everything else -> HTTP workers
    location /                  { proxy_pass http://odoo; }

    proxy_set_header Host              $host;
    proxy_set_header X-Forwarded-Host  $host;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

Pair this with proxy_mode = True in odoo.conf so Odoo trusts the forwarded headers. On Odoo 16 the live channel is /websocket; older versions used /longpolling, so keep both for safety.

Worked Example: 4 vCPU / 8 GB, Odoo + Postgres on One Box

A very common starter setup. Let's size it honestly, end to end.

  1. CPU ceiling: (4 x 2) + 1 = 9 workers max.
  2. RAM budget: 8 GB total − ~1 GB OS − ~2 GB PostgreSQL ≈ 5 GB for Odoo.
  3. RAM-constrained workers: at limit_memory_soft = 768 MB, 5 GB / 0.77 ≈ 6 processes. Reserve 1 for cron + 1 for gevent → ~4–5 HTTP workers.
  4. Lower of the two wins: 5 (RAM) < 9 (CPU) → run workers = 5, max_cron_threads = 1.
  5. Connections: (5 + 1 + 1) x db_maxconn 32 = 224 worst case → set Postgres max_connections = 250, or keep 100 and add PgBouncer.
  6. Postgres: shared_buffers = 2GB, effective_cache_size = 4GB, work_mem = 16MB, maintenance_work_mem = 256MB.

Five HTTP workers comfortably carry ~30 concurrent users on this box. Need to serve 60? You are RAM-bound, not CPU-bound, so add memory (or move Postgres off the box) before you add cores.

Golden rule: after deploying, watch real usage and keep peak CPU and RAM under ~80%. Headroom is what absorbs the Monday-morning login storm and the end-of-month invoicing run.

Skip the Math: the DevTalents Odoo Worker Calculator

Everything above is built into a free tool. Enter your CPU cores, RAM, storage type and whether Postgres shares the box, then applies the RAM ceiling, the connection math and the OLTP Postgres profile, then hands you a ready-to-paste odoo.conf and postgresql.conf.

Generate your config in 10 seconds

Workers, memory limits, cron threads, db_maxconn and a matching PostgreSQL profile. Copy, paste, restart.

Open the Worker Calculator →

Running a real production instance and want a second opinion? Our team sizes and tunes Odoo deployments for a living, so get in touch.

Odoo workers calculator, Odoo worker configuration, how many Odoo workers, Odoo workers formula, CPU x 2 + 1 Odoo, Odoo limit_memory_soft limit_memory_hard, Odoo limit_time_cpu limit_time_real, max_cron_threads Odoo, db_maxconn Odoo, Odoo odoo.conf example production, PostgreSQL tuning Odoo, postgresql.conf Odoo, shared_buffers work_mem effective_cache_size Odoo, Odoo too many connections fix, PgBouncer Odoo, Odoo nginx reverse proxy longpolling websocket 8072, Odoo performance optimization, Odoo PostgreSQL performance tuning, Odoo memory error MemoryError workers, Odoo multiprocessing workers RAM sizing
Share this post
OWL: The Future of Odoo Development