AWS · EC2 · Odoo · Ubuntu · Nginx

How to install Odoo 18 on AWS EC2 with Ubuntu 24.04 and Nginx

Odoo 18 on Ubuntu 24.04 over AWS EC2, with a clean setup and a practical security baseline.

In this guide we will walk through how to install Odoo 18 on Ubuntu 24.04 on an AWS EC2 instance using a setup that is simple to understand and easy to maintain. The goal is not only to get the service running, but to leave a sensible foundation for a real domain, HTTPS, reverse proxying and a clear directory structure.

We will build Odoo 18 with Nginx, PostgreSQL, systemd, HTTPS and a firewall, keeping 8069 for internal use only and publishing the application through 80/443, which is the usual pattern when you want to expose it behind a proper domain.

Visual summary of the Odoo 18 deployment on AWS EC2 with Ubuntu 24.04
Visual summary of the architecture and installation flow used in this guide.

What you are building

The final structure is this: EC2 + Ubuntu 24.04 + PostgreSQL + Odoo with a dedicated user + Nginx + HTTPS + port 8069 bound to localhost. It is a clear and solid base for a first small or medium Odoo deployment.

What this guide includes

  • Odoo is not exposed directly; it stays behind Nginx.
  • systemd, UFW, TLS with Certbot and post-install validation steps.
  • Clear directories for code, data, logs and custom addons.
  • A structure you can extend later with backups, database separation or monitoring.
  • A practical validation checklist so you confirm the service is really available.

Once Odoo is up, the next practical step is usually invoicing: How to create an invoice in Odoo 18 step by step.

1. Before you start

This guide is meant for anyone setting up a first Odoo 18 installation on AWS EC2 without jumping into an oversized architecture from day one. If you have a clean Ubuntu instance, a domain and SSH access, this is enough to leave a sensible deployment ready to work with.

You need

  • A fresh EC2 instance running Ubuntu 24.04.
  • SSH access with sudo privileges.
  • A domain you can point to the public IP of the server.

Keep in mind

  • This setup works very well for a first small or medium deployment.
  • If the project grows, you will probably separate database, backups and observability later.
  • The goal here is a clean and maintainable base, not a huge infrastructure from the first day.

2. Recommended architecture for a small or medium EC2 deployment

For a first Odoo 18 setup on AWS, the simple and sensible option is usually an EC2 instance with Ubuntu 24.04, PostgreSQL on the same host and Nginx in front handling HTTP and HTTPS. For a small business or a serious test environment, this is often enough if you size it properly and keep backups in place.

What does not make much sense is exposing 8069 directly to the Internet. The browser should access Odoo through 80/443, while the internal port stays in loopback.

Recommended architecture with Nginx in front of Odoo and local PostgreSQL
Traffic reaches Nginx first; Nginx forwards requests to Odoo on 127.0.0.1:8069.

3. Which EC2 instance to choose and which ports should stay closed

A standard setup usually starts with Ubuntu 24.04, a machine large enough to avoid memory pressure, and a security group that exposes only SSH, HTTP and HTTPS. Port 8069 should remain private.

Rule of thumb: if you are publishing Odoo with a domain, the public entry point should be 443. Port 8069 is an internal service detail, not a public interface.
Visual checklist for EC2 and security group settings for Odoo 18
Visual summary of AMI, size, storage and minimum port exposure.
  • AMI: Ubuntu Server 24.04 LTS.
  • Starting size: avoid old t2 instances; t3.medium or t3.large is usually more sensible depending on load.
  • Disk: 30 GB is a reasonable start; increase it if you expect attachments, documents or multiple databases.
  • Public ports: 22, 80 and 443.
  • Private port: 8069 bound to localhost only.

4. How to prepare Ubuntu 24.04 before installing Odoo 18

Before cloning anything, update the system and install the compilation dependencies, PostgreSQL, Nginx and Certbot. It is also worth enabling ufw from the start so port exposure is not left as an afterthought.

In many tutorials wkhtmltopdf appears too late or as an improvised extra. Odoo still commonly needs it for PDF generation, so it is better to handle it early:

5. Service user and PostgreSQL

The odoo account should not be a human admin user. It should exist only to run the service and own the directories under /opt/odoo. The same idea applies to PostgreSQL: for a local deployment, a dedicated role and local socket authentication are usually enough.

Practical improvement: you do not need a PostgreSQL password if Odoo and PostgreSQL are running on the same machine and you rely on the local socket. Less surface area, fewer secrets and fewer avoidable mistakes.

6. Download Odoo 18, create the virtual environment and install requirements

At this point we download the source code, create the virtual environment and lay out a clean structure under /opt/odoo. Keeping code, data, logs and addons clearly separated helps a lot when you later need to upgrade, troubleshoot or install your own modules.

If you use Odoo Enterprise, remember that you need a valid license and access to the private repository. For Community, the public official repository is enough.

7. Odoo configuration: the minimum to avoid a “lab-only” setup

The /etc/odoo.conf file is where the quality of the setup becomes visible. In this example we define directories, logs, workers, proxy_mode and local listening on 127.0.0.1.

[options]
admin_passwd = CAMBIA_ESTA_CLAVE_MAESTRA
db_host = False
db_port = False
db_user = odoo
db_password = False
addons_path = /opt/odoo/src/odoo/addons,/opt/odoo/custom-addons
data_dir = /opt/odoo/data
logfile = /opt/odoo/logs/odoo.log
proxy_mode = True
xmlrpc_interface = 127.0.0.1
xmlrpc_port = 8069
workers = 4
max_cron_threads = 2
limit_memory_soft = 2147483648
limit_memory_hard = 2684354560
limit_time_cpu = 600
limit_time_real = 1200
  • proxy_mode = True is required if Odoo is published behind Nginx.
  • xmlrpc_interface = 127.0.0.1 prevents direct public exposure.
  • workers depends on CPU and RAM, so adjust it to the actual size of the server.

8. Systemd, Nginx and HTTPS: the part that makes the installation publishable

The systemd service should start automatically, restart if the process crashes and write logs to the journal. Running Odoo manually from an open shell is not a good operating model.

Create the unit file as /etc/systemd/system/odoo18.service. That is the usual place for a custom service you manage yourself on Ubuntu. Open the file with sudo, paste the contents below, save it, then reload systemd and enable the service.

[Unit]
Description=Odoo 18
Documentation=https://www.odoo.com/documentation/18.0/
After=network.target postgresql.service
Requires=postgresql.service

[Service]
Type=simple
User=odoo
Group=odoo
WorkingDirectory=/opt/odoo/src/odoo
ExecStart=/opt/odoo/venv/bin/python3 /opt/odoo/src/odoo/odoo-bin -c /etc/odoo.conf
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=inherit
SyslogIdentifier=odoo18

[Install]
WantedBy=multi-user.target

If everything starts correctly, the terminal output should look similar to this:

ubuntu@ip-172-31-29-9:/tmp$ sudo vim /etc/systemd/system/odoo18.service
ubuntu@ip-172-31-29-9:/tmp$ sudo systemctl daemon-reload
ubuntu@ip-172-31-29-9:/tmp$ sudo systemctl enable odoo18
Created symlink /etc/systemd/system/multi-user.target.wants/odoo18.service → /etc/systemd/system/odoo18.service.
ubuntu@ip-172-31-29-9:/tmp$ sudo systemctl start odoo18
ubuntu@ip-172-31-29-9:/tmp$ sudo systemctl status odoo18
● odoo18.service - Odoo 18
     Loaded: loaded (/etc/systemd/system/odoo18.service; enabled; preset: enabled)
     Active: active (running) since Mon 2026-04-20 08:54:50 UTC; 6s ago
       Docs: https://www.odoo.com/documentation/18.0/
   Main PID: 23599 (python3)
      Tasks: 15 (limit: 1004)
     Memory: 239.9M (peak: 240.1M)
        CPU: 3.066s
     CGroup: /system.slice/odoo18.service
             ├─23599 /opt/odoo/venv/bin/python3 /opt/odoo/src/odoo/odoo-bin -c /etc/odoo.conf
             ├─23602 /opt/odoo/venv/bin/python3 /opt/odoo/src/odoo/odoo-bin -c /etc/odoo.conf
             ├─23603 /opt/odoo/venv/bin/python3 /opt/odoo/src/odoo/odoo-bin -c /etc/odoo.conf
             ├─23604 /opt/odoo/venv/bin/python3 /opt/odoo/src/odoo/odoo-bin -c /etc/odoo.conf
             ├─23606 /opt/odoo/venv/bin/python3 /opt/odoo/src/odoo/odoo-bin -c /etc/odoo.conf
             ├─23607 /opt/odoo/venv/bin/python3 /opt/odoo/src/odoo/odoo-bin gevent -c /etc/odoo.conf
             ├─23609 /opt/odoo/venv/bin/python3 /opt/odoo/src/odoo/odoo-bin -c /etc/odoo.conf
             └─23613 /opt/odoo/venv/bin/python3 /opt/odoo/src/odoo/odoo-bin -c /etc/odoo.conf

Apr 20 08:54:50 ip-172-31-29-9 systemd[1]: Started odoo18.service - Odoo 18.
ubuntu@ip-172-31-29-9:/tmp$ 
Practical note: if the service does not start, the first things to review are the paths in WorkingDirectory, ExecStart and the permissions of the odoo user over /opt/odoo.

Then we publish the application through Nginx so external access goes through the domain and HTTPS, while Odoo itself keeps listening only on localhost.

server {
    listen 80;
    server_name erp.midominio.com;

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

    location / {
        proxy_pass http://127.0.0.1:8069;
        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 /web/static/ {
        proxy_cache_valid 200 90m;
        proxy_buffering on;
        expires 10d;
        proxy_pass http://127.0.0.1:8069;
    }
}

Quick test with a public IP only

If you only want to test the installation, you can publish Odoo using the public IP of the EC2 instance without configuring a domain yet. In that case, the simplest option is to keep plain HTTP on port 80 and use a minimal Nginx block for the IP.

Keep in mind: using only the IP is fine for a quick test, but you normally lose HTTPS with Certbot and the address may change unless you are using an Elastic IP.
server {
    listen 80;
    server_name _;

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

    location / {
        proxy_pass http://127.0.0.1:8069;
        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 /web/static/ {
        proxy_cache_valid 200 90m;
        proxy_buffering on;
        expires 10d;
        proxy_pass http://127.0.0.1:8069;
    }
}

Once Certbot completes, the public entry point is HTTPS. If later you add CloudFront, a load balancer or a WAF, the general pattern still remains the same: Odoo should not be exposed raw to the Internet.

9. Startup checks and minimum validation after deployment

Do not run all checks at once. First validate that Odoo starts as a service, then confirm it is listening on 127.0.0.1:8069, and only after Nginx and DNS are ready should you test the public domain and HTTPS.

These are the basic systemctl commands: daemon-reload reloads unit files, enable makes the service start automatically on boot, start starts it now, and status shows whether it is actually running.

Once the service is up, verify the local listener before involving Nginx:

Only when Nginx is configured should you test the public entry point. Use HTTP first if the domain already points to the server, and use HTTPS only after Certbot has finished successfully.

  • If you have not configured the domain yet, do not expect erp.midominio.com to answer.
  • If Certbot has not been run yet, do not expect https://erp.midominio.com to work.
  • Open https://erp.midominio.com and confirm the setup page or login screen loads.
  • Expose database creation only if you really want that flow available.
  • Review /opt/odoo/logs/odoo.log and journalctl -u odoo18.
  • Confirm the security group does not allow public access to 8069.
  • Take an EC2 snapshot before major changes and keep an external database backup.

10. Common mistakes when installing Odoo on AWS

  • Opening 8069 publicly in EC2 and forgetting the reverse proxy.
  • Choosing an instance that is too small and blaming Odoo when the real issue is memory pressure.
  • Skipping proxy_mode and ending up with broken redirects or wrong URLs.
  • Not planning backups for the database and attachments.
  • Installing everything as ubuntu or root and mixing code with data.
  • Treating a quick-start setup as if it were already production-ready.

11. Final checklist

  • Your domain points to the EC2 instance and HTTPS is working.
  • Port 8069 is not publicly exposed in the security group.
  • Odoo starts with systemd and restart policies are active.
  • Database and attachment backups are defined before real users start working.
  • Logs and snapshots are available before you touch addons or upgrades.

12. Frequently asked questions about Odoo 18 on AWS EC2 and Ubuntu 24.04

Can Odoo 18 run on Ubuntu 24.04?

Yes. Ubuntu 24.04 is a valid base for Odoo 18 on EC2 as long as you manage the dependencies, the Python environment, PostgreSQL and the reverse proxy correctly.

Do I really need Nginx for Odoo on AWS?

If you want to publish Odoo behind a real domain and HTTPS, yes, it makes a lot of sense. Nginx terminates TLS, forwards the right headers and avoids exposing Odoo directly on 8069.

Which ports should be open for Odoo 18 on EC2?

In practice, 22 for administration, 80 for validation and redirection, and 443 for final access. Port 8069 should stay bound to localhost.

Is one EC2 instance enough for Odoo?

For a first small or medium deployment, yes. When usage grows, the usual next steps are separating the database, improving backups, adding observability and maybe introducing load balancing.

Conclusion

Installing Odoo 18 on AWS EC2 with Ubuntu 24.04 is not especially difficult if you keep the deployment ordered from the beginning. The key is to separate responsibilities clearly: operating system, database, service process, reverse proxy and final validation.

With this base in place, you can publish a first clean installation and grow later from a much better starting point. If the project becomes larger, the next logical step is to separate PostgreSQL, automate backups and define a proper upgrade workflow.