Skip to main content
Infrastructure Guide

Odoo Nginx Config — Production Reverse Proxy Setup

Production-ready Nginx configuration for Odoo. Reverse proxy, SSL, websocket for live chat, security headers, and performance tuning. Copy-paste ready.

15 min read
Updated February 2026
Odoo 16/17/18/19

Running Odoo without a reverse proxy in production is asking for trouble. Odoo's built-in HTTP server handles application logic just fine, but it was never designed to terminate SSL, serve static files efficiently, handle websocket upgrades for live chat, or protect against brute force attacks. That is what Nginx is for.

This guide gives you a complete, production-ready Nginx configuration for Odoo. Every section builds on the previous one, and at the end you get a single config file you can copy into your server and adapt.

Why Nginx for Odoo?

Odoo's Werkzeug-based HTTP server listens on port 8069 (and 8072 for longpolling/websockets). In development, hitting http://localhost:8069 directly is fine. In production, you need a layer in front for several reasons:

  • SSL/TLS termination. Nginx handles HTTPS so Odoo does not have to. Odoo has no built-in SSL support — it expects a reverse proxy to handle encryption.
  • Static file serving. Nginx serves CSS, JS, and images from Odoo's static paths orders of magnitude faster than Python can.
  • Websocket handling. Odoo's live chat, discuss module, and real-time notifications use longpolling or websockets on port 8072. Nginx routes these correctly.
  • Security headers. HSTS, CSP, X-Frame-Options — these belong in the reverse proxy, not the application.
  • Rate limiting. Protect login pages and API endpoints from brute force without modifying Odoo code.
  • Load balancing. When running multiple Odoo workers or instances, Nginx distributes traffic.
  • Request buffering. Nginx buffers slow client uploads so Odoo workers are not tied up waiting for data.

If you are running Odoo with Docker, Nginx is typically a separate container in your Compose stack. If you installed Odoo natively on Ubuntu, Nginx runs as a system service on the same machine.

Basic Nginx Server Block for Odoo

This is the minimal working configuration — no SSL, no websockets, just Nginx proxying requests to Odoo on port 8069:

/etc/nginx/sites-available/odoonginx
upstream odoo {
    server 127.0.0.1:8069;
}

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

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

    # Increase request body size for file uploads
    client_max_body_size 200m;

    # Proxy timeouts — Odoo reports can take a while
    proxy_connect_timeout 720s;
    proxy_send_timeout 720s;
    proxy_read_timeout 720s;

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

Key settings explained:

  • client_max_body_size 200m — Odoo users upload attachments, import CSVs, and install modules. The default 1 MB limit will break all of these.
  • proxy_read_timeout 720s — Odoo PDF report generation, large exports, and database operations can take minutes. 720 seconds gives them room to complete.
  • X-Forwarded-Proto $scheme — Combined with proxy_mode = True in Odoo's config, this tells Odoo whether the original request was HTTP or HTTPS.
  • X-Real-IP and X-Forwarded-For — Without these, every request Odoo sees comes from 127.0.0.1. You need the real client IP for logging, security, and rate limiting.

Odoo side: Make sure proxy_mode = True is set in your odoo.conf. Without it, Odoo ignores the proxy headers and generates incorrect URLs.

SSL/HTTPS with Let's Encrypt

No production Odoo deployment should run over plain HTTP. Let's Encrypt provides free certificates with automated renewal via Certbot.

Install Certbot:

Terminalbash
# Ubuntu/Debian
sudo apt update
sudo apt install certbot python3-certbot-nginx

Obtain the certificate:

Terminalbash
sudo certbot --nginx -d odoo.example.com

Certbot modifies your Nginx config to add the SSL directives. But for a clean setup, here is the full SSL server block you should use:

/etc/nginx/sites-available/odoonginx
upstream odoo {
    server 127.0.0.1:8069;
}

# Redirect all HTTP to HTTPS
server {
    listen 80;
    server_name odoo.example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name odoo.example.com;

    # SSL certificates (managed by Certbot)
    ssl_certificate /etc/letsencrypt/live/odoo.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/odoo.example.com/privkey.pem;

    # SSL configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 1.1.1.1 8.8.8.8 valid=300s;

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

    client_max_body_size 200m;

    proxy_connect_timeout 720s;
    proxy_send_timeout 720s;
    proxy_read_timeout 720s;

    location / {
        proxy_pass http://odoo;
        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 https;
        proxy_redirect off;
    }
}

Auto-renewal: Certbot installs a systemd timer or cron job automatically. Verify it is active:

Terminalbash
sudo systemctl status certbot.timer

Certificates renew 30 days before expiry. Nginx reloads automatically after renewal if you used the --nginx plugin.

Websocket Configuration for Live Chat & Longpolling

Odoo uses a separate process for real-time features: live chat, Discuss (internal messaging), bus notifications, and spreadsheet collaboration. This process listens on port 8072 by default.

Without proper websocket/longpolling configuration in Nginx, your users will see live chat fail silently, Discuss messages not updating in real time, and notification badges not appearing.

/etc/nginx/sites-available/odoonginx
upstream odoo {
    server 127.0.0.1:8069;
}

upstream odoo-websocket {
    server 127.0.0.1:8072;
}

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 443 ssl http2;
    server_name odoo.example.com;

    # ... SSL config from previous section ...

    client_max_body_size 200m;

    proxy_connect_timeout 720s;
    proxy_send_timeout 720s;
    proxy_read_timeout 720s;

    # Websocket / longpolling
    location /websocket {
        proxy_pass http://odoo-websocket;
        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 https;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_redirect off;
    }

    # Longpolling fallback (Odoo 16 and earlier)
    location /longpolling {
        proxy_pass http://odoo-websocket;
        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 https;
        proxy_redirect off;
    }

    # Everything else goes to the main Odoo process
    location / {
        proxy_pass http://odoo;
        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 https;
        proxy_redirect off;
    }
}

Version note: Odoo 17+ uses the /websocket endpoint with proper WebSocket protocol. Odoo 16 and earlier use /longpolling with HTTP long-polling. The config above handles both. If you are only running Odoo 17+, you can remove the /longpolling block.

The map directive translates the Upgrade HTTP header into the correct Connection header value. When a browser requests a WebSocket upgrade, Nginx passes it through. For regular HTTP requests, it closes normally.

Multi-Worker Configuration

For production with multiple concurrent users, Odoo should run with multiple workers. This is configured in odoo.conf:

odoo.confini
workers = 4
max_cron_threads = 2

When running workers, Odoo automatically uses port 8072 for the gevent-based websocket/longpolling process. The Nginx upstream block can also handle multiple Odoo instances for horizontal scaling:

Horizontal scaling upstreamsnginx
upstream odoo {
    server 10.0.1.10:8069;
    server 10.0.1.11:8069;
    server 10.0.1.12:8069;

    # Sticky sessions — required for Odoo
    ip_hash;
}

upstream odoo-websocket {
    server 10.0.1.10:8072;
    server 10.0.1.11:8072;
    server 10.0.1.12:8072;

    ip_hash;
}

Sticky sessions are mandatory. Odoo stores session data server-side. If a user's requests bounce between different backend servers without session affinity, they will be logged out randomly. The ip_hash directive routes all requests from the same client IP to the same backend.

For more sophisticated load balancing (e.g., if users share a corporate IP), consider using Nginx's sticky directive (requires the commercial Nginx Plus) or move session storage to a shared Redis instance.

Static File Caching

Odoo serves all its static assets (JavaScript, CSS, images, fonts) through the application server. Nginx can intercept these requests and serve them faster, with proper cache headers:

Static file caching locationsnginx
# Cache static assets
location ~* /web/static/ {
    proxy_pass http://odoo;
    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 https;

    expires 7d;
    add_header Cache-Control "public, immutable";
    access_log off;
}

# Cache uploaded content (images in website, product photos)
location ~* /web/content/ {
    proxy_pass http://odoo;
    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 https;

    expires 1d;
    add_header Cache-Control "public";
    access_log off;
}

# Cache website images
location ~* /web/image/ {
    proxy_pass http://odoo;
    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 https;

    expires 7d;
    add_header Cache-Control "public";
    access_log off;
}

Why proxy_pass instead of serving files directly? Odoo generates static bundles dynamically and stores them in the database or filestore. Unlike a traditional web app where static files sit in a directory, Odoo's static assets go through the application. The caching headers tell browsers to hold onto these files so they do not re-request them on every page load.

access_log off for static files keeps your log files focused on actual page requests and API calls.

Security Headers

These headers protect against common web attacks and should be present on every production Odoo deployment:

Security headersnginx
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

# Content Security Policy — adjust based on your integrations
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; frame-ancestors 'self';" always;

Header breakdown:

HeaderPurpose
X-Frame-Options: SAMEORIGINPrevents clickjacking by blocking your Odoo instance from being embedded in iframes on other domains
X-Content-Type-Options: nosniffStops browsers from MIME-type sniffing — a file declared as CSS will not be executed as JavaScript
Strict-Transport-SecurityTells browsers to always use HTTPS for your domain. The max-age=31536000 sets this for one year
Referrer-PolicyControls how much URL information is sent when users click external links
Permissions-PolicyDisables browser APIs you do not need (camera, microphone, geolocation)
Content-Security-PolicyRestricts which sources can load scripts, styles, and other resources

CSP warning for Odoo: Odoo heavily uses inline scripts and eval() for its web client. That is why 'unsafe-inline' and 'unsafe-eval' are required for script-src. This is not ideal from a security standpoint, but removing them breaks Odoo's UI entirely. The CSP above is the tightest policy that still allows Odoo to function.

If you use third-party integrations (Google Analytics, payment gateways, chat widgets), you will need to add their domains to the relevant CSP directives.

Rate Limiting

Protect your Odoo instance from brute force login attempts and API abuse:

Rate limit zones (http block)nginx
# Define rate limit zones (place in http block, outside server block)
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/m;
limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;
Rate-limited locations (server block)nginx
# Inside the server block:

# Rate limit login endpoints
location /web/login {
    limit_req zone=login burst=3 nodelay;
    limit_req_status 429;

    proxy_pass http://odoo;
    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 https;
    proxy_redirect off;
}

# Rate limit JSON-RPC API
location /jsonrpc {
    limit_req zone=api burst=10 nodelay;
    limit_req_status 429;

    proxy_pass http://odoo;
    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 https;
    proxy_redirect off;
}

# Rate limit XML-RPC API
location /xmlrpc {
    limit_req zone=api burst=10 nodelay;
    limit_req_status 429;

    proxy_pass http://odoo;
    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 https;
    proxy_redirect off;
}

What this does: The login endpoint is limited to 5 requests per minute per IP, with a burst allowance of 3. That means a user can try to log in 5 times per minute — plenty for legitimate use, but a brute force script will hit 429 errors almost immediately.

The API endpoints (/jsonrpc and /xmlrpc) get a higher limit of 30 requests per minute because integrations and automated scripts make frequent API calls.

Gzip Compression

Odoo responses — especially JSON-RPC payloads and HTML pages — compress well. Enabling gzip in Nginx reduces bandwidth usage and speeds up page loads significantly:

Gzip configurationnginx
# Gzip compression (place in http block or server block)
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5;
gzip_min_length 256;
gzip_types
    text/plain
    text/css
    text/javascript
    application/javascript
    application/json
    application/xml
    application/xml+rss
    image/svg+xml
    font/woff2;

Settings explained:

  • gzip_comp_level 5 — A good balance between compression ratio and CPU usage. Level 1 is fast but barely compresses. Level 9 squeezes out a few more percent but uses significantly more CPU. Level 5 gets you ~90% of the benefit at ~50% of the CPU cost.
  • gzip_min_length 256 — Do not bother compressing responses smaller than 256 bytes. The gzip overhead makes tiny responses larger, not smaller.
  • gzip_proxied any — Compress responses even when Nginx is acting as a reverse proxy (which is always, in this setup).
  • gzip_vary on — Adds a Vary: Accept-Encoding header so CDNs and caches store compressed and uncompressed versions separately.

Odoo's JSON-RPC responses are often 50–200 KB of uncompressed JSON. Gzip typically reduces these to 10–15% of their original size.

Complete Production Nginx Config

Here is the full configuration combining every section above. Copy this to /etc/nginx/sites-available/odoo and symlink it to sites-enabled:

/etc/nginx/sites-available/odoonginx
# /etc/nginx/sites-available/odoo

# Rate limit zones
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/m;

# Upstreams
upstream odoo {
    server 127.0.0.1:8069;
}

upstream odoo-websocket {
    server 127.0.0.1:8072;
}

# Websocket upgrade map
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

# Redirect HTTP to HTTPS
server {
    listen 80;
    server_name odoo.example.com;
    return 301 https://$server_name$request_uri;
}

# Main HTTPS server
server {
    listen 443 ssl http2;
    server_name odoo.example.com;

    # ----- SSL -----
    ssl_certificate /etc/letsencrypt/live/odoo.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/odoo.example.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 1.1.1.1 8.8.8.8 valid=300s;

    # ----- Security Headers -----
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; frame-ancestors 'self';" always;

    # ----- Logging -----
    access_log /var/log/nginx/odoo-access.log;
    error_log /var/log/nginx/odoo-error.log;

    # ----- Gzip -----
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 5;
    gzip_min_length 256;
    gzip_types
        text/plain
        text/css
        text/javascript
        application/javascript
        application/json
        application/xml
        application/xml+rss
        image/svg+xml
        font/woff2;

    # ----- General Settings -----
    client_max_body_size 200m;

    proxy_connect_timeout 720s;
    proxy_send_timeout 720s;
    proxy_read_timeout 720s;
    proxy_buffers 16 64k;
    proxy_buffer_size 128k;

    # ----- Rate-Limited Endpoints -----

    location /web/login {
        limit_req zone=login burst=3 nodelay;
        limit_req_status 429;

        proxy_pass http://odoo;
        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 https;
        proxy_redirect off;
    }

    location /jsonrpc {
        limit_req zone=api burst=10 nodelay;
        limit_req_status 429;

        proxy_pass http://odoo;
        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 https;
        proxy_redirect off;
    }

    location /xmlrpc {
        limit_req zone=api burst=10 nodelay;
        limit_req_status 429;

        proxy_pass http://odoo;
        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 https;
        proxy_redirect off;
    }

    # ----- Websocket / Longpolling -----

    location /websocket {
        proxy_pass http://odoo-websocket;
        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 https;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_redirect off;
    }

    location /longpolling {
        proxy_pass http://odoo-websocket;
        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 https;
        proxy_redirect off;
    }

    # ----- Static File Caching -----

    location ~* /web/static/ {
        proxy_pass http://odoo;
        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 https;

        expires 7d;
        add_header Cache-Control "public, immutable";
        access_log off;
    }

    location ~* /web/content/ {
        proxy_pass http://odoo;
        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 https;

        expires 1d;
        add_header Cache-Control "public";
        access_log off;
    }

    location ~* /web/image/ {
        proxy_pass http://odoo;
        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 https;

        expires 7d;
        add_header Cache-Control "public";
        access_log off;
    }

    # ----- Default: Proxy to Odoo -----

    location / {
        proxy_pass http://odoo;
        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 https;
        proxy_redirect off;
    }
}

To deploy this config:

Terminalbash
# Copy the config
sudo cp odoo-nginx.conf /etc/nginx/sites-available/odoo

# Enable it
sudo ln -s /etc/nginx/sites-available/odoo /etc/nginx/sites-enabled/odoo

# Remove the default site (optional)
sudo rm /etc/nginx/sites-enabled/default

# Test the configuration
sudo nginx -t

# Reload Nginx
sudo systemctl reload nginx

Always run nginx -t before reloading. A syntax error in the config will take your entire Nginx instance down on reload if you skip the test.

Nginx vs Traefik for Odoo

Both Nginx and Traefik work well as reverse proxies for Odoo. The right choice depends on your deployment style.

FeatureNginxTraefik
ConfigurationStatic config filesDynamic, auto-discovers services
SSL certificatesCertbot (separate tool)Built-in Let's Encrypt with auto-renewal
Docker integrationManual upstream configReads Docker labels, auto-configures routes
Learning curveLower — huge community, abundant examplesHigher — but powerful once learned
PerformanceSlightly better for static file servingComparable for proxying
Multi-service routingNeeds manual config per serviceHandles automatically via labels
Best forSingle-server Odoo, native installsDocker Compose / Swarm, multiple services

Use Nginx when:

  • You are running Odoo natively on a server (not Docker)
  • You want fine-grained control over caching, rate limiting, and headers
  • You have a single Odoo instance behind the proxy
  • Your team already knows Nginx

Use Traefik when:

  • You are running everything in Docker Compose
  • You want automatic SSL without managing Certbot
  • You have multiple services (Odoo, pgAdmin, Grafana) that all need reverse proxying
  • You want Docker-native configuration via container labels

If you are using our Docker Compose Generator, you can choose between Nginx and Traefik as your reverse proxy option. The generator outputs the correct config for either.

Common Nginx Issues with Odoo

These are the problems that come up repeatedly when running Nginx in front of Odoo:

502 Bad Gateway

Cause: Nginx cannot connect to the Odoo backend. Either Odoo is not running, it crashed, or the upstream address is wrong.

Debugging 502bash
# Check if Odoo is actually running
sudo systemctl status odoo
# or for Docker:
docker compose ps

# Check if Odoo is listening on the expected port
ss -tlnp | grep 8069

# Check Nginx error log for details
tail -f /var/log/nginx/odoo-error.log

Report Generation Times Out

Cause: Large PDF reports (invoices, stock reports) exceed the default proxy timeout.

Fix: increase timeoutsnginx
proxy_read_timeout 720s;
proxy_send_timeout 720s;

If 720 seconds is not enough for your reports, you likely have a database performance issue rather than an Nginx issue.

File Upload Fails (413 Request Entity Too Large)

Cause: The uploaded file exceeds client_max_body_size.

Fix: increase body sizenginx
client_max_body_size 200m;  # Adjust based on your needs

Set this to the largest file your users might reasonably upload. Module installation files, CSV imports for data migration, and product image batches can be tens or hundreds of megabytes.

Mixed Content Warnings

Cause: Odoo generates HTTP URLs even though the site is served over HTTPS.

Fix: Two things must be configured together:

1. In Nginx, set the forwarded proto header:

Nginxnginx
proxy_set_header X-Forwarded-Proto https;

2. In odoo.conf, enable proxy mode:

odoo.confini
proxy_mode = True

Both are required. Without proxy_mode, Odoo ignores the X-Forwarded-Proto header entirely.

Live Chat / Discuss Not Working

Cause: WebSocket or longpolling requests are not reaching Odoo's gevent process on port 8072.

Fix: Add the websocket and longpolling location blocks as shown in the Websocket Configuration section. Also verify that Odoo is running with workers > 0 — the gevent process only starts in multi-worker mode.

Odoo Shows 127.0.0.1 for All Requests

Cause: Proxy headers are missing or proxy_mode is not enabled.

Fix: Ensure all three headers are set:

Nginx proxy headersnginx
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;

And in odoo.conf:

odoo.confini
proxy_mode = True

Or Skip Nginx Config — Deploy on OEC.sh

Nginx configuration is one of those things that is straightforward once you have done it a few times, but the first time involves a lot of debugging. And then you have to maintain it: certificate renewals, security updates, config changes when you add services.

OEC.sh handles reverse proxy, SSL, websocket routing, and load balancing automatically. You pick your cloud provider, deploy Odoo, and the infrastructure layer is configured for you. No Nginx files to manage, no Certbot to babysit, no debugging 502s at 2 AM.

You still get full SSH access. If you want to customize the Nginx config on your OEC.sh-deployed server, you can. But most teams never need to.

Skip the Nginx config entirely

OEC.sh configures reverse proxy, SSL, websockets, and load balancing automatically — on any cloud provider, with both Community and Enterprise support.

  • Free tier available
  • No credit card required
  • Auto SSL & reverse proxy
Try OEC.sh Free

Frequently Asked Questions

Do I need Nginx if I'm running Odoo in Docker?

Yes. The Odoo Docker container does not include a reverse proxy. You need either Nginx (as a separate container or host service) or Traefik in front of Odoo for SSL, security headers, and websocket handling. Running Odoo's port 8069 exposed directly to the internet is not safe for production.

What ports does Odoo use that Nginx needs to proxy?

Port 8069 for the main HTTP interface and port 8072 for websocket/longpolling. If you run Odoo with workers = 0 (single process mode, not recommended for production), all traffic goes through 8069. With workers > 0, real-time features require separate routing to 8072.

How do I get Nginx to work with Odoo's longpolling?

Add a separate upstream block pointing to port 8072 and route /websocket (Odoo 17+) and /longpolling (Odoo 16 and earlier) to that upstream. Include the Upgrade and Connection headers for WebSocket support. See the Websocket Configuration section for the complete config.

Why do I get mixed content warnings after setting up SSL?

Odoo generates URLs based on what it thinks the protocol is. If proxy_mode = True is not set in odoo.conf, Odoo assumes HTTP even when Nginx is terminating HTTPS. Set proxy_mode = True and ensure Nginx sends X-Forwarded-Proto https.

Should I use Nginx or Apache for Odoo?

Use Nginx. Apache can work, but Nginx handles concurrent connections more efficiently (event-driven vs. thread-per-connection), has better websocket support, and the Odoo community has standardized on Nginx. Nearly all Odoo deployment guides, including Odoo's own documentation, use Nginx examples.

How do I configure Nginx for multiple Odoo instances on one server?

Create separate server blocks with different server_name directives, each pointing to a different Odoo upstream port. For example, erp.example.com proxies to 127.0.0.1:8069 and staging.example.com proxies to 127.0.0.1:8169. Each Odoo instance needs its own set of ports configured in its odoo.conf.

Deploy Odoo Without the Nginx Headache

Reverse proxy, SSL, websockets, rate limiting, and gzip — configured automatically. Pick your cloud provider, deploy Odoo, and focus on your business instead of infrastructure.