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.
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:
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 withproxy_mode = Truein Odoo's config, this tells Odoo whether the original request was HTTP or HTTPS. - •
X-Real-IPandX-Forwarded-For— Without these, every request Odoo sees comes from127.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:
# Ubuntu/Debian
sudo apt update
sudo apt install certbot python3-certbot-nginxObtain the certificate:
sudo certbot --nginx -d odoo.example.comCertbot modifies your Nginx config to add the SSL directives. But for a clean setup, here is the full SSL server block you should use:
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:
sudo systemctl status certbot.timerCertificates 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.
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:
workers = 4
max_cron_threads = 2When 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:
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:
# 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 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:
| Header | Purpose |
|---|---|
| X-Frame-Options: SAMEORIGIN | Prevents clickjacking by blocking your Odoo instance from being embedded in iframes on other domains |
| X-Content-Type-Options: nosniff | Stops browsers from MIME-type sniffing — a file declared as CSS will not be executed as JavaScript |
| Strict-Transport-Security | Tells browsers to always use HTTPS for your domain. The max-age=31536000 sets this for one year |
| Referrer-Policy | Controls how much URL information is sent when users click external links |
| Permissions-Policy | Disables browser APIs you do not need (camera, microphone, geolocation) |
| Content-Security-Policy | Restricts 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:
# 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;# 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 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 aVary: Accept-Encodingheader 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/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:
# 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 nginxAlways 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.
| Feature | Nginx | Traefik |
|---|---|---|
| Configuration | Static config files | Dynamic, auto-discovers services |
| SSL certificates | Certbot (separate tool) | Built-in Let's Encrypt with auto-renewal |
| Docker integration | Manual upstream config | Reads Docker labels, auto-configures routes |
| Learning curve | Lower — huge community, abundant examples | Higher — but powerful once learned |
| Performance | Slightly better for static file serving | Comparable for proxying |
| Multi-service routing | Needs manual config per service | Handles automatically via labels |
| Best for | Single-server Odoo, native installs | Docker 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.
# 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.logReport Generation Times Out
Cause: Large PDF reports (invoices, stock reports) exceed the default proxy timeout.
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.
client_max_body_size 200m; # Adjust based on your needsSet 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:
proxy_set_header X-Forwarded-Proto https;2. In odoo.conf, enable proxy mode:
proxy_mode = TrueBoth 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:
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:
proxy_mode = TrueOr 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.
- •See pricing
- •Generate a Docker Compose file (includes Nginx config)
- •Read the Odoo Docker guide for the full deployment picture
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
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.
Related Resources
Odoo Docker Guide
Complete guide to running Odoo in Docker containers.
Docker Compose for Odoo
Production-ready Docker Compose setup with PostgreSQL and Nginx.
Odoo Docker Hub Reference
Official image tags, versions, and environment variables.
Enterprise vs Community Docker
Run Enterprise or Community Edition with Docker.
Odoo Ubuntu Install Guide
Install Odoo natively on Ubuntu without Docker.
Docker Compose Generator
Generate production-ready Docker Compose files visually.
Server Calculator
Calculate the right server size for your Odoo workload.
Deploy Odoo Guide
Step-by-step guide to deploy Odoo on any cloud provider.