by Aria Shaw
Deploying Odoo 18 on Vultr Cloud using Docker Compose eliminates the complexity of manual installations while delivering production-ready infrastructure in under 30 minutes. Unlike traditional bare-metal setups that require intricate dependency management and multi-step configurations, Docker Compose encapsulates your entire Odoo environment—application server, PostgreSQL database, and networking—into a single declarative file.
Vultr’s High Performance AMD instances with NVMe storage provide a 20% performance boost over standard SSD configurations, making them ideal for PostgreSQL-intensive workloads like Odoo. For a small business with 10-15 users, you’re looking at just $24/month for compute plus $5/month for backup storage—significantly less than managed hosting alternatives while maintaining full control over your data sovereignty.
This guide walks you through the complete deployment process: provisioning a Vultr instance, configuring Docker Compose with optimized settings for Odoo 18 and PostgreSQL 16, setting up Nginx as a reverse proxy with Let’s Encrypt SSL, and tuning performance parameters based on your server specifications. You’ll also get production-ready configurations for security hardening, automated backups, and monitoring that you can deploy immediately.
This guide covers deployment, but what about day-2 operations? Download our battle-tested backup automation, PostgreSQL tuning, and health monitoring scripts—designed specifically for Docker Compose deployments like this one. Used by 500+ production Odoo instances.
Download Free Scripts →Step 1: Create Vultr Compute Instance (5 minutes)
Log into your Vultr account and click “Deploy New Server”. Select “Cloud Compute” and choose your location (pick the region closest to your users for lowest latency). Under operating system, select Ubuntu 24.04 LTS x64. For server size, choose “High Performance AMD - 2 vCPU” ($24/month) if you have 5-15 users, or “High Performance AMD - 4 vCPU” ($48/month) for 15-50 users. The NVMe storage on these plans significantly improves PostgreSQL query performance compared to regular SSD instances.
Add your SSH key under “SSH Keys” (if you don’t have one, generate it with ssh-keygen -t ed25519
). Disable “Auto Backups” for now—we’ll set up a better backup strategy later. Set a server hostname like “odoo-production” and click “Deploy Now”. Wait 3-5 minutes for the instance to provision, then note the IP address.
Step 2: Configure Vultr Firewall Rules (3 minutes)
Before connecting to your server, set up firewall rules. In the Vultr dashboard, navigate to “Firewall” and create a new firewall group named “odoo-production-fw”. Add these inbound rules:
Critical: Do NOT open port 8069. Odoo will run on this port internally, but all external traffic must go through Nginx on port 443. Attach this firewall group to your instance.
Step 3: Install Docker and Docker Compose (5 minutes)
SSH into your server: ssh root@YOUR_SERVER_IP
. Update the package index and install prerequisites:
apt update && apt upgrade -y
apt install -y ca-certificates curl gnupg lsb-release
Add Docker’s official GPG key and repository:
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
Install Docker Engine and Docker Compose v2:
apt update
apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
Verify installation: docker compose version
. You should see version 2.10 or higher. Note: Use docker compose
(with space), not the deprecated docker-compose
command.
Step 4: Create Project Directory Structure (2 minutes)
Create a dedicated directory for your Odoo deployment:
mkdir -p /opt/odoo/{addons,config,postgres-data,odoo-data}
cd /opt/odoo
This structure separates custom addons, configuration files, and persistent data volumes. Set proper permissions:
chmod -R 755 /opt/odoo
Step 5: Configure docker-compose.yml (5 minutes)
Create your Docker Compose configuration file. See the docker_compose_generator
section below for the complete configuration. Save it as /opt/odoo/docker-compose.yml
. This configuration uses PostgreSQL 16 (recommended for Odoo 18), sets up named volumes for data persistence, and configures a dedicated network for container communication.
Key settings to note:
shared_buffers=1024MB
(25% of 4GB RAM)Step 6: Set Environment Variables (3 minutes)
Create a .env
file in /opt/odoo/
to store sensitive credentials:
cat > /opt/odoo/.env <<EOF
POSTGRES_DB=odoo
POSTGRES_USER=odoo
POSTGRES_PASSWORD=$(openssl rand -base64 32)
ODOO_ADMIN_PASSWD=$(openssl rand -base64 24)
EOF
Secure the file: chmod 600 /opt/odoo/.env
. This generates strong random passwords for PostgreSQL and Odoo’s master password.
Step 7: Launch Containers (3 minutes)
Start the Odoo stack:
cd /opt/odoo
docker compose up -d
Verify both containers are running: docker compose ps
. You should see odoo-web
and odoo-db
with status “Up”. Check logs: docker compose logs -f odoo-web
. Wait for the message “odoo.service.server: HTTP service (werkzeug) running on 0.0.0.0:8069”. Press Ctrl+C to exit logs.
Test internal access: curl http://localhost:8069/web/database/selector
. If you see HTML output, Odoo is running correctly.
Step 8: Install and Configure Nginx (5 minutes)
Install Nginx:
apt install -y nginx
systemctl enable nginx
Create Nginx configuration for Odoo. See the nginx_config_snippet
section below for the complete configuration. Save it as /etc/nginx/sites-available/odoo
and create a symlink:
ln -s /etc/nginx/sites-available/odoo /etc/nginx/sites-enabled/
rm /etc/nginx/sites-enabled/default
Test configuration: nginx -t
. If successful, reload: systemctl reload nginx
.
Step 9: Configure SSL with Let’s Encrypt (5 minutes)
First, point your domain’s DNS A record to your Vultr server IP. Verify propagation: dig yourdomain.com
should show your server IP.
Install Certbot:
apt install -y certbot python3-certbot-nginx
Obtain SSL certificate:
certbot --nginx -d yourdomain.com -d www.yourdomain.com
Follow the prompts. Choose option 2 to redirect all HTTP traffic to HTTPS. Certbot automatically modifies your Nginx config and sets up auto-renewal via systemd timer. Verify renewal works: certbot renew --dry-run
.
Step 10: Optimize PostgreSQL Settings (5 minutes)
PostgreSQL’s default settings aren’t optimized for Odoo. See the postgresql_tuning_script
section for a complete tuning script. Run it to apply settings based on your instance’s RAM. Restart PostgreSQL container: docker compose restart odoo-db
.
Step 11: Configure System Monitoring (3 minutes)
Increase file watch limit for Odoo’s auto-reload feature:
echo "fs.inotify.max_user_watches=524288" >> /etc/sysctl.conf
sysctl -p
Set up log rotation for Odoo:
cat > /etc/logrotate.d/odoo <<EOF
/var/lib/docker/containers/*/*.log {
rotate 7
daily
compress
missingok
delaycompress
copytruncate
}
EOF
Step 12: Verify Deployment and Create Admin User (5 minutes)
Open your browser and navigate to https://yourdomain.com
. You should see the Odoo database creation screen. Fill in:
production
(or your company name)Click “Create Database”. Wait 2-3 minutes for initial setup. Once complete, you’ll be logged into Odoo 18. Install your first app to verify everything works. Your production Odoo instance is now live.
```yaml
services:
odoo-db:
image: postgres:16
container_name: odoo-db
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_INITDB_ARGS: "-E UTF8"
volumes:
- odoo-db-data:/var/lib/postgresql/data
networks:
- odoo-network
restart: unless-stopped
command: >
postgres
-c shared_buffers=1024MB
-c effective_cache_size=3GB
-c maintenance_work_mem=256MB
-c checkpoint_completion_target=0.9
-c wal_buffers=16MB
-c default_statistics_target=100
-c random_page_cost=1.1
-c effective_io_concurrency=200
-c work_mem=10485kB
-c min_wal_size=1GB
-c max_wal_size=4GB
-c max_worker_processes=2
-c max_parallel_workers_per_gather=1
-c max_parallel_workers=2
-c max_parallel_maintenance_workers=1
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
odoo-web:
image: odoo:18.0
container_name: odoo-web
depends_on:
odoo-db:
condition: service_healthy
environment:
HOST: odoo-db
USER: ${POSTGRES_USER}
PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- odoo-web-data:/var/lib/odoo
- ./addons:/mnt/extra-addons
- ./config:/etc/odoo
networks:
- odoo-network
ports:
- "127.0.0.1:8069:8069"
restart: unless-stopped
command: >
--
--workers=5
--max-cron-threads=1
--limit-memory-hard=2684354560
--limit-memory-soft=2147483648
--limit-request=8192
--limit-time-cpu=600
--limit-time-real=1200
--db_host=odoo-db
--db_port=5432
--db_user=${POSTGRES_USER}
--db_password=${POSTGRES_PASSWORD}
--dbfilter=^%d$
--proxy-mode
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8069/web/health || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
volumes:
odoo-db-data:
driver: local
odoo-web-data:
driver: local
networks:
odoo-network:
driver: bridge
```
**Configuration Explanation:**
- **PostgreSQL tuning**: `shared_buffers=1024MB` allocates 25% of 4GB RAM for database caching. `effective_cache_size=3GB` tells PostgreSQL how much RAM is available for caching. `random_page_cost=1.1` is optimized for NVMe SSDs (default 4.0 is for spinning disks).
- **Odoo workers**: `--workers=5` follows the formula `1 + (vCPU * 2)` for 2-core instances. This enables multiprocess mode for better concurrency. `--max-cron-threads=1` ensures scheduled tasks don't compete with web requests.
- **Memory limits**: `--limit-memory-hard=2684354560` (2.5GB) prevents memory leaks from crashing the server. `--limit-memory-soft=2147483648` (2GB) triggers worker recycling before hard limit.
- **Security**: Port 8069 is bound to `127.0.0.1` only—not accessible from internet. `--proxy-mode` enables Odoo to trust X-Forwarded-For headers from Nginx. `--dbfilter=^%d$` prevents database enumeration attacks.
- **Health checks**: PostgreSQL health check ensures database is ready before Odoo starts. Odoo health check monitors web service availability for container orchestration.
```nginx
# Upstream configuration for Odoo
upstream odoo {
server 127.0.0.1:8069;
}
upstream odoo-chat {
server 127.0.0.1:8072; # Odoo longpolling for chat/notifications
}
# Rate limiting zone to prevent abuse
limit_req_zone $binary_remote_addr zone=odoo_limit:10m rate=10r/s;
# HTTP to HTTPS redirect
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
# Allow Let's Encrypt validation
location /.well-known/acme-challenge/ {
root /var/www/html;
}
# Redirect all other HTTP traffic to HTTPS
location / {
return 301 https://$server_name$request_uri;
}
}
# HTTPS server configuration
server {
listen 443 ssl http2;
server_name yourdomain.com www.yourdomain.com;
# SSL certificates (managed by Certbot)
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
# SSL optimization
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 10m;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
# Proxy settings
proxy_read_timeout 720s;
proxy_connect_timeout 720s;
proxy_send_timeout 720s;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_redirect off;
# Gzip compression
gzip on;
gzip_types text/css text/scss text/plain text/xml application/xml application/json application/javascript;
gzip_min_length 1000;
gzip_proxied any;
gzip_comp_level 6;
# File upload size limit (adjust based on your needs)
client_max_body_size 100M;
# Block access to database manager in production
location ~* /web/database {
deny all;
return 403;
}
# Odoo longpolling for real-time features
location /longpolling {
proxy_pass http://odoo-chat;
proxy_buffering off;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
}
# Static files with aggressive caching
location ~* /web/static/ {
proxy_cache_valid 200 304 60m;
proxy_buffering on;
expires 864000;
add_header Cache-Control "public, immutable";
proxy_pass http://odoo;
}
# User-uploaded content with moderate caching
location ~* /web/content/ {
expires 7d;
add_header Cache-Control "public";
proxy_pass http://odoo;
}
# Main Odoo application with rate limiting
location / {
limit_req zone=odoo_limit burst=20 nodelay;
proxy_pass http://odoo;
}
# Logging
access_log /var/log/nginx/odoo-access.log;
error_log /var/log/nginx/odoo-error.log;
}
```
**Configuration Highlights:**
- **Database manager blocking**: The `location ~* /web/database` block prevents public access to Odoo's database management interface, a critical security measure for production.
- **Static file caching**: `/web/static/` files (CSS, JS, images) are cached for 10 days with `immutable` flag. This reduces Odoo worker load by 30-40% for returning visitors.
- **Rate limiting**: `limit_req_zone` allows 10 requests per second per IP with a burst buffer of 20. Prevents brute-force attacks on login forms.
- **SSL hardening**: Only TLS 1.2+ with strong ciphers. HSTS header ensures browsers always use HTTPS for 1 year after first visit.
- **Longpolling**: Odoo 18's real-time features (chat, notifications) use WebSocket-like connections on port 8072. This requires a separate upstream to avoid timeout issues.
Plan | vCPUs | RAM (GB) | Storage (GB) | Price |
---|---|---|---|---|
High Performance AMD - 1 vCPU | 1 | 2 | 55 | $10/mo |
Regular Performance - 2 vCPU | 2 | 4 | 80 | $20/mo |
High Performance AMD - 2 vCPU | 2 | 4 | 80 | $24/mo |
Regular Performance - 4 vCPU | 4 | 8 | 160 | $40/mo |
High Performance AMD - 4 vCPU | 4 | 8 | 160 | $48/mo |
High Performance AMD - 6 vCPU | 6 | 16 | 320 | $96/mo |
Optimized Cloud Compute - General Purpose | 4 | 16 | 100 | $120/mo |
1. Deploying on Under-Spec’d Instances
The single most common mistake is choosing Vultr’s $10/month 1 vCPU plan to save costs. While this meets Odoo’s minimum requirements on paper (2GB RAM), you’ll hit performance walls immediately with 5+ concurrent users. PostgreSQL and Odoo compete for the same CPU core, causing request queueing during peak usage. Instead, start with the High Performance AMD 2 vCPU plan at $24/month. The NVMe storage alone provides 20% faster database queries than regular SSDs, and the second core prevents CPU contention between database and application.
2. Not Blocking Port 8069 at the Firewall
Many administrators configure Nginx as a reverse proxy but forget to block direct access to Odoo’s port 8069. This creates a massive security hole—users can bypass Nginx’s rate limiting, SSL, and access controls by hitting port 8069 directly. In Vultr’s firewall settings, ensure you ONLY allow ports 22 (SSH from your IP), 80 (HTTP for Let’s Encrypt), and 443 (HTTPS). Port 8069 should never appear in your inbound rules. In your docker-compose.yml, bind Odoo to 127.0.0.1:8069
(not 0.0.0.0:8069
) to prevent external access.
3. Skipping Worker Configuration (Running Single-Process Mode)
Odoo’s default configuration runs in single-process mode, meaning it can only handle one request at a time. With 5 users clicking around simultaneously, the fifth user waits for the first four to complete. Set --workers=5
(formula: 1 + vCPU * 2) in your docker-compose.yml command arguments. This enables multiprocess mode where Odoo spawns 5 worker processes, each handling concurrent requests. Don’t forget to also set --max-cron-threads=1
to prevent scheduled tasks from consuming worker slots.
4. Using Default PostgreSQL Settings Without Tuning
PostgreSQL’s out-of-the-box configuration is designed to run on a 128MB RAM machine from 1998. Shared_buffers defaults to 128MB regardless of your instance size—leaving gigabytes of RAM unused. On a 4GB Vultr instance, PostgreSQL should use 1GB for shared_buffers (25% of RAM) and have effective_cache_size set to 3GB (75% of RAM). Use the tuning script in this guide to calculate optimal settings. The performance difference between default and tuned PostgreSQL can be 3-5x for complex Odoo queries.
5. Disabling or Ignoring AUTOVACUUM
This is a slow-burning disaster that won’t show up until months after deployment. PostgreSQL’s AUTOVACUUM process cleans up dead rows and updates query statistics. Disabling it (which some performance guides incorrectly suggest) leads to database bloat—we’ve seen production databases slow down by 2000% after 6 months due to this. Keep autovacuum enabled and tune it aggressively: set autovacuum_naptime=10s
and autovacuum_max_workers=3
. Monitor with docker exec odoo-db psql -U odoo -d postgres -c 'SELECT * FROM pg_stat_all_tables WHERE n_dead_tup > 1000;'
.
6. Forgetting DNS Propagation Before SSL Setup
Enthusiastic admins often run certbot --nginx
immediately after creating their Vultr instance, before updating DNS records. Let’s Encrypt validation fails because the domain doesn’t resolve to the new server IP yet. DNS propagation can take 1-48 hours depending on your registrar. Before running Certbot, verify DNS with dig yourdomain.com @8.8.8.8
or nslookup yourdomain.com
. You should see your Vultr IP address. If not, wait and check again in an hour. Failed validation attempts count against Let’s Encrypt’s rate limits (5 failures per hour).
7. Not Configuring Nginx to Serve Static Files
By default, Nginx proxies ALL requests to Odoo, including static assets like CSS, JavaScript, and images. This wastes 30-40% of your worker capacity serving files that never change. Configure Nginx location blocks for /web/static/
with long cache headers (expires 864000;
= 10 days) and proxy_cache_valid 200 304 60m
. This tells Nginx to cache these files in memory and serve them directly without bothering Odoo workers. For a site with 20 active users, this single change can reduce Odoo’s CPU usage by 25%.
8. Storing Sessions in PostgreSQL (Default Behavior)
Odoo 18 defaults to storing user sessions in the PostgreSQL database. Every page load triggers a session read/write, creating unnecessary database load. For production deployments with 10+ concurrent users, this becomes a significant bottleneck. Configure Odoo to store sessions on the filesystem by adding --session-storage=filesystem
to your docker-compose.yml command arguments. For even better performance on multi-instance setups, use Redis: --session-storage=redis --redis-host=redis --redis-port=6379
.
9. Using Outdated ‘docker-compose’ Command
If you’re following older guides, you might use docker-compose up -d
(with hyphen). Docker deprecated this standalone tool in favor of the integrated docker compose
(with space) plugin. The v2 plugin is faster, better integrated with Docker Engine, and required for modern features like health checks and profiles. Verify you have v2 installed: docker compose version
should show 2.10 or higher. If you see command not found, install the docker-compose-plugin package as shown in this guide’s installation steps.
10. No Backup Strategy for Docker Volumes
Docker volumes (odoo-db-data
and odoo-web-data
in the docker-compose.yml) store all your critical data. By default, these live in /var/lib/docker/volumes/
with no backup. A simple docker compose down -v
(with the -v flag) will DELETE YOUR ENTIRE DATABASE. Set up automated daily backups to Vultr Block Storage or S3. Use the AriaShaw backup scripts referenced at the end of this guide. At minimum, run docker exec odoo-db pg_dump -U odoo -d production > backup-$(date +%Y%m%d).sql
daily via cron and copy the file offsite. Test your restore procedure before you need it.
Problem: Odoo container keeps restarting in a loop
Check logs: docker compose logs odoo-web --tail 100
. Common causes:
Database connection failure: If you see “FATAL: password authentication failed for user odoo”, your .env file credentials don’t match. Verify: docker compose exec odoo-db psql -U odoo -d postgres -c '\du'
shows the correct username. Reset password: docker compose exec odoo-db psql -U postgres -c "ALTER USER odoo WITH PASSWORD 'newpassword';"
and update .env
.
Out of memory: Check docker stats odoo-web
. If memory usage hits limit-memory-hard (2.5GB in our config), reduce worker count. For 2GB instances, use --workers=3
instead of 5 and set --limit-memory-hard=1610612736
(1.5GB).
Port conflict: Another service might be using port 8069. Check: netstat -tulpn | grep 8069
. If occupied, either stop the conflicting service or change Odoo’s port in docker-compose.yml to 127.0.0.1:8070:8069
.
Problem: Can’t access Odoo via HTTPS (browser shows “connection refused”)
Systematic check:
Verify Nginx is running: systemctl status nginx
. If not active, check error log: journalctl -u nginx -n 50
. Common issue: syntax error in /etc/nginx/sites-available/odoo
. Test config: nginx -t
.
Check Vultr firewall: Log into Vultr dashboard, verify firewall group attached to your instance allows port 443 from 0.0.0.0/0. Firewall rules take 60-90 seconds to propagate.
Verify DNS resolution: From your local machine, run nslookup yourdomain.com
. Should show your Vultr server IP. If not, DNS hasn’t propagated yet—wait 1-6 hours.
Check SSL certificate: certbot certificates
should show your domain with a valid expiry date. If certificate is missing, re-run: certbot --nginx -d yourdomain.com
.
Test from server: SSH into server and run curl -I http://localhost:8069/web
. Should return 200 OK. If this works but external HTTPS doesn’t, issue is in Nginx proxy configuration.
Problem: Odoo is very slow (5+ seconds per page load)
Performance diagnosis:
Check PostgreSQL stats: docker exec odoo-db psql -U odoo -d production -c 'SELECT datname, numbackends, xact_commit, xact_rollback, blks_read, blks_hit FROM pg_stat_database WHERE datname = '\''production'\'';'
. If blks_read
is high relative to blks_hit
, you need more shared_buffers. Run the tuning script from this guide.
Identify slow queries: docker exec odoo-db psql -U odoo -d postgres -c 'SELECT query, calls, total_time/calls as avg_time_ms FROM pg_stat_statements ORDER BY total_time DESC LIMIT 10;'
. Queries averaging >100ms need optimization (add indexes or rewrite logic).
Check worker utilization: docker compose logs odoo-web | grep "worker"
. If you see many “worker X processing request” messages with same worker ID repeatedly, you need more workers. Increase --workers
setting or upgrade to larger Vultr instance.
Monitor resource usage: docker stats
. If CPU is consistently >80% on odoo-web, you’re CPU-bound—upgrade instance. If PostgreSQL memory is near limit, increase shared_buffers. If disk I/O wait is high, consider Vultr’s Block Storage for database volume.
Check for session storage bottleneck: If you’re using default PostgreSQL session storage, sessions table gets bloated. Switch to filesystem storage: add --session-storage=filesystem
to docker-compose.yml Odoo command, then docker compose restart odoo-web
.
Problem: Let’s Encrypt certificate renewal fails
Run certbot renew --dry-run
to see the exact error. Common issues:
Port 80 blocked: Let’s Encrypt HTTP-01 validation requires port 80 open. Check Vultr firewall allows 0.0.0.0/0 on port 80. Check Nginx: curl http://yourdomain.com/.well-known/acme-challenge/test
should NOT return 404.
Nginx redirect too aggressive: If your Nginx config redirects ALL port 80 traffic to HTTPS (including /.well-known), Certbot validation fails. Ensure you have exception: location /.well-known/acme-challenge/ { root /var/www/html; }
BEFORE the redirect rule.
Rate limiting: Let’s Encrypt limits 50 certificates per domain per week. Check recent attempts: certbot certificates
. If you hit limit, wait 7 days or use staging environment for testing: certbot --staging
.
Problem: Odoo database manager accessible in production
This is a critical security issue. Visit https://yourdomain.com/web/database/manager
. If you see the database manager interface, your Nginx config isn’t blocking it. Add this to your Nginx server block:
location ~* /web/database {
deny all;
return 403;
}
Reload Nginx: systemctl reload nginx
. Test again—you should get 403 Forbidden. Alternative: In Odoo config, set list_db = False
to hide database selector, but Nginx blocking is more secure.
Problem: File uploads fail with “413 Request Entity Too Large”
Nginx’s default client_max_body_size is 1MB. For Odoo document uploads, this is too small. Edit /etc/nginx/sites-available/odoo
and add inside the server
block:
client_max_body_size 100M; # Adjust based on your needs
Reload Nginx: systemctl reload nginx
. Also check Odoo’s limit: --limit-request=8192
in docker-compose.yml allows 8MB request bodies (header size, not file size). For larger files, increase this value.
Problem: Can’t connect to Odoo after server reboot
Docker containers should auto-start due to restart: unless-stopped
policy. If they don’t:
Check Docker service: systemctl status docker
. If not running, start it: systemctl start docker && systemctl enable docker
.
Check container status: docker compose ps
. If containers show “Exited”, view exit code: docker compose ps -a
. Exit code 137 = killed by OOM, reduce memory limits. Exit code 1 = application error, check logs.
Manually restart: cd /opt/odoo && docker compose up -d
. If this works, issue might be boot order—database container starts before network is ready. Add delay: create /etc/systemd/system/docker-compose-odoo.service
with dependency on network-online.target.
Once your Vultr deployment is live, the next critical step is implementing automated backups. Download our production-ready Odoo backup script.
Not sure which Vultr plan to choose? Use our Odoo Requirements Calculator.
This Vultr deployment guide is part of our complete Odoo self-hosting blueprint.
Considering AWS instead of Vultr? Compare the tradeoffs in our Odoo AWS deployment guide.
For disaster recovery planning beyond basic automation, review our comprehensive backup and restore guide.