Odoo deployment
How to host Odoo on a subdomain with HTTPS
This guide shows how to expose a self-hosted Odoo instance safely through a dedicated subdomain using DNS, Nginx, HTTPS, proxy headers, websocket routing, and database manager protection.
Target architecture
Odoo should stay private on local ports. Public traffic should reach Nginx first, and Nginx should proxy trusted requests to Odoo.
Browser
→ https://odoo.example.com
→ Nginx on 80/443
→ 127.0.0.1:8069 Odoo
→ 127.0.0.1:8072 Odoo websocket
→ private PostgreSQLRead this before changing Nginx or DNS
Planning
Design decisions to make before changing DNS, Nginx, HTTPS, or public access.
DNS change
Domain records that affect where the subdomain points. DNS may take time to propagate.
Safe check
Read-only commands or manual checks that should not change the server state.
Caution
Checks that may reveal secrets or require careful interpretation before taking action.
Change required
Actions that can affect DNS, HTTPS, Nginx, access, or production availability. Plan before applying.
Deployment checklist
1. Choose a dedicated Odoo subdomain
PlanningOdoo should be exposed through a clean HTTPS subdomain instead of directly through port 8069. This keeps the public entry point controlled by Nginx.
Show command and interpretation
Command or manual check
Recommended example:
odoo.example.com
Avoid exposing Odoo directly through:
http://your-server-ip:8069How to interpret the result
The public URL should be a normal HTTPS domain.
Good:
- https://odoo.example.com
Avoid:
- http://your-server-ip:8069
- http://your-server-ip:8072
- public Docker bindings such as 0.0.0.0:80692. Create the DNS record
DNS changeThe subdomain must point to the public IP address of the server that will host Odoo before Certbot can issue an HTTPS certificate.
Show command and interpretation
Command or manual check
# In your DNS provider, create:
Type: A
Name: odoo
Value: YOUR_SERVER_PUBLIC_IP
TTL: Auto or default
# Then verify from your computer or server:
dig +short odoo.example.com
# Optional: compare with public DNS resolvers
dig @8.8.8.8 +short odoo.example.com
dig @1.1.1.1 +short odoo.example.com
# If the public DNS resolvers return the right IP
# but the local server still returns nothing or an old value:
sudo resolvectl flush-caches
sudo systemctl restart systemd-resolved
# Then test again:
dig +short odoo.example.comHow to interpret the result
The command should return the public IP address of the server that will host Odoo.
Good result:
- YOUR_SERVER_PUBLIC_IP
If it returns nothing, the old IP, or a different IP:
- wait for DNS propagation
- confirm the DNS zone is the correct one
- confirm there is no conflicting CNAME or A record
- confirm you are editing the DNS provider actually used by the domain
- compare local DNS with public resolvers such as 8.8.8.8 and 1.1.1.1
If public resolvers return the correct IP but the server still returns nothing, the server may have a stale local DNS cache. Flushing systemd-resolved can fix this on Ubuntu-based servers.
If Odoo is hosted on an office/on-premise server instead of a VPS:
- the public IP must route to that server or firewall
- ports 80 and 443 must be forwarded to the reverse proxy
- the server should still avoid exposing Odoo 8069, Odoo 8072, or PostgreSQL 5432 directly3. Confirm Odoo is only listening locally
Safe checkBefore exposing Odoo through Nginx, confirm Odoo and PostgreSQL are not already exposed directly to the internet.
Show command and interpretation
Command or manual check
sudo ss -tulpn | grep -E ":(8069|8072|5432)"
docker ps --format "table {{.Names}}\t{{.Ports}}"How to interpret the result
Safer examples:
- 127.0.0.1:8069
- 127.0.0.1:8072
- PostgreSQL internal Docker network only
- PostgreSQL on 127.0.0.1 only
Risky examples:
- 0.0.0.0:8069
- 0.0.0.0:8072
- 0.0.0.0:5432
- 0.0.0.0:8069->8069/tcp in Docker
If Odoo is already public, fix the binding before continuing.4. Confirm local Odoo responds
Safe checkNginx should proxy to a working local Odoo service. Test Odoo locally before debugging DNS or HTTPS.
Show command and interpretation
Command or manual check
curl -I http://127.0.0.1:8069/
curl -I http://127.0.0.1:8069/web/database/managerHow to interpret the result
The local Odoo homepage may return 200, 303, or another normal Odoo response.
The database manager route may exist locally. That is not automatically dangerous if Odoo is local-only.
The important goal is:
- local Odoo works
- public direct port 8069 is not exposed
- public access will go through Nginx only5. Create the Nginx reverse proxy config
Change requiredNginx will become the public HTTPS entry point and forward traffic to local Odoo services.
Show command and interpretation
Command or manual check
sudo nano /etc/nginx/sites-available/odoo.example.comHow to interpret the result
Create a dedicated Nginx site for the Odoo subdomain.
Do not put Odoo inside the main website or application Nginx config unless you intentionally want them coupled.
Use a separate server block so Odoo can be managed, secured, tested, and disabled independently from the main application.6. Example Nginx config for Odoo
Change requiredThis config proxies normal Odoo traffic to 8069, websocket traffic to 8072, and blocks sensitive database manager routes publicly.
Show command and interpretation
Command or manual check
server {
listen 80;
listen [::]:80;
server_name odoo.example.com;
client_max_body_size 100M;
# Certbot will later replace this with HTTPS redirects/config.
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;
proxy_read_timeout 720s;
proxy_connect_timeout 720s;
proxy_send_timeout 720s;
proxy_buffering off;
}
# Odoo websocket / longpolling
location /websocket {
proxy_pass http://127.0.0.1:8072;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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_read_timeout 720s;
proxy_connect_timeout 720s;
proxy_send_timeout 720s;
}
# Extra protection for sensitive Odoo database manager routes
location ~* ^/web/database/(manager|selector|create|drop|backup|restore|duplicate) {
return 403;
}
}How to interpret the result
Important points:
- public traffic goes to Nginx
- Nginx proxies Odoo to 127.0.0.1:8069
- websocket traffic goes to 127.0.0.1:8072
- sensitive /web/database routes are blocked publicly
- proxy headers are set so Odoo understands HTTPS and client IPs
- client_max_body_size allows normal Odoo uploads, imports, and attachments without hitting Nginx's small default upload limit
This assumes Odoo has proxy_mode = True in odoo.conf.7. Enable the Nginx site
Change requiredAfter creating the config, enable it with a symlink and test Nginx before reloading.
Show command and interpretation
Command or manual check
sudo ln -s /etc/nginx/sites-available/odoo.example.com /etc/nginx/sites-enabled/odoo.example.com
sudo nginx -t
sudo systemctl reload nginxHow to interpret the result
Expected:
- nginx -t returns successful
- reload does not disconnect SSH
- http://odoo.example.com reaches Odoo or redirects later after HTTPS setup
If nginx -t fails, do not reload until the syntax error is fixed.8. Issue HTTPS certificate with Certbot
Change requiredHTTPS should be enabled before real users log in to Odoo. Certbot can configure Nginx automatically.
Show command and interpretation
Command or manual check
sudo certbot --nginx -d odoo.example.comHow to interpret the result
Certbot should:
- verify DNS points to the VPS
- issue a certificate
- update Nginx config for HTTPS
- optionally redirect HTTP to HTTPS
If Certbot fails:
- confirm DNS resolves to the VPS
- confirm ports 80 and 443 are open
- confirm Nginx is running
- confirm the domain is not proxied incorrectly by a DNS/CDN provider
- if validation still fails, the catch-all proxy may intercept the ACME challenge; make sure /.well-known/acme-challenge/ is served locally and not proxied to Odoo9. Test HTTPS and redirect behavior
Safe checkAfter Certbot, confirm the subdomain works over HTTPS and HTTP no longer serves insecure Odoo sessions.
Show command and interpretation
Command or manual check
curl -I http://odoo.example.com/
curl -I https://odoo.example.com/How to interpret the result
Expected:
- HTTP should return 301 or 308 and redirect to HTTPS
- HTTPS should return 200, 303, or another normal Odoo response
- the certificate should be valid in the browser
Good examples:
- http://odoo.example.com/ returns 301 or 308 with Location: https://odoo.example.com/
- https://odoo.example.com/ returns 200 OK or a normal Odoo redirect
- the browser shows a valid HTTPS certificate
What would be bad:
- HTTP serves Odoo directly without redirecting to HTTPS
- HTTPS fails with certificate errors
- HTTPS returns 502 or 504, which usually means Nginx cannot reach Odoo locally
- HTTPS returns 403 on the main Odoo homepage, unless you intentionally restricted the whole site
- the browser shows mixed content or wrong generated URLs
Important:
- 403 is bad for the main Odoo homepage
- 403 is good for sensitive /web/database routes if you intentionally block them publicly
If HTTPS works but Odoo shows wrong URLs or mixed content, check:
- proxy_mode = True in odoo.conf
- X-Forwarded-Proto header in Nginx
- X-Forwarded-Host / Host headers in Nginx
- Odoo was restarted after config changes10. Test database manager protection publicly
Safe checkThe Odoo database manager is sensitive. Public access should be blocked or intentionally restricted.
Show command and interpretation
Command or manual check
curl -I https://odoo.example.com/web/database/manager
curl -I https://odoo.example.com/web/database/selector
curl -I https://odoo.example.com/web/database/createHow to interpret the result
Target public result:
- 403 Forbidden
- or another intentionally blocked/restricted result
Avoid public 200 OK on database manager routes unless you fully understand and accept the risk.
Even if these routes are blocked in Nginx:
- keep admin_passwd strong
- keep list_db = False for production-style setups
- keep dbfilter configured11. Verify Odoo configuration for proxy mode
CautionOdoo should know it is behind a trusted reverse proxy so HTTPS, cookies, and generated URLs behave correctly.
Show command and interpretation
Command or manual check
# Go to the folder where your Odoo deployment is managed.
# Example for a Docker Compose install:
cd /opt/odoo-secure
# Review the important proxy/security settings without printing the master password.
grep -E "^(proxy_mode|list_db|dbfilter|admin_passwd)" config/odoo.conf \
| sed -E 's/(admin_passwd) = .+/\1 = ***hidden***/'
# If you changed odoo.conf, restart Odoo.
sudo docker compose restart odoo
# Re-test HTTPS after restart.
curl -I https://odoo.example.com/
# Re-test public database manager protection.
curl -I https://odoo.example.com/web/database/managerHow to interpret the result
Recommended:
- proxy_mode = True
- list_db = False
- dbfilter = ^your_database_name$
- admin_passwd is strong and not printed publicly
The command should be executed from the folder where your Odoo deployment is managed.
Examples:
- /opt/odoo-secure for a dedicated Docker Compose deployment
- another project folder if docker-compose.yml and config/odoo.conf are stored elsewhere
- /etc/odoo/odoo.conf for some package-based Linux installs
If you changed odoo.conf:
- restart the Odoo container or service
- re-test the HTTPS homepage
- re-test that /web/database routes are blocked or restricted publicly
For Docker Compose:
- sudo docker compose restart odoo
For a package-based Linux install, the service may be:
- sudo systemctl restart odoo
- sudo systemctl restart odoo-server
Good HTTPS result:
- https://odoo.example.com/ returns 200 OK, 303, or another normal Odoo response
Good database manager protection result:
- https://odoo.example.com/web/database/manager returns 403, 404, or another intentionally restricted response12. Final public exposure check
Safe checkBefore considering Odoo online, verify only intended public ports are reachable and Odoo/PostgreSQL remain private.
Show command and interpretation
Command or manual check
sudo ss -tulpn | grep -E ":(22|80|443|5432|8069|8072)"
sudo ufw status verbose
docker ps --format "table {{.Names}}\t{{.Ports}}"How to interpret the result
Good final state:
- 22 is public only if SSH is needed
- 80 and 443 are public for Nginx
- 8069 is bound to 127.0.0.1 only
- 8072 is bound to 127.0.0.1 only
- PostgreSQL is not public
- Docker does not publish PostgreSQL publicly
- firewall does not allow 8069, 8072, or 5432 publiclyFinal verification
- ✓https://odoo.example.com opens Odoo successfully.
- ✓HTTP redirects to HTTPS.
- ✓The certificate is valid in the browser.
- ✓Odoo 8069 is not publicly exposed.
- ✓Odoo 8072 is not publicly exposed.
- ✓PostgreSQL 5432 is not publicly exposed.
- ✓/web/database routes are blocked or intentionally restricted.
- ✓proxy_mode is enabled in the active Odoo configuration.
- ✓Nginx test passes with nginx -t.
- ✓Certbot renewal is installed and can renew certificates.
Need help exposing Odoo safely?
If you want to put Odoo behind Nginx, HTTPS, and a clean subdomain without exposing port 8069, port 8072, PostgreSQL, or database manager routes publicly, I can help review or set up the deployment.
I can also help troubleshoot common deployment issues such as wrong proxy headers, broken websocket routing, Certbot failures, HTTP not redirecting to HTTPS, or Odoo generating incorrect URLs.
Contact me for Odoo HTTPS setup →Related guide
Before hosting Odoo publicly, first make sure the underlying VPS is hardened and audited.
How to secure Odoo on a VPS →