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:
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:
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.
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.
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:
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.
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.
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.
- CPU ceiling: (4 x 2) + 1 = 9 workers max.
- RAM budget: 8 GB total − ~1 GB OS − ~2 GB PostgreSQL ≈ 5 GB for Odoo.
- 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.
- Lower of the two wins: 5 (RAM) < 9 (CPU) → run workers = 5, max_cron_threads = 1.
- Connections: (5 + 1 + 1) x db_maxconn 32 = 224 worst case → set Postgres max_connections = 250, or keep 100 and add PgBouncer.
- 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.
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.
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.