L'architecture cible
3 briques à configurer dans l'ordre — Odoo liaison locale, Nginx reverse proxy, SSL via certbot. Chacune ajoute une couche de sécurité sans impacter les autres.
1 — Prérequis : VPS Debian 12 + DNS prêt
Avant toute chose, tu as besoin de :
- Un VPS Debian 12 (2 vCPU, 4 Go RAM minimum pour 20 utilisateurs)
- Un nom de domaine (ex.
erp.monentreprise.dz) avec un enregistrement A pointant l'IP publique du VPS - Un Odoo 19 CE déjà installé et tournant sur
127.0.0.1:8069(cf. article T01 de la série) - Un accès root SSH par clé publique
Vérification rapide DNS :
# Depuis ton poste local
dig +short erp.monentreprise.dz
# → doit retourner l'IP publique de ton VPS
# Ex: 195.110.35.177
/.well-known/acme-challenge/ — si le
domaine ne pointe pas vers ton VPS, l'émission du cert échoue.
2 — Verrouiller Odoo sur l'interface loopback
En prod, Odoo ne doit jamais écouter sur 0.0.0.0
(toutes les interfaces). Le reverse proxy Nginx est le seul interlocuteur
externe — Odoo reste derrière. Éditer /etc/odoo/odoo.conf :
[options]
; Bind loopback seulement
xmlrpc_interface = 127.0.0.1
longpolling_port = 8072
; Proxy mode : Odoo fait confiance au X-Forwarded-*
proxy_mode = True
; Filestore persistant
data_dir = /var/lib/odoo/filestore
; Log
logfile = /var/log/odoo/odoo.log
log_level = info
log_handler = :INFO
; DB
db_host = localhost
db_port = 5432
db_user = odoo
db_password = CHANGE_ME
; Admin
admin_passwd = CHANGE_ME_TOO_LONG
list_db = False
; Workers pour prod (nombre de vCPU × 2 + 1)
workers = 5
max_cron_threads = 2
limit_memory_hard = 2684354560
limit_memory_soft = 2147483648
limit_time_cpu = 600
limet_time_real = 1200
Le flag proxy_mode = True est critique : sans
lui, Odoo ignore les headers X-Forwarded-Proto et X-Real-IP
que Nginx transmet, et génère des redirections HTTP incorrectes.
Redémarrer et vérifier :
systemctl restart odoo
ss -tlnp | grep -E '8069|8072'
# → doit afficher 127.0.0.1:8069 et 127.0.0.1:8072 uniquement
# Jamais 0.0.0.0:*
3 — Installer et configurer Nginx
apt update
apt install -y nginx
systemctl enable --now nginx
Créer le vhost pour Odoo. Fichier /etc/nginx/sites-available/odoo :
upstream odoo {
server 127.0.0.1:8069;
}
upstream odoo_chat {
server 127.0.0.1:8072;
}
# Bloc HTTP — redirection vers HTTPS
server {
listen 80;
listen [::]:80;
server_name erp.monentreprise.dz;
# ACME challenge reste en clair (exigence certbot)
location /.well-known/acme-challenge/ {
root /var/www/html;
}
# Tout le reste en HTTPS
location / {
return 301 https://$host$request_uri;
}
}
# Bloc HTTPS — proxy vers Odoo
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name erp.monentreprise.dz;
# Certificats Let's Encrypt (ajoutés par certbot à l'étape 4)
# ssl_certificate /etc/letsencrypt/live/erp.monentreprise.dz/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/erp.monentreprise.dz/privkey.pem;
# Sécurité TLS moderne (Mozilla Intermediate 2024)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# Headers de sécurité
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options SAMEORIGIN;
add_header Referrer-Policy strict-origin-when-cross-origin;
# Logs dédiés
access_log /var/log/nginx/odoo.access.log;
error_log /var/log/nginx/odoo.error.log;
# Limites upload (imports CSV lourds, photos produits)
client_max_body_size 100M;
# Proxy général vers Odoo
location / {
proxy_pass http://odoo;
proxy_read_timeout 720s;
proxy_connect_timeout 720s;
proxy_send_timeout 720s;
proxy_http_version 1.1;
# Headers pour proxy_mode=True côté 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_set_header X-Forwarded-Host $host;
proxy_redirect off;
}
# Canal long-polling (notifications chatter temps réel)
location /longpolling {
proxy_pass http://odoo_chat;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 720s;
}
# Cache statique (assets compilés, images)
location ~* /web/static/ {
proxy_cache_valid 200 60m;
proxy_buffering on;
expires 864000;
proxy_pass http://odoo;
}
# Gzip
gzip on;
gzip_min_length 1100;
gzip_buffers 4 32k;
gzip_types text/css text/less text/plain text/xml application/xml application/json application/javascript;
gzip_vary on;
}
Activer le vhost et tester :
ln -s /etc/nginx/sites-available/odoo /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
nginx -t # vérification syntaxe
systemctl reload nginx
ssl_certificate*
en commentaire pour que nginx -t passe. Décommente après
l'étape 4.
4 — Obtenir un certificat Let's Encrypt avec certbot
Installer certbot + plugin Nginx :
apt install -y certbot python3-certbot-nginx
Lancer l'obtention du certificat :
certbot --nginx -d erp.monentreprise.dz \
--email admin@monentreprise.dz \
--agree-tos --no-eff-email --redirect
Certbot fait 3 choses automatiquement :
- Génère une paire clé/cert via challenge HTTP-01 sur
/.well-known/acme-challenge/ - Stocke la clé dans
/etc/letsencrypt/live/erp.monentreprise.dz/ - Décommente les lignes
ssl_certificate*dans le vhost Nginx et recharge le service
Vérifier le résultat :
curl -I https://erp.monentreprise.dz
# → HTTP/2 303 (redirection web → /odoo)
# → strict-transport-security: max-age=63072000; includeSubDomains
# → server: nginx/1.22.1
certbot certificates
# → Certificate Name: erp.monentreprise.dz
# → Domains: erp.monentreprise.dz
# → Expiry Date: 2026-07-14 (90 jours)
5 — Auto-renouvellement du certificat
Let's Encrypt émet des certs valides 90 jours. Les laisser expirer = site inaccessible en HTTPS. Certbot installe automatiquement un timer systemd :
systemctl list-timers | grep certbot
# → certbot.timer activé, tick 2×/jour
# Tester le renouvellement à blanc (dry-run)
certbot renew --dry-run
# → Congratulations, all simulated renewals succeeded
Le certbot renouvelle uniquement les certs expirant < 30 jours. Donc 2 fois par an dans la pratique. Zéro action manuelle nécessaire — mais surveille quand même les logs :
journalctl -u certbot.service --since "7 days ago"
Optionnel mais recommandé — recharger Nginx après chaque renouvellement.
Créer /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh :
#!/bin/bash
systemctl reload nginx
chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
6 — Durcissement final (hardening)
Le setup marche, SSL vert, Odoo accessible. 5 derniers gestes qui distinguent une prod sérieuse d'un déploiement dilettante :
6.1 — Firewall UFW
apt install -y ufw
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp # SSH (change le port en prod)
ufw allow 80/tcp # HTTP (pour redirection + ACME)
ufw allow 443/tcp # HTTPS
ufw enable
ufw status numbered
Odoo reste invisible de l'extérieur — 8069 n'est jamais ouvert.
6.2 — Fail2ban sur Nginx
apt install -y fail2ban
systemctl enable --now fail2ban
Configuration /etc/fail2ban/jail.local :
[sshd]
enabled = true
maxretry = 3
bantime = 1h
[nginx-http-auth]
enabled = true
filter = nginx-http-auth
logpath = /var/log/nginx/odoo.error.log
[nginx-noscript]
enabled = true
filter = nginx-noscript
logpath = /var/log/nginx/odoo.access.log
maxretry = 6
6.3 — list_db = False
Critique : dans /etc/odoo/odoo.conf tu as
list_db = False. Sans ça, n'importe quel
visiteur peut lister tes bases via /web/database/manager —
risque de bruteforce sur admin_passwd.
6.4 — Backup automatique
Cron quotidien vers un stockage distant (cf. article T07 de la série) :
# /etc/cron.daily/backup-odoo
#!/bin/bash
set -e
DATE=$(date +%Y%m%d)
DEST=/var/backups/odoo
mkdir -p $DEST
pg_dump -U odoo -Fc mydb > $DEST/db-$DATE.dump
tar czf $DEST/filestore-$DATE.tgz -C /var/lib/odoo/filestore mydb
# Rétention 14 jours
find $DEST -name 'db-*.dump' -mtime +14 -delete
find $DEST -name 'filestore-*.tgz' -mtime +14 -delete
# Sync offsite (rclone → S3/Wasabi/Backblaze)
rclone copy $DEST remote:odoo-backups/$(hostname)/
6.5 — Monitoring
Au minimum : uptime externe (UptimeRobot gratuit) + alerte email si HTTP 5xx. Optionnel : Netdata / Prometheus pour métriques système.
Checklist de mise en production
- ✅ DNS A record pointe vers l'IP VPS
- ✅ Odoo binding loopback uniquement (
xmlrpc_interface = 127.0.0.1) - ✅
proxy_mode = Truedans odoo.conf - ✅
admin_passwdfort (> 24 caractères) - ✅
list_db = False - ✅ Nginx vhost + HTTPS redirect
- ✅ Certificat Let's Encrypt émis
- ✅
certbot renew --dry-runOK - ✅ UFW actif, 22/80/443 uniquement
- ✅ Fail2ban actif sur SSH + Nginx
- ✅ Backup quotidien vers stockage offsite
- ✅ Uptime monitoring externe
En résumé
Une mise en prod Odoo 19 sérieuse tient en 6 étapes et environ 2 heures la première fois (30 minutes les suivantes). Les 3 erreurs que je vois trop souvent :
- Odoo bind 0.0.0.0 et firewall ouvert → port 8069 accessible publiquement
proxy_mode=False+ reverse proxy HTTPS → redirections incohérentes, cookies cassés- Pas de auto-renew testé → cert expire un dimanche, site HS 18h
Évite ces 3 pièges et ta stack Odoo 19 tient la route plusieurs années sans friction. C'est exactement le setup qui fait tourner https://odooskills.com aujourd'hui.
Prochain article (T25) : passer à la couche applicative Website — créer ton site vitrine Odoo sans écrire une ligne de HTML, maîtriser les menus, les thèmes, le SEO on-page et les extensions utiles.