Skip to content
Deployment

Install Odoo 19 on Ubuntu 24.04 for Production

2026-07-19 · 16 min read

Last updated: 2026-08-09

This is the exact sequence we run to install Odoo 19 on a fresh Ubuntu 24.04 server for production use, not a quick-start dev setup. Every command below has been run and verified on Ubuntu 24.04 LTS.

1. System update and dependencies

sudo apt update && sudo apt upgrade -y
sudo apt install -y git python3-pip python3-venv python3-dev \
  build-essential wget libpq-dev libxml2-dev libxslt1-dev \
  libjpeg-dev libcairo2-dev libpango1.0-dev libgdk-pixbuf2.0-dev \
  libffi-dev zlib1g-dev fontconfig

2. Create the odoo system user

sudo useradd -m -d /opt/odoo -U -r -s /bin/bash odoo

This creates a dedicated, non-login-shell-restricted system user with its home directory at /opt/odoo, so the Odoo process never runs as root. The -r flag marks it as a system account (UID below the normal user range on Ubuntu), which keeps it out of the regular login user list and is the correct convention for a service account that will only ever be invoked by systemd, never by an interactive login.

3. Install PostgreSQL 16 (Ubuntu 24.04's default)

sudo apt install -y postgresql postgresql-contrib
sudo -u postgres createuser -s odoo

Ubuntu 24.04 ships PostgreSQL 16 by default, which is fully compatible with Odoo 19. The createuser -s flag creates a superuser role, which is the simplest correct setup for a single-server, single-tenant install since Odoo needs to create and drop databases through its own database manager. If you're running a hardened multi-tenant server, see our psycopg2 role troubleshooting post for a narrower grant pattern once the basic install is confirmed working.

Confirm the role was actually created before moving on, rather than assuming the command succeeded silently:

sudo -u postgres psql -c "\du" | grep odoo

4. Install wkhtmltopdf (for PDF reports)

sudo apt install -y wkhtmltopdf

The Ubuntu repository version is patched and headless-compatible, which is what Odoo's report engine needs. Avoid mixing in a manually downloaded build unless you've specifically verified it matches Odoo's expected wkhtmltopdf version. Confirm the binary actually resolves and runs headless before moving on, a broken wkhtmltopdf install surfaces later as a confusing PDF generation failure deep inside a report action, not as an obvious install error:

wkhtmltopdf --version
echo '<h1>test</h1>' > /tmp/test.html && wkhtmltopdf /tmp/test.html /tmp/test.pdf && ls -la /tmp/test.pdf

5. Clone Odoo 19 and set up the Python virtual environment

sudo -u odoo git clone https://github.com/odoo/odoo.git --branch 19.0 --depth 1 /opt/odoo/odoo19
sudo -u odoo python3 -m venv /opt/odoo/odoo19-venv
sudo -u odoo /opt/odoo/odoo19-venv/bin/pip install --upgrade pip wheel
sudo -u odoo /opt/odoo/odoo19-venv/bin/pip install -r /opt/odoo/odoo19/requirements.txt

6. Create the config file, custom addons path, and log directory

sudo mkdir -p /opt/odoo/odoo19-custom-addons /var/log/odoo
sudo chown odoo:odoo /opt/odoo/odoo19-custom-addons /var/log/odoo
sudo nano /etc/odoo19.conf
[options]
admin_passwd = generate_a_real_random_value_here
db_host = False
db_port = False
db_user = odoo
db_password = False
addons_path = /opt/odoo/odoo19/addons,/opt/odoo/odoo19-custom-addons
default_productivity_apps = True
logfile = /var/log/odoo/odoo19.log
workers = 5
limit_time_cpu = 600
limit_time_real = 1200
proxy_mode = True

proxy_mode = True matters, it tells Odoo to trust the X-Forwarded-* headers Nginx sets, without it Odoo won't correctly detect HTTPS and will generate broken links.

The workers = 5 value above is a starting point for a modest production server, not a universal number. Odoo's own guidance is roughly (CPU cores * 2) + 1 for the worker count, then checking that against available RAM at roughly 150 to 300MB per worker depending on module load. A 4-core, 8GB server would land closer to workers = 6 with headroom left for PostgreSQL and the OS. Undersizing this leaves requests queueing under load, oversizing it starves the server of memory, both eventually surface as the kind of 502s covered in our 502 troubleshooting post.

Also set db_maxconn if you have more than a couple of workers, the default connection pool size per worker can add up quickly against PostgreSQL's own max_connections, which is exactly the kind of mismatch covered in cause 5 of our 502 post.

7. systemd unit

sudo nano /etc/systemd/system/odoo19.service
[Unit]
Description=Odoo 19
Requires=postgresql.service
After=network.target postgresql.service

[Service]
Type=simple
SyslogIdentifier=odoo19
PermissionsStartOnly=true
User=odoo
Group=odoo
ExecStart=/opt/odoo/odoo19-venv/bin/python3 /opt/odoo/odoo19/odoo-bin -c /etc/odoo19.conf
StandardOutput=journal+console
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now odoo19
sudo systemctl status odoo19

8. Nginx reverse proxy with SSL

sudo apt install -y nginx certbot python3-certbot-nginx
sudo nano /etc/nginx/sites-available/odoo19
upstream odoo19 {
    server 127.0.0.1:8069;
}
upstream odoo19chat {
    server 127.0.0.1:8072;
}

server {
    listen 80;
    server_name yourdomain.com;

    location / {
        proxy_pass http://odoo19;
        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;
    }

    location /websocket {
        proxy_pass http://odoo19chat;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 3600s;
    }
}
sudo ln -s /etc/nginx/sites-available/odoo19 /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d yourdomain.com

Certbot rewrites the server block to add SSL and a redirect from port 80 automatically, review the result afterward rather than assuming it matches every custom header you added.

9. Ownership and permissions checklist

  • /opt/odoo/odoo19-custom-addons: owned by odoo:odoo, so module installs and upgrades from that path don't hit permission errors.
  • /etc/odoo19.conf: readable by the odoo user, not world-readable, it contains admin_passwd.
  • /var/log/odoo: owned by odoo:odoo so the systemd service can write to it.
sudo chmod 640 /etc/odoo19.conf
sudo chown root:odoo /etc/odoo19.conf

640 permissions with root:odoo ownership lets the odoo group read the config (needed for the systemd service to load it) while blocking read access for any other user on the server, which matters because the file contains admin_passwd in plain text.

10. Firewall and log rotation

A fresh Ubuntu 24.04 server should have UFW enabled with only the ports you actually need exposed. Odoo's own ports (8069, 8072) should never be directly internet-facing, only Nginx's 80/443 should be:

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

Confirm 8069 and 8072 are not listed as allowed from the public internet, they should only be reachable from 127.0.0.1, which is the default if Odoo is configured to bind locally and Nginx is the only thing proxying to them.

Odoo's own log file will grow indefinitely without rotation. Ubuntu's logrotate handles this cleanly with a small config file:

sudo nano /etc/logrotate.d/odoo19
/var/log/odoo/odoo19.log {
    daily
    missingok
    rotate 14
    compress
    delaycompress
    notifempty
    copytruncate
}

copytruncate is the correct choice here rather than the default rotation strategy, since Odoo holds the log file open for the life of the process and doesn't respond to a rotation signal to reopen it, copytruncate copies the current content then truncates the original in place, avoiding a gap in logging or a restart requirement.

Common mistakes

  • Skipping proxy_mode. Odoo generates broken HTTP links behind an SSL-terminating Nginx without it.
  • Running Odoo as root. Never do this in production, it's an unnecessary privilege escalation surface for zero benefit.
  • Forgetting the websocket location block. The main web client works fine, but real-time features silently fail. See our WebSocket troubleshooting post if you hit this.
  • Leaving PostgreSQL defaults untouched. The out-of-the-box shared_buffers and max_connections are not sized for production. See our PostgreSQL tuning guide immediately after this install.
  • Exposing 8069/8072 directly to the internet. Beyond being unnecessary, it bypasses Nginx's SSL termination and any rate limiting you've configured there. Confirm with sudo ufw status and sudo ss -tlnp | grep 806 that these ports are bound to 127.0.0.1, not 0.0.0.0.
  • No log rotation configured. A silently growing log file on a small root volume is a slow-motion disk-full incident, which can cascade into PostgreSQL write failures long before anyone notices the actual cause.
  • Forgetting to test the systemd unit survives a reboot. sudo systemctl enable without a real reboot test is a common gap, confirm with sudo systemctl is-enabled odoo19 and, if this is a new server, an actual reboot before calling the install done.

Sources: Odoo installation documentation, Nginx documentation.

Talal Yousaf, Senior DevOps Engineer at DevFusion Tech. 70+ Odoo deployments across Community and Enterprise, versions 13 through 19. More about DevFusion · LinkedIn

Published 2026-07-19 · updated 2026-08-09

ShareLinkedInXWhatsApp

Hit the same wall in production?

Tell us what you're running and where it breaks. A 30 minute call, no pitch deck, and a straight answer on whether we can help.