Skip to content
DantePereyra edited this page Dec 1, 2025 · 20 revisions

Install & Configure Nginx

Here we'll do a basic Nginx configuration to use Port 80.

Install Nginx

sudo -i
apt install nginx

Create Site Config

First, remove the default archive (with Webmin or by command), and create one called odoo-80 (for _test use odoo-82) :

rm /etc/nginx/sites-available/default
nano /etc/nginx/sites-available/odoo-80

Paste the following (you must change server_name with the IP or VM name):

# odoo server
upstream odoo {              #When doing erp_test change to odoo-test
    server 127.0.0.1:8169;
}
upstream odoochat {          #When doing erp_test change to odoochat-test
    server 127.0.0.1:8172;
}
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;
    server_name $ip_or_vm;
    proxy_read_timeout 720s;
    proxy_connect_timeout 720s;
    proxy_send_timeout 720s;
    client_max_body_size 128M;
    large_client_header_buffers 16 32k;

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

    location /websocket {
        proxy_pass http://odoochat;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header X-Forwarded-Host $http_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;

        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
    }

    location / {
        proxy_set_header X-Forwarded-Host $http_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;
        proxy_pass http://odoo;

        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
    }

    location /.well-known/acme-challenge/ {
	    alias /var/www/acme/;
	    try_files $uri =404;
    }

    gzip_types text/css text/scss text/plain text/xml application/xml application/json application/javascript;
    gzip on;
}

Enable the Site

Again, remove the default archive and create a symbolic link with the one created before:

rm /etc/nginx/sites-enabled/default
ln -s /etc/nginx/sites-available/odoo-80 /etc/nginx/sites-enabled/odoo-80

Set Proxy Mode

Now it's time to end the .env configuration we talked about here:

nano .env
PROXY_MODE=True

Restart Odoo and Nginx

sudo docker-compose down
sudo docker-compose up -d
systemctl restart nginx.service

If Nginx fails, check:

systemctl status nginx.service

Note: If you follow Odoo's configuration system and cookie_flags fails, check first if Nginx version is 1.18 and remove the following line (requires Nginx 1.19+):

proxy_cookie_flags session_id samesite=lax secure;  # requires nginx 1.19.8

Allow Port 80 (UFW)

After all configuration, it's time to check if it works on port 80 by putting the server name on your browser and see if Odoo is active. If it doesn’t, check ufw and allow 80/tcp:

sudo ufw status
sudo ufw allow 80/tcp

Let’s Encrypt

For this to work, we need to ensure the destination route exists and then we can restart nginx:

mkdir /var/www/acme
systemctl restart nginx

Next, the structure that will host the certificates and other items is created:

mkdir /etc/nginx/ssl/
chmod 700 /etc/nginx/ssl/
cd /etc/nginx/ssl/

At this point, you can create the different structures (from now on, the subdomain indicated is the example one, x-odoo.solvos.es):

Certificate for the account we will "open" in Let's Encrypt

openssl genrsa 4096 > acct.key

Domain private key (When doing test config, repeat from this point if also have HTTPS, remember change $domain)

openssl genrsa 4096 > $domain.key

Fault the domain certificate request (the following line would fail), only if necessary

dd if=/dev/urandom of=/root/.rnd bs=256 count=1

Request for the domain certificate domain

openssl req -new -sha256 -key $domain.key -subj "/CN=$domain" > $domain.csr

You now have the private key and the certificate request, among other things. From there, we download the script that allows us to create and recreate the final certificate:

Download the signing tool

wget -O - "https://raw.githubusercontent.com/diafygi/acme-tiny/master/acme_tiny.py" > acme_tiny.py

Generate the signed certificate against Let's Encrypt

python3 acme_tiny.py --account-key ./acct.key --csr ./$domain.csr --acme-dir /var/www/acme/ > ./signed.crt

Create the Nginx-compatible certificate (pem)

wget -O - https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem > intermediate.pem
cat signed.crt intermediate.pem > $domain.pem

⚠️ Change all $domain with real domain e.g. solvos-odoo.es:

#odoo server
upstream odoo {             #When doing erp_test change to odoo-test
  server 127.0.0.1:8169;
}
upstream odoochat {         #When doing erp_test change to odoochat-test
  server 127.0.0.1:8172;
}
map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;
}

# http -> https + Let's Encrypt challenge
server {
  listen 80;
  server_name $domain;

  location / {
    rewrite ^(.*) https://$host$1 permanent;
  }

  location /.well-known/acme-challenge/ {
    alias /var/www/acme/;
    try_files $uri =404;
  }

}

server {
  listen 443 ssl;
  server_name $domain;
  proxy_read_timeout 720s;
  proxy_connect_timeout 720s;
  proxy_send_timeout 720s;

  # SSL parameters
  ssl_certificate /etc/nginx/ssl/$domain.pem;
  ssl_certificate_key /etc/nginx/ssl/$domain.key;
  ssl_session_timeout 30m;
  ssl_protocols TLSv1.2;
  ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
  ssl_prefer_server_ciphers off;

  # log
  access_log /var/log/nginx/odoo.access.log;    #odoo-test.access.log
  error_log /var/log/nginx/odoo.error.log;      #odoo-test.error.log
  # file size upload
  client_max_body_size 128M;

  # Redirect websocket requests to odoo gevent port
  location /websocket {
    proxy_pass http://odoochat;                 #odoochat-test
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_set_header X-Forwarded-Host $http_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;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
  }

  # Redirect requests to odoo backend server
  location / {
    # Add Headers for odoo proxy mode
    proxy_set_header X-Forwarded-Host $http_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;
    proxy_pass http://odoo;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
  }

  # common gzip
  gzip_types text/css text/scss text/plain text/xml application/xml application/json application/javascript;
  gzip on;
}

Create cron to renewal

nano renewal.sh
#!/bin/sh

cd /etc/nginx/ssl
python3 ./acme_tiny.py --account-key ./acct.key --csr ./domain.csr --acme-dir /var/www/acme/ > ./signed.crt || exit
wget -O - https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem > intermediate.pem
cat ./signed.crt ./intermediate.pem > ./domain.pem
systemctl restart nginx

Give execution permission

chmod +x renewal.sh

At 6 in the morning on the 30th

crontab -e
0 6 30 * * /etc/nginx/ssl/renewal.sh

Clone this wiki locally