# Odoo best practices

Odoo is a Python ERP that runs as its own long-lived service behind an Nginx reverse proxy. Getting good, stable performance is mostly about right-sizing the worker pool, keeping PostgreSQL healthy, and letting Nginx serve static and filestore content. This page covers what TurboStack already configures for you and the optimizations worth applying on top.

## What TurboStack configures for you

When you set `app_type: odoo`, the platform provisions a complete, tuned Odoo stack:

- **A dedicated systemd service** that launches `odoo-bin` from your pyenv-managed Python and restarts automatically on failure (`Restart=on-failure`).
- **An Nginx reverse proxy** with `proxy_mode = True` set in Odoo, forwarding `X-Forwarded-*` and `X-Real-IP` headers so Odoo sees the correct client address and scheme.
- **Multiprocess workers**, auto-calculated from the server's CPUs, plus `max_cron_threads = 2` for scheduled jobs.
- **A separate websocket/longpolling endpoint** - Nginx routes `/websocket` (Odoo 16+) and `/longpolling` (Odoo 15 and earlier) to the gevent port (`8072` by default). Normal traffic goes to the main port (`8069`).
- **Per-worker resource limits**: `limit_memory_soft` (2 GB), `limit_memory_hard` (4 GB), `limit_time_cpu` (3600 s), `limit_time_real` (7200 s) and `limit_request` (8192).
- **Direct filestore delivery**: `x_sendfile = True` plus an Nginx `internal` `/web/filestore` location, so attachments are streamed by Nginx instead of through Python workers.
- **A PostgreSQL connection** with a dedicated role and database, plus a generated `admin_passwd` for the database manager.
- **A ready-made `odoo.conf.sample`** in your `conf/` directory with all of the above as a starting point.

> [!NOTE]
> An Odoo deployment uses PostgreSQL only. There is no PHP, no MySQL/MariaDB, and no Varnish layer.

## Recommended optimizations

- **Tune the worker count to your workload.** The default scales with CPUs. Odoo's guideline is roughly `(2 x CPU) + 1` HTTP workers, or about one worker per 6 concurrent users - use the lower of the two. More workers serve more concurrent users but cost RAM - see [Sizing and scaling](#sizing-and-scaling).
- **Keep filestore on disk, not in the database.** The platform already serves it via Nginx; avoid storing attachments in PostgreSQL, which bloats the database and backups.
- **Run Odoo in production mode.** Do not enable `dev_mode` or `demo` data on a live site; the defaults already disable them (`without_demo = True`).
- **Pre-compile and cache assets.** Let Odoo bundle and minify JS/CSS (the default), and serve them through Nginx with long cache headers.
- **Right-size cron threads.** Heavy scheduled actions (mailings, accounting, inventory valuation) run under `max_cron_threads`; keep them modest so cron jobs do not starve HTTP workers.
- **Tighten timeouts for long operations.** Imports and reports may need higher `limit_time_real`/`limit_time_cpu`; raise them deliberately rather than disabling them.
- **Front it with HTTP caching/Content Delivery Network (CDN) where safe.** Static assets and public application pages cache well; never cache authenticated `/web` session traffic.
- **Install Python dependencies in the virtual environment.** Use the project's pip, never system-wide: `pip install -r odoo/requirements.txt` for core, and `pip install -r custom-addons/<addon>/requirements.txt` per add-on.
- **Leave `db_maxconn` at its default (64).** The platform's PostgreSQL is tuned for high concurrency; raising it rarely helps and can exhaust connections.

Manage the Odoo and PostgreSQL services from the host [Services](../../platform/hosts/services.md) tab.

## Sizing and scaling

The worker pool and memory limits are auto-tuned from the server's resources, and PostgreSQL is sized by the platform. Override defaults only with measured evidence:

| Variable | Tune when |
| --- | --- |
| `workers` | CPUs are consistently busy and requests queue - but ensure RAM covers `workers × limit_memory_soft`. |
| `limit_memory_soft` / `limit_memory_hard` | Workers are recycled too aggressively under legitimate load. |
| PostgreSQL memory and connections | The database becomes the bottleneck rather than the app. |

> [!TIP]
> Each Odoo worker can consume up to ~2-4 GB. Add workers only if free memory comfortably absorbs the extra processes, or you risk swapping and Out Of Memory (OOM) kills.

See [Performance tuning](../../concepts/performance-tuning.md) for the general approach to changing auto-tuned values.

## Stability

- **Back up regularly.** Ensure both PostgreSQL and the filestore are covered - see [Backups](../../platform/hosts/backups.md). A database backup without the matching filestore loses attachments.
- **Watch the [Health](../../platform/hosts/health.md) tab** for CPU, memory, and service status; rising memory or worker restarts are early warnings.
- **Keep Odoo and modules current.** Apply security and point releases, and review third-party modules before upgrading major versions.
- **Test on a staging clone.** Validate module installs/upgrades (`-u`) and configuration changes on a copy before touching production.

## Related

- [Deploy Odoo on TurboStack](deploy.md)
- [Odoo reference](reference.md)
- [Troubleshooting Odoo](troubleshooting.md)
- [Services](../../platform/hosts/services.md)
- [Performance tuning](../../concepts/performance-tuning.md)
