Skip to content

Odoo Horizontal Scaling: Multi-Server Architecture Guide (2026)

DeployMonkey Team · March 23, 2026 12 min read

When to Scale Horizontally

A single Odoo server handles 50-200 concurrent users depending on workload. Beyond that, you hit CPU limits, worker exhaustion, and slow response times. Horizontal scaling adds more Odoo servers behind a load balancer, distributing the load across machines.

Signs you need horizontal scaling:

  • CPU consistently above 80% during business hours
  • Response times exceeding 3-5 seconds
  • Workers frequently reaching memory limits and recycling
  • Users reporting timeouts during peak periods
  • Large report generation blocking other users

Architecture Overview

Users → Load Balancer (nginx/HAProxy)
              ↓
    ┌─────────┼─────────┐
    ↓         ↓         ↓
  Odoo 1    Odoo 2    Odoo 3
  (workers) (workers) (workers)
    └─────────┼─────────┘
              ↓
     PostgreSQL Primary
              ↓
     PostgreSQL Replica (read-only)

Component Architecture

ComponentRoleScaling
Load BalancerDistribute requests, SSL terminationSingle (redundant pair)
Odoo WorkersProcess HTTP requestsHorizontal (2-10+ servers)
PostgreSQL PrimaryRead/write databaseVertical (bigger server)
PostgreSQL ReplicaRead-only queries, backupsHorizontal (1-3 replicas)
Shared FilestoreOdoo attachments, imagesNFS, S3, or GlusterFS
Redis/MemcachedSession storageSingle or sentinel

Step 1: Load Balancer Setup

# nginx load balancer configuration
upstream odoo_backend {
    ip_hash;  # Session sticky by client IP
    server 10.0.1.10:8069;
    server 10.0.1.11:8069;
    server 10.0.1.12:8069;
}

upstream odoo_longpolling {
    ip_hash;
    server 10.0.1.10:8072;
    server 10.0.1.11:8072;
    server 10.0.1.12:8072;
}

server {
    listen 443 ssl;
    server_name erp.yourdomain.com;
    
    ssl_certificate /etc/letsencrypt/live/erp.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/erp.yourdomain.com/privkey.pem;
    
    proxy_read_timeout 720s;
    proxy_connect_timeout 720s;
    proxy_send_timeout 720s;
    
    location /longpolling {
        proxy_pass http://odoo_longpolling;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
    
    location / {
        proxy_pass http://odoo_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Step 2: Shared Filestore

All Odoo servers must share the same filestore. Options:

NFS (Simplest)

# On filestore server
apt install nfs-kernel-server
mkdir -p /srv/odoo-filestore
chown odoo:odoo /srv/odoo-filestore
echo '/srv/odoo-filestore 10.0.1.0/24(rw,sync,no_subtree_check)' >> /etc/exports
exportfs -ra

# On each Odoo server
apt install nfs-common
mount -t nfs fileserver:/srv/odoo-filestore /opt/odoo/data/filestore

S3-Compatible Object Storage

Use an S3 adapter for Odoo to store attachments in object storage. This eliminates the shared filesystem requirement entirely and provides unlimited, durable storage.

Step 3: Session Management

By default, Odoo stores sessions on the local filesystem. With multiple servers, you need centralized session storage:

# Option 1: Sticky sessions (ip_hash in nginx)
# Simplest but not fault-tolerant. If a server dies, those users lose sessions.

# Option 2: PostgreSQL session store
# Odoo can store sessions in the database (slower but shared automatically)

# Option 3: Redis session store (recommended)
# Requires custom session handler or community module
# Redis provides fast, shared session storage across all Odoo servers

Step 4: Database Replication

For read-heavy workloads, set up PostgreSQL streaming replication:

# On primary
postgresql.conf:
  wal_level = replica
  max_wal_senders = 5
  wal_keep_size = 1024

# On replica
pg_basebackup -h primary_host -D /var/lib/postgresql/data -U replicator -P

postgresql.conf:
  primary_conninfo = 'host=primary_host user=replicator password=xxx'
  hot_standby = on

Note: Odoo does not natively support read replicas for query routing. You would need a connection proxy like PgBouncer with read/write splitting, or a custom Odoo patch to route specific queries (reports, analytics) to the replica.

Step 5: Worker Configuration

With multiple servers, tune workers per server:

# Per Odoo server (4 vCPU, 8GB RAM)
workers = 6
max_cron_threads = 1  # Only ONE server should run cron!
limit_memory_hard = 2684354560
limit_memory_soft = 2147483648
limit_time_cpu = 600
limit_time_real = 1200

Critical: Only one Odoo server should run cron jobs. Set max_cron_threads = 0 on all servers except one to prevent duplicate cron execution.

Step 6: Cron Job Isolation

Designate one server as the cron runner. All other servers handle only HTTP requests:

# Server 1 (cron + HTTP)
workers = 4
max_cron_threads = 2

# Server 2 (HTTP only)
workers = 6
max_cron_threads = 0

# Server 3 (HTTP only)
workers = 6
max_cron_threads = 0

Capacity Planning

Concurrent UsersOdoo ServersWorkers TotalDB Server
1-5014-6Same server
50-15028-12Dedicated 4 vCPU
150-300312-18Dedicated 8 vCPU
300-5004-518-30Dedicated 16 vCPU + replica
500+5+30+Dedicated 32 vCPU + replicas

DeployMonkey + Horizontal Scaling

DeployMonkey manages multi-server Odoo deployments. Add servers, configure load balancing, and scale your Odoo deployment without managing infrastructure yourself.