This is the checklist you want to havebeforeopening Odoo to the world. It's not theory: it's what prevents 80% of startup incidents (auth, pools, websocket, queues, locks, logs, backups).
1) Network and exposure (layer "don't get hacked")
Odoo is NOT exposeddirectly to the Internet
http_interface = 127.0.0.1 (or private network)
Only thereverse proxy (Nginx/HAProxy)listens on 443 (TLS)
Firewall:
443 open to the world
8069/8072 closed to the world (only localhost or internal network)
6432 (PgBouncer) only accessible from Odoo/internal network
5432 (Postgres) only accessible from PgBouncer/bastion
2) Reverse proxy and WebSocket (the "classic ghost bug")
/ → Odoo HTTP (8069)
/websocket/ → Odoo gevent (8072)
Correct headers:
X-Forwarded-For
X-Forwarded-Proto
Host
proxy_mode = True in Odoo
Reasonable proxy timeouts (PDF reports are not 5s)
3) Odoo: minimum secure configuration
strong admin_passwd and outside the repo
list_db = False
defined dbfilter (if multi-DB / multi-tenant applies)
separate addons_path (core vs custom)
defined logfile with rotation (logrotate/journald)
4) Odoo: concurrency and limits (so it doesn't "eat" the host)
workers sized by CPU/RAM (not by faith)
conservative max_cron_threads (1–2) if you haven't measured yet
Configured limits:
limit_time_cpu
limit_time_real
limit_memory_soft
limit_memory_hard
If there are spikes due to crons: batching + avoid long transactions (mitigation plan)
5) PgBouncer: correct pool (and no surprises)
pool_mode = transaction for Odoo web
calculated default_pool_size (and not 'random')
reserve_pool_size for short spikes
sufficient max_client_conn for workers + spikes
If there are multiple DBs: consider max_db_connections and limits per pool
6) Auth: users, userlist, and reloads
defined auth_type (ideally scram-sha-256)
auth_file (userlist.txt) with correct format and permissions 0600
If you use auth_user/auth_query: technical role and well-structured query/fn
Rotation procedure:
edit userlist
RELOAD (or SIGHUP) without downtime
7) TLS 'if applicable' (not always, but when it applies, do it right)
If Odoo↔PgBouncer cross untrusted networks: TLS enabled
db_sslmode in Odoo (ideally verify-full if you have CA/hostname correct)
Certificates with correct SAN (hostname)
Certificate rotation plan (expiration monitored)
8) PostgreSQL: limits, timeouts, and DB health
max_connections consistent with actual pools (PgBouncer)
Timeouts:
statement_timeout (according to your policy)
lock_timeout
idle_in_transaction_session_timeout
Useful logs:
log_lock_waits (if you need visibility of locks)
Monitored autovacuum (so performance doesn't degrade over time)
9) Minimum observability (without this, you fly blind)
PgBouncer
SHOW POOLS; monitored (cl_waiting, maxwait)
SHOW STATS; collected (TPS/aggregated latency)
Logs:
log_connections=1
log_disconnections=1
log_pooler_errors=1
Odoo
p95/p99 of key endpoints (login, listings, write, reports)
errors (timeouts, pool full, deadlocks) as metrics/alerts
duration of crons and overlap (backlog)
Postgres
long transactions (xact_start)
sessions waiting for locks
top queries (if you use pg_stat_statements)
Infra
CPU, RAM, swap
I/O wait and disk latency
network (drops/retransmits)
file descriptors (especially PgBouncer)
10) Backups, restore, and 'day 2' (what defines if you survive)
Verified automatic backups (not just 'exist')
Tested restore in staging (real RTO, not theoretical)
Maintenance plan:
Odoo upgrade
Postgres upgrade
PgBouncer upgrade
Internal documentation:
how to view queue (SHOW POOLS)
how to reload auth_file
what to do in case of locks/long transactions
Closure (the golden rule)
If you only do 3 things to be at ease:
Well-routed Proxy + WebSocket
PgBouncer sized with metrics (cl_waiting, maxwait)
Crons without long transactions (batching)