Deploy Odoo on VPS with PostgreSQL: 2026 Production Guide
21 min read 4,179 words Tamim Iqbal

Deploy Odoo on VPS with PostgreSQL: 2026 Production Guide

OdooPostgreSQLVPSNginxUbuntuERP

To deploy Odoo on a VPS for production, install Odoo 20 Community from Odoo's official apt repository on Ubuntu 24.04 LTS. Let it use the local PostgreSQL 16 server through the non-superuser odoo role the package creates. Switch Odoo to multi-processing mode (workers above 0), bind it to localhost, and put Nginx in front for TLS and the /websocket route. Then turn off the database manager with list_db = False and back up both the database and the filestore every night. Everything below is copy-pasteable and checked against the Odoo 20.0 system configuration docs, the packaging files in the odoo/odoo 20.0 branch, and each tool's own documentation, as of 25 September 2026.

I've configured VPS servers running Odoo ERP and PostgreSQL for De Castro Group. This guide is the checklist I'd hand to any sysadmin setting up a single-company Odoo Community instance on one server. It covers sizing, installation, odoo.conf, systemd, Nginx, HTTPS, firewall, backups, updates and troubleshooting, in the order you'll do them.

Key takeaways

  • Version and OS: Odoo 20 was released in September 2026 (release notes). Its official .deb supports Ubuntu 24.04 LTS (Noble). Odoo 20 needs Python 3.12+ and PostgreSQL 16+, and Noble ships exactly those.
  • Install method: Odoo's own update guide calls packaged installers "the recommended method". For one company on one VPS, the nightly apt repository is the best balance of simplicity and patching.
  • Sizing rule: Odoo's docs say workers = (CPU × 2) + 1 at most, one worker handles about 6 concurrent users, and RAM ≈ workers × (0.8 × 150 MB + 0.2 × 1 GB).
  • Must-set options: workers, limit_memory_soft/limit_memory_hard, proxy_mode = True, list_db = False, a random admin_passwd, db_name and dbfilter.
  • Backups: the database alone isn't a backup. Attachments live in the filestore, so dump both, copy them off the server, and test a restore.

Which Odoo version and Ubuntu release should you deploy?

Deploy Odoo 20.0 on Ubuntu 24.04 LTS. Ubuntu 26.04 LTS came out in April 2026, but Odoo's Linux packaging page for 20.0 says the Odoo 20 .deb "currently supports Ubuntu Noble (24.04LTS)" (Odoo packaged installers). Noble gets standard security maintenance until May 2029 (Ubuntu release cycle), so you have plenty of runway.

The version requirements line up. The 20.0 source-install docs raised the minimums to Python 3.12 and PostgreSQL 16 (Odoo source install docs). On Noble, the python3 package is 3.12.3 and postgresql is 16 (Ubuntu packages: postgresql). No third-party repositories needed.

A caution about timing: Odoo 20 is brand new. If you depend on community or third-party modules that haven't been ported yet, the same steps work for 19.0. Replace 20.0 with 19.0 in the repository line, because the 19.0 packaging docs also target Ubuntu 24.04.

How do you size a VPS for Odoo?

Size from concurrent users, not total users. Odoo's deployment docs give three rules of thumb. Use at most (#CPU × 2) + 1 workers, including cron. Expect one worker to serve about 6 concurrent users. Assume 80% light requests at about 150 MB and 20% heavy requests at about 1 GB per worker (Odoo worker and memory calculation). Their worked example is a 4-CPU server with 8 HTTP workers plus 1 cron worker, which needs "~= 3GB RAM for Odoo".

Applying those formulas to common VPS sizes gives this table. The worker, user and Odoo RAM columns are straight arithmetic from the docs. The suggested VPS RAM is my own recommendation, with headroom for PostgreSQL, Nginx and the OS on the same machine.

vCPUMax workers (CPU × 2 + 1)Split≈ Concurrent users≈ RAM for Odoo workersSuggested VPS RAM
254 HTTP + 1 cron241.6 GB4 GB
498 HTTP + 1 cron482.9 GB (docs: ~3 GB)8 GB
81716 HTTP + 1 cron965.4 GB16 GB

Treat these numbers as starting points. The docs assume well-written computed fields and SQL. Heavy custom modules, large imports and big PDF reports push the heavy-request share up, so watch CPU load and adjust. Odoo's own example aims for a load between 7 and 7.5 on 8 threads. If you're still choosing a provider, my overview of cloud infrastructure on AWS, Azure and GCP compares the main options.

Packages, source or Docker: which install method?

Use the official packages unless you have a specific reason not to. Here's how the three supported routes compare for a production VPS:

MethodStrengthsTrade-offsBest for
Official .deb / nightly apt repoCreates the odoo system user, the PostgreSQL role, /etc/odoo/odoo.conf and a systemd unit. Updates come through apt.Community edition only via the repo (Enterprise .deb requires a customer login). Tied to supported distro releases.Single-company production on one VPS
Source (git clone + virtualenv)Pin exact commits, run several versions side by side, patch codeYou write the systemd unit, manage Python dependencies and pull updates with gitDevelopers and heavily customized instances
Docker (official odoo image)Reproducible containers, easy to move between hostsThe docs note the multi-threaded server is the default "also for docker containers", so you must configure workers yourself. On 25 September 2026 the newest tag on Docker Hub was 19.0, with no 20.0 image yet.Teams already running container orchestration

Odoo's bugfix update guide calls installing from a package "the recommended method". The package's post-install script creates a non-superuser PostgreSQL role for you (debian/postinst), which removes a common security mistake. The rest of this guide uses the apt repository.

Step 1: Prepare Ubuntu 24.04 and point DNS

Start from a fresh Ubuntu 24.04 VPS. Log in as a sudo user with SSH key authentication, and create an A record (for example odoo.example.com) pointing to the server's public IP. Certbot needs that record in Step 7.

sudo apt update && sudo apt upgrade -y
sudo timedatectl set-timezone UTC   # optional: keep server logs in UTC
sudo apt install -y postgresql postgresql-client nginx

Odoo uses wkhtmltopdf to render PDF reports. The docs warn that it must be installed manually, in version 0.12.6, "for it to support headers and footers" (Odoo packaged installers). The 0.12.6.1-3 release ships Jammy and Bookworm builds but no Noble build. Install the Jammy .deb with apt so it pulls in dependencies, then confirm the binary runs:

cd /tmp
wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6.1-3/wkhtmltox_0.12.6.1-3.jammy_amd64.deb
sudo apt install -y ./wkhtmltox_0.12.6.1-3.jammy_amd64.deb
wkhtmltopdf --version   # expect: wkhtmltopdf 0.12.6.1 (with patched qt)

If apt can't resolve a dependency on your image, check the Odoo wkhtmltopdf wiki before trying other builds.

Step 2: Install Odoo 20 from the official repository

These three commands come from Odoo's packaging page, with the branch set to 20.0:

wget -q -O - https://nightly.odoo.com/odoo.key | sudo gpg --dearmor -o /usr/share/keyrings/odoo-archive-keyring.gpg
echo 'deb [signed-by=/usr/share/keyrings/odoo-archive-keyring.gpg] https://nightly.odoo.com/20.0/nightly/deb/ ./' | sudo tee /etc/apt/sources.list.d/odoo.list
sudo apt-get update && sudo apt-get install odoo

The package does four things you'd otherwise do by hand. It creates the odoo system user with its home at /var/lib/odoo. It registers a PostgreSQL role with createuser -d -R -S odoo. It installs /etc/odoo/odoo.conf with mode 0640, and it adds an odoo.service unit that logs to /var/log/odoo/odoo-server.log. Stop the service while you configure it:

sudo systemctl stop odoo

Step 3: Check PostgreSQL and the dedicated odoo role

Keep PostgreSQL on the same VPS and connect over its Unix socket. That's the default when db_host is unset. PostgreSQL then only accepts local and loopback connections, and nothing listens on the network (Odoo PostgreSQL configuration). Because the Odoo service runs as the Unix user odoo and the role is also odoo, peer authentication works without a password.

Confirm the role has "Create DB" but isn't a superuser. Odoo's security checklist says the database user must not be a superuser.

sudo -u postgres psql -c '\du odoo'
# Expected attributes: Create DB  (and NOT Superuser, NOT Create role)
psql --version   # PostgreSQL 16.x on Ubuntu 24.04

If you installed with a different method, create the equivalent role yourself:

sudo -u postgres createuser -d -R -S odoo

Only open pg_hba.conf and listen_addresses to the network if PostgreSQL runs on a separate machine. In that case, follow the docs' md5 sample and consider db_sslmode. If you plan to use Odoo's AI features, the docs say you'll need the pgvector extension. Ubuntu 24.04 packages it as postgresql-16-pgvector.

Step 4: Write a production odoo.conf

This configuration is for the 4 vCPU / 8 GB tier from the sizing table. The memory and time limits are copied from Odoo's own configuration sample. Generate the master password with the command from the docs first:

python3 -c 'import base64, os; print(base64.b64encode(os.urandom(24)))'

Paste the string (without the b'…' wrapper) into admin_passwd below, and change odoo_prod if you want a different database name:

sudo tee /etc/odoo/odoo.conf > /dev/null <<'EOF'
[options]
; Master password for database operations (generated above)
admin_passwd = REPLACE_WITH_GENERATED_VALUE

; PostgreSQL over the local Unix socket as the non-superuser "odoo" role
db_host = False
db_port = False
db_user = odoo
db_password = False

; One production database, selected for every request
db_name = odoo_prod
dbfilter = ^odoo_prod$
list_db = False

; Files: filestore lands in /var/lib/odoo/filestore/<db_name>
data_dir = /var/lib/odoo

; Listen on localhost only; Nginx is the public entry point
http_interface = 127.0.0.1
http_port = 8069
gevent_port = 8072
proxy_mode = True

; Multi-processing: 4 vCPU -> (4 x 2) + 1 = 9 = 8 HTTP + 1 cron
workers = 8
max_cron_threads = 1
limit_memory_hard = 1677721600
limit_memory_soft = 629145600
limit_request = 8192
limit_time_cpu = 600
limit_time_real = 1200

; Shipped by the package
default_productivity_apps = True
EOF
sudo chown odoo:odoo /etc/odoo/odoo.conf
sudo chmod 0640 /etc/odoo/odoo.conf

Replace REPLACE_WITH_GENERATED_VALUE with your string before you start the service. Here's what each production setting does, based on the Odoo 20 command-line reference and the deploy guide:

  • workers: any non-zero value switches on the multi-processing server. It also starts the event-driven worker on gevent_port (default 8072) that serves live chat and websockets. With workers = 0 you get the threaded development server.
  • limit_memory_soft / limit_memory_hard: byte values per worker. Past the soft limit (600 MiB here), the worker is recycled after its current request. Past the hard limit (1,600 MiB), memory allocation fails immediately. Without these lines, the defaults are 2048 MiB and 2560 MiB, which is too generous on an 8 GB machine running nine workers.
  • limit_time_cpu / limit_time_real: per-request CPU and wall-clock caps. The defaults are 60 s and 120 s. The sample raises them to 600 s and 1200 s so long reports and imports can finish.
  • proxy_mode = True: trusts the X-Forwarded-* headers from Nginx so Odoo sees the real hostname, scheme and client IP. Odoo ignores those headers when X-Forwarded-Host is missing. Only enable it behind a reverse proxy.
  • list_db = False: the same as --no-database-list. It hides the database list and blocks the database manager screens. The docs strongly recommend this for any internet-facing system.
  • db_name + dbfilter: once the database manager is off, Odoo needs these to pick the database for each request. Otherwise users get blocked.
  • http_interface = 127.0.0.1: keeps ports 8069 and 8072 off the public interface, so every request has to come through Nginx.

Step 5: Create the database and start the systemd service

With list_db = False, you can't create databases in the browser, so use the db init command that the Odoo 20 CLI provides. Run it as the odoo user so peer authentication applies. Don't use admin as the login (Odoo's security checklist says not to), and leave demo data off, which is the default:

read -rsp 'New Odoo admin password: ' ODOO_ADMIN_PW; echo
sudo -u odoo odoo db -c /etc/odoo/odoo.conf init odoo_prod \
  --username it-admin@example.com \
  --password "$ODOO_ADMIN_PW" \
  --language en_US \
  --country BD
unset ODOO_ADMIN_PW

Change --country to your company's ISO code. The password is briefly visible in the process list while the command runs, so change it from the user menu after your first login. Next, look at the unit the package installed (debian/odoo.service). It runs /usr/bin/odoo --config /etc/odoo/odoo.conf --logfile /var/log/odoo/odoo-server.log as odoo:odoo with KillMode=mixed. It has no restart policy, so I add a small drop-in that uses standard systemd service options:

sudo mkdir -p /etc/systemd/system/odoo.service.d
sudo tee /etc/systemd/system/odoo.service.d/override.conf > /dev/null <<'EOF'
[Unit]
After=network.target postgresql.service

[Service]
Restart=on-failure
RestartSec=5s
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now odoo
systemctl status odoo --no-pager
sudo ss -ltnp | grep -E ':8069|:8072'   # both should show 127.0.0.1
sudo tail -n 50 /var/log/odoo/odoo-server.log

The CLI reference says --logfile output "can be managed by external log rotation programs" and is reopened automatically when replaced. A basic logrotate rule is enough:

sudo tee /etc/logrotate.d/odoo > /dev/null <<'EOF'
/var/log/odoo/*.log {
    weekly
    rotate 12
    compress
    delaycompress
    missingok
    notifempty
}
EOF

Step 6: Configure Nginx as a reverse proxy with the websocket route

Nginx forwards normal traffic to port 8069 and anything under /websocket to the gevent worker on 8072. The file below is Odoo's official Nginx sample with the TLS lines removed, because Certbot adds them in the next step. On Odoo 15 and earlier this route was /longpolling on the longpolling_port. Since Odoo 16 it's /websocket on gevent_port (Odoo 16 deploy docs), so older guides that proxy /longpolling leave Discuss and live chat broken.

One caution before you paste: the Strict-Transport-Security header with includeSubDomains tells browsers to use HTTPS only, for a year, on this host and every subdomain below it. Drop includeSubDomains if other hosts under that name still serve plain HTTP.

sudo tee /etc/nginx/sites-available/odoo.conf > /dev/null <<'EOF'
upstream odoo {
  server 127.0.0.1:8069;
}
upstream odoochat {
  server 127.0.0.1:8072;
}
map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;
}

server {
  listen 80;
  server_name odoo.example.com;

  proxy_read_timeout 720s;
  proxy_connect_timeout 720s;
  proxy_send_timeout 720s;
  client_max_body_size 64m;

  access_log /var/log/nginx/odoo.access.log;
  error_log /var/log/nginx/odoo.error.log;

  # Redirect websocket requests to odoo gevent port
  location /websocket {
    proxy_pass http://odoochat;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_set_header X-Forwarded-Host $http_host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Real-IP $remote_addr;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
    proxy_cookie_flags session_id samesite=lax secure;
  }

  # Redirect requests to odoo backend server
  location / {
    proxy_set_header X-Forwarded-Host $http_host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_redirect off;
    proxy_pass http://odoo;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
    proxy_cookie_flags session_id samesite=lax secure;
  }

  gzip_types text/css text/scss text/plain text/xml application/xml application/json application/javascript;
  gzip on;
}
EOF
sudo ln -sf /etc/nginx/sites-available/odoo.conf /etc/nginx/sites-enabled/odoo.conf
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx

A few notes on this file:

  • proxy_cookie_flags needs Nginx 1.19.8 or later, and Ubuntu 24.04 ships 1.24 (Ubuntu packages: nginx). Because it marks the session cookie secure, you can't log in over plain HTTP. Finish Step 7 before your first login.
  • proxy_http_version 1.1 is my addition to Odoo's sample. Nginx needs HTTP/1.1 to the upstream for the WebSocket upgrade to work (Nginx WebSocket proxying).
  • client_max_body_size 64m is also my addition. Nginx's default of 1 MB rejects most attachment uploads. The docs recommend limiting request size at the proxy, so choose a cap that fits your largest expected file.
  • Serving /<module>/static/ files straight from Nginx is an optional speed-up described in the docs' "Serving static files" section. For the Debian package, the root is /usr/lib/python3/dist-packages/odoo/addons.

Step 7: Enable HTTPS with Let's Encrypt

Odoo sends login credentials in cleartext unless HTTPS terminates in front of it, so the docs treat TLS as mandatory for a secure deployment. The Certbot instructions for Nginx recommend the snap package:

sudo snap install --classic certbot
sudo ln -sf /snap/bin/certbot /usr/local/bin/certbot
sudo certbot --nginx -d odoo.example.com --redirect
sudo certbot renew --dry-run
systemctl list-timers | grep -i certbot

Certbot edits the server block to listen on 443 with your certificate and adds the HTTP-to-HTTPS redirect that the docs recommend. Open https://odoo.example.com and log in with the account you created in Step 5. Logging in as an administrator through your domain sets the database's web.base.url, which Odoo uses in every link it sends to customers (Odoo web base URL docs). Check it under Settings → Technical → System Parameters in developer mode. It should read https://odoo.example.com.

Step 8: Lock down the firewall and SSH

Allow only SSH, HTTP and HTTPS. PostgreSQL (5432) and Odoo's 8069/8072 stay closed because they listen on localhost anyway. Ubuntu's firewall guide covers the ufw application profiles used here:

sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status verbose

Odoo's security section adds three steps worth doing on day one. First, turn off SSH password logins and allow only keys. Second, if you front the server with a WAF or CDN, restrict 80/443 to that provider's IP ranges. Third, block brute-force logins with fail2ban. Odoo logs every failed login, and the docs publish a matching filter:

sudo apt install -y fail2ban
sudo tee /etc/fail2ban/filter.d/odoo-login.conf > /dev/null <<'EOF'
[Definition]
failregex = ^ \d+ INFO \S+ \S+ Login failed for db:\S+ login:\S+ from <HOST>
ignoreregex =
EOF
sudo tee /etc/fail2ban/jail.d/odoo-login.conf > /dev/null <<'EOF'
[odoo-login]
enabled = true
backend = auto
port = http,https
bantime = 900
maxretry = 10
findtime = 60
logpath = /var/log/odoo/odoo-server.log
EOF
sudo systemctl restart fail2ban
sudo fail2ban-client status odoo-login

If a CDN or WAF sits in front of the server, these bans won't take effect at the edge, so use that provider's rate limiting too. Also note that the jail only sees the real client IP because proxy_mode is on and Nginx sends X-Forwarded-For. For broader policies on patching and access control, see my notes on IT management best practices.

Step 9: Back up the database and the filestore

Odoo stores records in PostgreSQL and attachments in the filestore (/var/lib/odoo/filestore/odoo_prod with the data_dir set above). You need both. The docs recommend daily backups of "your databases and filestore data", copied "to a remote archiving server that is not accessible from the server itself". This script uses pg_dump in custom format plus a tarball of the filestore:

sudo install -d -o odoo -g odoo -m 0750 /var/backups/odoo
sudo tee /usr/local/bin/odoo-backup.sh > /dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
DB="odoo_prod"
DEST="/var/backups/odoo"
STAMP="$(date +%F_%H%M)"

pg_dump -Fc -f "$DEST/${DB}_${STAMP}.dump" "$DB"
tar -C /var/lib/odoo/filestore -czf "$DEST/${DB}_${STAMP}_filestore.tar.gz" "$DB"

# keep 14 days locally; the off-site copy is your real retention
find "$DEST" -type f -mtime +14 -delete
EOF
sudo chmod 0755 /usr/local/bin/odoo-backup.sh
sudo -u odoo /usr/local/bin/odoo-backup.sh && sudo ls -lh /var/backups/odoo
# installs a new crontab for the odoo user (replaces any existing one)
echo '30 2 * * * /usr/local/bin/odoo-backup.sh' | sudo crontab -u odoo -

Pull those files from a separate backup host rather than pushing from the VPS. A compromised Odoo server then can't delete your history. If you'd rather have a single file, Odoo 20's CLI can also produce a zip that includes the filestore:

sudo -u odoo odoo db -c /etc/odoo/odoo.conf dump odoo_prod /var/backups/odoo/odoo_prod_$(date +%F).zip

Test a restore, not just a backup

A backup you've never restored is only a hope. Once a month, load the latest dump into a throwaway database. The --neutralize flag of odoo db load runs each module's neutralization script. The base one deactivates mail servers and scheduled actions (base neutralize.sql). Because dbfilter only matches odoo_prod, the test copy never appears to users.

# Option A: restore the pg_dump file and filestore
LATEST=$(sudo -u odoo sh -c 'ls -t /var/backups/odoo/odoo_prod_*.dump | head -1')
sudo -u odoo createdb restore_test
sudo -u odoo pg_restore --no-owner -d restore_test "$LATEST"
sudo -u odoo mkdir -p /var/lib/odoo/filestore/restore_test
sudo -u odoo tar -xzf "${LATEST%.dump}_filestore.tar.gz" -C /var/lib/odoo/filestore/restore_test --strip-components=1
sudo -u odoo psql -d restore_test -c 'SELECT count(*) FROM res_users;'
sudo -u odoo odoo db -c /etc/odoo/odoo.conf drop restore_test

# Option B: restore an Odoo zip, neutralized
sudo -u odoo odoo db -c /etc/odoo/odoo.conf load --neutralize restore_test /var/backups/odoo/odoo_prod_YYYY-MM-DD.zip
sudo -u odoo odoo db -c /etc/odoo/odoo.conf drop restore_test

In Option B, swap YYYY-MM-DD for the date of the zip you're testing. Option A skips neutralization, so do it on a staging server, or don't start a second Odoo process against restore_test. For a full rehearsal, restore on a separate staging VPS and click through a few invoices with attachments. The pg_restore documentation covers parallel restores for large databases.

Step 10: Apply updates safely

Odoo separates updating (new bugfix builds of the same version, no change to your data) from upgrading (moving a database to a new major version, which is irreversible). For updates with the apt repository, the packaging docs say to use the usual apt-get upgrade:

sudo -u odoo /usr/local/bin/odoo-backup.sh
sudo apt-get update
sudo apt-get install --only-upgrade odoo
sudo systemctl restart odoo
sudo tail -n 100 /var/log/odoo/odoo-server.log

Take a backup first, as step 2 of the update guide requires, and run OS patches in the same maintenance window. Treat a major version jump, such as 19 to 20, as a project. Rehearse it on a copy, check every custom module, and read Odoo's upgrade documentation first. Structured requirement gathering helps here, much like the approach in my guide to system analysis techniques.

Troubleshooting common Odoo VPS problems

SymptomLikely causeFix
Nginx returns 502 Bad GatewayOdoo is stopped, crashed on startup or listens on a different portsystemctl status odoo, read /var/log/odoo/odoo-server.log, and confirm 8069 with ss -ltnp
Discuss or live chat doesn't update; websocket errors in the browser consoleNo /websocket location, missing Upgrade/Connection headers, or workers = 0 (the gevent port isn't used in threaded mode)Use the Nginx block above, set workers above 0 and check that 8072 is listening
Links or redirects point to http:// or 127.0.0.1; logs show the proxy IPproxy_mode off, or X-Forwarded-Host not sentSet proxy_mode = True, keep all four proxy_set_header lines, and restart Odoo
Can't log in over HTTP, or the session drops right awayproxy_cookie_flags ... secure needs HTTPSFinish the Certbot step and use the https:// URL
Log shows Virtual memory limit reached and workers restartlimit_memory_soft exceeded (the worker is recycled after the request)Normal now and then. If it happens constantly, find the heavy report or import, or raise the limits in step with available RAM.
Log shows WorkerHTTP (…) timeout after …s or CPU time limit exceededA request exceeded limit_time_real or limit_time_cpuRun long imports and reports as scheduled jobs, or raise the limits moderately
Nginx 413 Request Entity Too Large on uploadclient_max_body_size is too smallRaise it in the server block and reload Nginx
Database selector or "database not found" after disabling the managerdb_name/dbfilter don't match the real database nameCheck with sudo -u postgres psql -l and fix odoo.conf
PDF reports without headers, footers or stylingWrong or missing wkhtmltopdf buildInstall 0.12.6.1 (Step 1) and restart Odoo
FATAL: role "root" does not exist or peer authentication failedAn Odoo or psql command ran as the wrong Unix userPrefix commands with sudo -u odoo
Certbot fails the HTTP challengeDNS doesn't point to the VPS yet, or port 80 is blockedCheck the A record and ufw status, then retry

FAQ

How much RAM does Odoo need on a VPS?

By Odoo's formula, each worker averages about 325 MB (0.8 × 150 MB + 0.2 × 1 GB). A 2 vCPU server with 5 workers needs about 1.6 GB for Odoo, and Odoo's 4-CPU example needs about 3 GB. Add room for PostgreSQL and the OS: 4 GB is a sensible minimum, and 8 GB is comfortable for a few dozen concurrent users.

Can Odoo and PostgreSQL run on the same VPS?

Yes. It's the default for the Debian package, which expects PostgreSQL on the same host and connects over a Unix socket. Split them onto separate machines only when you need more capacity or several Odoo servers sharing one database.

Should I run Odoo Community in Docker in production?

You can, but you have to set workers, memory limits and the proxy yourself, because containers default to the threaded server. As of 25 September 2026 Docker Hub had no Odoo 20 image yet. For a single VPS, the apt repository is simpler to patch.

Why keep list_db = False if I already set admin_passwd?

Odoo's docs describe the database manager as a development and demo tool that "may even expose dangerous features to attackers", and it can hit memory limits on large databases. A strong admin_passwd is a second layer, not a replacement for disabling it.

Wrapping up

A production Odoo VPS comes down to a short list: Ubuntu 24.04 with the official Odoo 20 package, a non-superuser PostgreSQL role, multi-processing workers sized with Odoo's formula, localhost binding with proxy_mode, Nginx with the /websocket route, Let's Encrypt, a tight firewall, and nightly database-plus-filestore backups that you actually restore. Once that's in place, Odoo modules like HR can grow on a stable base. My HRIS implementation guide covers the process side of that rollout.

If you'd like a second pair of eyes on your own Odoo or PostgreSQL server, you can reach me through tamimiqbal.com.

Tamim Iqbal

Tamim Iqbal

IT Manager & AI Developer at REGENERA LUXURY and a certified System Analyst and HRIS Specialist based in Dhaka. He writes about IT management, cloud infrastructure, AI automation and web development from hands-on work. Resume · LinkedIn