Security & Privacy

Odoo Security: A Practical Guide to Hardening Your Instance

J
James Eriksson
··13 min read
Harden your self-hosted Odoo with 2FA, access controls, reverse proxy, PostgreSQL hardening, and backups. Master the five security layers protecting your data.
TL;DR
  • Odoo's application layer protects against SQL injection and XSS, but infrastructure security is your responsibility
  • Enable 2FA for admins, configure record rules to restrict data visibility per user, and test access controls before launch
  • Run Nginx reverse proxy with TLS 1.3, harden PostgreSQL with strong passwords and network restrictions, and automate off-site backups
  • Vet third-party modules for maintenance history and code quality before installing in production
  • Managed Odoo hosting automates infrastructure hardening and monitoring if your team lacks DevOps capacity

Self-hosted Odoo holds your company's financial records, customer data, and operational secrets. A misconfigured instance leaks all of it. This guide walks you through the five layers of security every Odoo deployment needs: authentication, access control, infrastructure, code integrity, and operational monitoring.

Five Security Layers: The Big Picture

Odoo security is not a single gate. It is five independent zones that must work together. Authentication controls who logs in and how they prove it. Access control determines which users can see which data records and perform which actions. Infrastructure is the network, server, and database that hold your instance. Code integrity covers third-party modules and custom code running inside Odoo. Operations means monitoring, logging, backups, and recovery when things go wrong.

Odoo handles the first two layers reasonably well out of the box. The application protects against SQL injection, XSS, and CSRF by default because it uses its own ORM instead of raw SQL. But you must configure those protections correctly. Layers three, four, and five are your responsibility. A reverse proxy in front of Odoo, a hardened PostgreSQL database, regular backups, and active monitoring are not optional.

Many self-hosted deployments fail at infrastructure. You expose Odoo directly to the internet on port 8069. Your PostgreSQL password is "postgres". Nobody watches the logs. This guide fixes those mistakes.

Authentication and User Management: 2FA, Passwords, Sessions

Odoo's authentication layer controls who logs in and what happens if someone tries to break in. Start by enforcing two-factor authentication (2FA) for all admin users and anyone accessing sensitive modules: accounting, human resources, payroll. Odoo's built-in 2FA uses time-based one-time passwords (TOTP). Enable it in Settings > Users & Companies > Users. Each user must set up TOTP in their account before they can proceed. There is no bypass; they cannot log in without it.

For master password (Settings > General Settings), use a strong, randomly generated string of at least 32 characters. Store it in a password manager, not in Slack or email. The master password can reset user accounts and export data; it must be protected like your AWS root key. If you need multiple people to access it, use a shared credential vault like 1Password or Vault, not a spreadsheet.

Admin accounts are high-value targets. Consider disabling password-based login for admins entirely and using OAuth (e.g., Google Workspace SSO) or LDAP integration instead. This shifts authentication burden to your identity provider, which (if you use Okta or Azure AD) has better brute-force detection, passwordless options, and audit trails.

Session timeout matters. Go to Settings > General Settings and set "Session Timeout" to 30 or 60 minutes depending on your security posture. After timeout, the user must log in again. This limits damage if someone leaves their browser unattended. Enable rate limiting on the login page to slow down brute-force attacks. This is not a native Odoo setting; it lives in your reverse proxy (Nginx) configuration, covered below.

Never share logins. Every person who touches Odoo must have their own account, even if they perform identical tasks. Shared accounts destroy audit trails and make incident response impossible. You will not know who deleted the invoice or changed the price list. Individual accounts cost nothing in Odoo Community Edition and are essential in Enterprise for SOX compliance and internal audit.

Access Control: ACLs, Record Rules, and Least Privilege

Odoo separates two layers of access: model-level access (what objects a user can read, write, create, delete) and record-level access (which specific rows of data they can see). Model-level access is controlled by Access Control Lists (ir.model.access.csv in your module, or the GUI under Settings > Users > Groups). Here is the pattern: a user belongs to a group (e.g., "Sales Team"), and that group has CRUD permissions on specific models. A sales rep should have read/write on lead, opportunity, and customer. They should not have access to journal entries, employee records, or purchase orders. Assign the minimum permission required for their job.

Record-level access is controlled by record rules (ir.rule). An example: a sales rep should see only leads and opportunities assigned to them or their team, not leads from other territories. Record rules filter the data a user can access even if they have model-level read permission. Without record rules, a rep with read access to the lead model can see all 10,000 leads in the system. With a record rule scoped to their own territory, they see 50.

The common mistake is defining ACLs, testing with admin, going live, then discovering record rules were never created. Non-admin users can now access far more data than intended. Test your ACLs with a real non-admin user account before launch. Log in, try to view a customer record from a different sales team. If you can see it, the record rule is missing.

Access rights are stored in ir.model.access.csv in your module source code or managed directly in the UI. If you use the UI, export the settings to version control so you can restore them in staging and production. The file is simple CSV: model name, group, read, write, create, delete (each is 1 or 0). Use groups like "Accounting Team", "Sales Team", "Operations Team" instead of assigning access per user. Groups scale; individual permissions break at 20 people.

Infrastructure Security: Reverse Proxy, HTTPS, PostgreSQL, Firewall

Your instance runs on a server connected to the internet. That server must be hardened. Never expose Odoo directly to the internet on port 8069. Instead, run Nginx as a reverse proxy in front of Odoo. The reverse proxy terminates TLS, forwards HTTPS requests to Odoo on localhost, and handles security headers, rate limiting, and static file serving.

Here is the minimal Nginx configuration: use TLS 1.3 minimum (TLS 1.2 is acceptable but older). Obtain a certificate from Let's Encrypt (free, auto-renews). Use strong cipher suites; Nginx defaults are fine. Add strict-transport-security, x-content-type-options: nosniff, x-frame-options: SAMEORIGIN headers. These prevent clickjacking, MIME sniffing, and force HTTPS-only access. Limit login attempts to 5 per minute per IP to slow down attackers. Serve /web/static/* directly from Nginx disk cache instead of passing through Odoo. This is faster and reduces load.

Obtain and auto-renew TLS certificates using certbot (Let's Encrypt). Renewal is free and automatic; set it and forget it. If you need help comparing hosting models and infrastructure options, explore Opsily's guide to Odoo hosting to understand your choices.

PostgreSQL is your data store. Secure it by allowing PostgreSQL connections only from localhost (127.0.0.1) and from your Odoo server IP. Disable remote access entirely unless you have a specific reason (read-only reporting, backup replication). Set listen_addresses = 'localhost' in postgresql.conf if Odoo and PostgreSQL are on the same machine. Use md5 or scram-sha-256 password hashing (not plaintext). Set a strong password for the odoo database user (20+ random characters). Remove or disable the default postgres admin account's password login; use sudo only.

Use the non-default port (not 5432) if you expose PostgreSQL to other servers. This is security by obscurity and weak, but every layer helps. Block port 5432 from the public internet via firewall. Allow it only from your Odoo server's private IP.

Your server itself must run a firewall (iptables/ufw on Linux). Expose only port 443 (HTTPS) to the public internet and port 22 (SSH) to a restricted IP range (your office, your VPN, your deployment machine), never to 0.0.0.0/0. Disable password-based SSH login. Use private keys only. Store private keys in your secrets manager or hardware security key, not on developer laptops.

Regular patching is non-negotiable. Apply operating system security updates weekly. Kernel, SSL libraries, Python, and system dependencies can have critical vulnerabilities. Automate this: use unattended-upgrades on Debian/Ubuntu.

Module Vetting and Custom Code Security

Third-party Odoo modules and custom code can execute arbitrary Python and database commands. A malicious or poorly written module bypasses all your access controls. Audit every third-party module before installing it in production: check if it is actively maintained (when was the last update?), look at popularity and reviews in the OdooApps store and GitHub, clone the repo and scan code for raw SQL (bad) versus ORM usage (good), check what database access it requests, and prefer modules with minimal permissions.

For custom code (Python modules you or your team write): never construct SQL queries by string concatenation. Always use the Odoo ORM (e.g., self.env['sale.order'].search([('id', '=', order_id)])). The ORM escapes inputs and prevents SQL injection. Validate any data coming from a user (form field, API call, import). Check type, length, and format. Reject invalid data with a clear error message.

If you display user-provided data in reports or emails, escape it. Odoo's template language (QWeb) escapes by default, but be explicit if you use Python string formatting. Never use eval(), exec(), or pickle to deserialize untrusted data. These are instant backdoors. Use dev -> staging -> production environments. Test custom code in staging before deploying to production. Stage mirrors production data (sanitized) and configuration.

Monitoring, Backups, and Disaster Recovery

You cannot prevent every attack or mistake, but you can detect and recover quickly. Set Odoo's debug level to info (not debug; too noisy). Review Odoo logs daily for errors, permission denials, and failed authentication attempts. Look for repeated failed logins to the same account (brute force) or unusual IP addresses accessing sensitive data. Monitor /var/log/auth.log for SSH login attempts. Monitor /var/log/syslog or journalctl for kernel errors and service crashes.

Configure log aggregation (e.g., ELK stack, Splunk, or email-based alerts) to notify you of critical errors, multiple failed logins, or database connection failures. Do not wait for a user to report the outage.

PostgreSQL requires regular, tested backups. Your backup strategy has two metrics: RPO (recovery point objective) and RTO (recovery time objective). RPO is how much data you can afford to lose. If you back up daily, your RPO is one day. If you back up every 6 hours, your RPO is 6 hours. Odoo Online achieves RPO of 24 hours and RTO of 6 hours (for server failure) or 24 hours (for datacenter failure) because it automates backups across multiple datacenters.

RTO is how long you can tolerate downtime. If you can tolerate one hour, you need to restore and start a new instance in under 60 minutes. If you need 15 minutes, that drives your backup tool choices.

For self-hosted Odoo: run a full database backup (pg_dump) once daily. Compress it with gzip. Store it on disk. Enable PostgreSQL WAL archiving to S3 or another off-site location. WAL logs record every change to the database at a granular level. Combined with a daily full backup, WAL archiving lets you restore to any point in time (PITR) between backups. Back up /var/lib/odoo/filestore/ (your files: invoices, attachments, images) to the same location as your database. Keep at least one copy of backups off-site. If your data center burns down, a backup sitting on the same server does not help. Use S3, Backblaze B2, or a cloud provider.

Once a month, restore a backup to a staging environment and verify it works. A backup you have never restored from is not a backup; it is hope.

What Managed Hosting Handles for You

Self-hosting Odoo gives you full control but requires mature operational discipline. Every layer described above becomes your job. Managed Odoo hosting automates most of this burden. You define your Odoo configuration and access controls; the platform handles infrastructure hardening (reverse proxy, firewall, SSH key management, OS patching), HTTPS with auto-renewing certificates, PostgreSQL backups (typically multiple snapshots per day, retention of 30 days or more), disaster recovery and failover (automated restores in minutes if hardware fails), monitoring and alerting (24/7 uptime monitoring, logs aggregated and searchable), and security updates (OS, Python, PostgreSQL patched on your schedule, tested before deployment).

You retain full control of Odoo's application security: access rights, 2FA configuration, module selection, and custom code. You do not lose security; you trade operational overhead for focus on Odoo configuration.

If your team is smaller than 3 people or your infrastructure maturity is low, managed hosting is pragmatic. You get enterprise-grade infrastructure for a predictable monthly cost, and you spend your time building the business, not hardening servers. You can also review detailed pricing for self-hosted Odoo to compare your actual infrastructure costs against managed alternatives. The break-even is roughly when you spend more than one engineer's loaded cost ($120-180K annually) on operational overhead. For most 10-100 person companies, managed hosting costs less and frees engineering bandwidth for product work.

Frequently Asked Questions

Is Odoo secure by default?

Odoo's application layer is secure: it protects against SQL injection, XSS, and CSRF by default because it uses its ORM instead of raw SQL. However, default configuration is not production-ready. You must enable 2FA, configure access controls (ACLs and record rules), and harden the infrastructure (reverse proxy, HTTPS, PostgreSQL).

What is the master password and how often should I change it?

The master password is a backup admin credential stored in /var/lib/odoo/master_password. It can reset user accounts and trigger data exports. Treat it like your AWS root key: strong, random, and protected. Change it when a team member leaves or annually, whichever is sooner.

How do I prevent the database manager from being exposed?

The database manager (usually accessible at /web/database/manager) allows anyone on the network to create, drop, or restore databases if not protected. In production, disable it entirely by removing the database_manager module or blocking it at the reverse proxy (Nginx). If you must keep it, protect it with a strong password and restrict access to a private IP range.

Can I use Odoo's built-in 2FA or should I integrate SSO?

Odoo's built-in 2FA (TOTP) is good enough for most teams. If your company already uses Google Workspace, Azure AD, or Okta, SSO integration is better: it centralizes user management, improves UX (users do not create a separate Odoo password), and often includes stronger 2FA options (hardware keys, passkeys). Both are valid; SSO scales better in larger organizations.

How do I audit which users accessed sensitive data?

Odoo logs reads and writes to the database via the audit trail (Settings > Audit Trail, if enabled). For higher-level audit, monitor PostgreSQL logs and Odoo application logs. External log aggregation (ELK, Splunk) lets you build dashboards: "Show me all reads of customer.customer from user X in the last 7 days."

What should I do if I suspect a security breach?

Immediately disable the affected user account and reset their password. Export Odoo audit logs and system logs for forensics. Restore a clean backup to a staging environment and inspect for unauthorized changes. Review firewall logs for unusual access patterns. If customer data was exposed, consult your legal team and notify affected users within your jurisdiction's required timeframe (72 hours in GDPR, varies elsewhere). Deploy a post-incident fix: patch the vulnerability, tighten access controls, enable monitoring.

How does Odoo Online compare to self-hosted in terms of security?

Odoo Online is hosted by Odoo S.A.S. (Odoo's company). It holds ISO 27001 certification as of April 2026 and CSA STAR Level 1 self-assessment. Odoo Online handles all infrastructure, backups, updates, and compliance. You control Odoo configuration and access rights. Self-hosted requires you to manage everything in layers 3, 4, and 5. Online is typically more secure because Odoo S.A.S. has dedicated security, DevOps, and compliance teams. You are one customer among thousands; they invest in hardening and monitoring at scale.

The Bottom Line

Odoo security is layered. Your application is reasonably safe if you use the ORM and follow OWASP guidelines. Your infrastructure is not safe unless you run a reverse proxy, use HTTPS, harden PostgreSQL, and automate backups. Your operations are not mature unless you monitor logs, test restores, and update regularly.

If you self-host, plan for 4-6 weeks of initial hardening and 2-4 hours per week of ongoing operations. If that is too much, managed hosting handles infrastructure so you focus on your business. Either way, the security debt is real. Pay it early or pay it later when something breaks. Start with the five layers and test everything in a non-production environment first.

Reduce Infrastructure Overhead
Opsily handles server hardening, monitoring, backups, and updates so your team focuses on Odoo configuration instead of DevOps.
Get Started Free

Ready to self-host your own apps?

One server. Multiple apps. No per-app fees.

Get started →