Seguridad en Odoo: hardening del servidor y Nginx para producción

Guía técnica completa para reducir la superficie de ataque de Odoo: usuario no-root, firewall, fail2ban, TLS, HSTS, rate limiting, headers de seguridad, ocultar el gestor de bases de datos y backups cifrados.

Por qué la seguridad de Odoo en producción se descuida sistemáticamente

La mayoría de implantaciones Odoo llegan a producción con la configuración por defecto del asistente de instalación, un Nginx copiado de un tutorial de hace cinco años y la contraseña maestra de base de datos como «admin». Esa combinación es el sueño de cualquier atacante automatizado que escanea rangos de IP buscando instancias Odoo expuestas.

Odoo expone por defecto varios vectores de ataque que deben cerrarse explícitamente antes de poner una instancia en Internet: el gestor de bases de datos accesible desde fuera, el endpoint de login sin límite de intentos, la versión exacta de Odoo en las cabeceras HTTP, y una configuración TLS que puede quedar en parámetros de hace una década. Esta guía recorre cada uno de esos vectores y da la configuración concreta para cerrarlos.

El contexto es real: la arquitectura descrita aquí es la que aplico en producción en clientes como Rehabmedic (sector salud, datos de pacientes, cumplimiento RGPD) y la que está en la base del servidor que aloja skanndar.top.

Superficie de ataque de Odoo: qué puede ser atacado

Antes de endurecer, hay que entender qué hay que endurecer. En una instalación Odoo estándar con Nginx como proxy, los vectores de ataque son:

  • /web/database/manager: interfaz web para crear, borrar, duplicar y restaurar bases de datos. Si está accesible desde Internet, un atacante puede intentar crear una BD propia, restaurar un dump con datos alterados o simplemente borrar las existentes.
  • /web/database/backup y /web/database/restore: endpoints que permiten descargar y restaurar backups de toda la base de datos con solo conocer la contraseña maestra.
  • Endpoint de login /web/login: sin rate limiting, acepta intentos de fuerza bruta contra cualquier cuenta de usuario, incluida la del administrador.
  • Cabeceras HTTP de versión: Odoo incluye por defecto cabeceras que revelan la versión exacta del sistema, lo que permite a los atacantes buscar vulnerabilidades conocidas de esa versión específica.
  • Puerto de base de datos 5432: si PostgreSQL está accesible desde fuera del servidor (un error común), cualquiera con credenciales puede conectarse directamente a la BD.
  • Puerto 8069 expuesto directamente: Odoo no está diseñado para recibir tráfico externo directamente; el protocolo HTTP de Odoo no tiene las protecciones que añade un proxy reverso.
  • Xmlrpc: los endpoints /xmlrpc/2/common y /xmlrpc/2/object permiten acceso programático a casi toda la funcionalidad de Odoo. Sin restricciones de IP ni autenticación API adecuada, son otro vector de fuerza bruta.

Hardening del sistema operativo

Usuario no-root para Odoo

Odoo nunca debe correr como root. Si el proceso de Odoo es comprometido, el atacante no debe tener privilegios de sistema. La práctica estándar es crear un usuario dedicado sin shell de login:

# Crear usuario y grupo odoo sin home ni shell interactiva
useradd -r -s /usr/sbin/nologin -d /opt/odoo -m odoo

# Asignar ownership de los directorios de Odoo
chown -R odoo:odoo /opt/odoo
chown -R odoo:odoo /var/log/odoo
chmod 750 /opt/odoo
chmod 750 /var/log/odoo

# El fichero odoo.conf no debe ser legible por otros usuarios
chown odoo:odoo /etc/odoo/odoo.conf
chmod 640 /etc/odoo/odoo.conf

En entornos Docker, esto equivale a definir el usuario en el Dockerfile (USER odoo) y asegurarse de que el entrypoint no hace sudo ni cambia de usuario en tiempo de ejecución.

Firewall con UFW o iptables

La política por defecto debe ser denegar todo el tráfico entrante y abrir solo los puertos necesarios. Los puertos 8069 y 8072 de Odoo nunca deben estar abiertos en el firewall público: solo Nginx debe hablar con ellos, y debe hacerlo por la interfaz de loopback o por la red interna Docker:

# UFW: política restrictiva
ufw default deny incoming
ufw default allow outgoing

# Solo SSH, HTTP y HTTPS son accesibles desde Internet
ufw allow 22/tcp    # SSH (mejor cambiar al puerto no estándar)
ufw allow 80/tcp    # HTTP → redirigir a HTTPS en Nginx
ufw allow 443/tcp   # HTTPS

# PostgreSQL solo desde localhost o red interna
# NO abrir 5432 al exterior

# Activar
ufw enable
ufw status verbose

Si el servidor está en una VPC o red privada de nube (AWS, Hetzner, OVH), añadir también las reglas de grupo de seguridad a nivel de proveedor como segunda capa.

fail2ban: bloqueo automático de ataques de fuerza bruta

fail2ban lee los logs de Nginx y de Odoo y bloquea automáticamente las IPs que superan un umbral de intentos fallidos. Es especialmente efectivo contra los escáneres automatizados que intentan credenciales conocidas contra el endpoint de login de Odoo:

# /etc/fail2ban/jail.local

[DEFAULT]
bantime  = 3600       # 1 hora bloqueado por defecto
findtime = 600        # ventana de detección: 10 minutos
maxretry = 5          # 5 intentos fallidos antes del ban
ignoreip = 127.0.0.1/8 ::1  # no banear localhost

# Jail para Nginx: ataques genéricos y escaneos
[nginx-http-auth]
enabled  = true
port     = http,https
logpath  = /var/log/nginx/error.log
maxretry = 5

# Jail específica para el login de Odoo
# (requiere que los logs de Odoo registren intentos fallidos)
[odoo-login]
enabled  = true
port     = http,https
logpath  = /var/log/odoo/odoo.log
filter   = odoo-login
maxretry = 5
bantime  = 7200

El filtro de fail2ban para Odoo busca la cadena que Odoo escribe en el log cuando un login falla:

# /etc/fail2ban/filter.d/odoo-login.conf
[Definition]
failregex = .*Login failed for.*<HOST>
            .*Authentication failure.*<HOST>
ignoreregex =

Verificar que fail2ban está corriendo y ver los bans activos:

systemctl status fail2ban
fail2ban-client status odoo-login
fail2ban-client status nginx-http-auth

Configuración segura de Nginx

TLS moderno y HSTS

La configuración TLS por defecto de Nginx es demasiado permisiva. Hay que forzar TLS 1.2 y 1.3 exclusivamente, usar cifrados seguros y activar HSTS con preload para que los navegadores recuerden que el dominio solo debe ser accedido por HTTPS:

# /etc/nginx/conf.d/ssl-params.conf
# Incluir este fichero desde los bloques server con: include conf.d/ssl-params.conf;

# Solo TLS 1.2 y 1.3 (eliminar TLS 1.0 y 1.1)
ssl_protocols TLSv1.2 TLSv1.3;

# Cifrados modernos — Mozilla Intermediate Configuration (2024)
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;

# Sesiones TLS
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_session_tickets off;

# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;

# Parámetros DH (generar con: openssl dhparam -out /etc/nginx/dhparam.pem 4096)
ssl_dhparam /etc/nginx/dhparam.pem;

Headers de seguridad HTTP

Los headers de seguridad son la segunda línea de defensa contra ataques XSS, clickjacking e inyección de contenido. Odoo ya envía algunos por defecto, pero deben reforzarse en Nginx para garantizar que se aplican aunque Odoo falle:

# /etc/nginx/conf.d/security-headers.conf
# Incluir con: include conf.d/security-headers.conf;

# Evitar que la página sea cargada en un iframe (clickjacking)
add_header X-Frame-Options "SAMEORIGIN" always;

# Evitar que el navegador detecte el MIME type de forma automática
add_header X-Content-Type-Options "nosniff" always;

# XSS Protection (para navegadores antiguos sin CSP)
add_header X-XSS-Protection "1; mode=block" always;

# HSTS: forzar HTTPS durante 1 año, incluir subdominios y preload
# AVISO: una vez activado, NO se puede revertir sin esperar el tiempo de expiración
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

# Referrer Policy: no filtrar la URL completa en peticiones cross-origin
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

# Permissions Policy: desactivar funcionalidades del navegador no necesarias
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always;

# Content Security Policy (CSP) — ajustar a los dominios reales de tu instancia
# Este es un punto de partida conservador; afina según los errores de consola
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'self';" always;

# Ocultar la versión de Nginx
server_tokens off;

Nota importante sobre CSP en Odoo: Odoo usa unsafe-inline y unsafe-eval extensivamente en su JavaScript. Una política CSP estricta romperá partes de la UI. El CSP mostrado arriba es el mínimo funcional; en producción se recomienda monitorizar las violaciones CSP en modo report-only antes de activar la política en modo de bloqueo.

Rate limiting para prevenir fuerza bruta y DDoS

El rate limiting de Nginx limita cuántas peticiones puede hacer una IP por segundo a endpoints específicos. Los targets más críticos son el endpoint de login y los de la API XML-RPC:

# /etc/nginx/nginx.conf (bloque http)
http {
    # ...

    # Zonas de rate limiting
    # Zona para el endpoint de login: 5 req/seg por IP, buffer de 10MB
    limit_req_zone $binary_remote_addr zone=odoo_login:10m rate=5r/m;

    # Zona para la API (XML-RPC / JSON-RPC): 30 req/seg por IP
    limit_req_zone $binary_remote_addr zone=odoo_api:10m rate=30r/s;

    # Zona global para todo el tráfico Odoo
    limit_req_zone $binary_remote_addr zone=odoo_global:10m rate=100r/s;

    # Tamaño máximo de body (evitar ataques de upload masivo)
    client_max_body_size 64m;
    client_body_timeout 30s;
    client_header_timeout 30s;
    keepalive_timeout 30s;
    send_timeout 30s;

    # ...
}

Bloque Nginx completo para Odoo en producción

Este es el fichero de configuración de servidor virtual completo. Combina todas las medidas anteriores con el proxy reverso hacia Odoo y el bloqueo explícito del gestor de bases de datos:

# /etc/nginx/sites-available/odoo.conf

# Redirigir HTTP → HTTPS
server {
    listen 80;
    server_name tudominio.com www.tudominio.com;
    return 301 https://tudominio.com$request_uri;
}

# Redirigir www → non-www (SEO: evitar contenido duplicado)
server {
    listen 443 ssl;
    server_name www.tudominio.com;
    include conf.d/ssl-params.conf;
    ssl_certificate /etc/letsencrypt/live/tudominio.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/tudominio.com/privkey.pem;
    return 301 https://tudominio.com$request_uri;
}

# Servidor principal
server {
    listen 443 ssl;
    server_name tudominio.com;

    # Certificado TLS (Certbot / Let's Encrypt)
    ssl_certificate /etc/letsencrypt/live/tudominio.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/tudominio.com/privkey.pem;
    include conf.d/ssl-params.conf;

    # Headers de seguridad
    include conf.d/security-headers.conf;

    # Logs
    access_log /var/log/nginx/odoo_access.log combined;
    error_log  /var/log/nginx/odoo_error.log warn;

    # -----------------------------------------------------------------
    # BLOQUEAR el gestor de bases de datos desde el exterior
    # Esta es una de las medidas más importantes para Odoo
    # -----------------------------------------------------------------
    location ~* ^/web/database/ {
        # Permitir solo desde localhost (para acceso de administración local)
        allow 127.0.0.1;
        deny all;
        # Opcional: devolver 404 en lugar de 403 para no revelar que existe
        # return 404;
    }

    # Bloquear también los endpoints de backup y restore explícitamente
    location = /web/database/backup {
        allow 127.0.0.1;
        deny all;
    }
    location = /web/database/restore {
        allow 127.0.0.1;
        deny all;
    }

    # -----------------------------------------------------------------
    # Rate limiting en endpoint de login
    # -----------------------------------------------------------------
    location = /web/login {
        limit_req zone=odoo_login burst=3 nodelay;
        limit_req_status 429;

        proxy_pass http://127.0.0.1:8069;
        include conf.d/proxy-params.conf;
    }

    # -----------------------------------------------------------------
    # Rate limiting en endpoints XML-RPC / JSON-RPC
    # -----------------------------------------------------------------
    location ~* ^/xmlrpc/ {
        limit_req zone=odoo_api burst=20 nodelay;
        limit_req_status 429;

        proxy_pass http://127.0.0.1:8069;
        include conf.d/proxy-params.conf;
    }

    location ~* ^/web/dataset/ {
        limit_req zone=odoo_api burst=50 nodelay;

        proxy_pass http://127.0.0.1:8069;
        include conf.d/proxy-params.conf;
    }

    # -----------------------------------------------------------------
    # Longpolling (chat, notificaciones en tiempo real)
    # -----------------------------------------------------------------
    location /longpolling/ {
        proxy_pass http://127.0.0.1:8072;
        include conf.d/proxy-params.conf;
        proxy_read_timeout 3600s;
        proxy_connect_timeout 3600s;
    }

    # -----------------------------------------------------------------
    # Ficheros estáticos: cache agresiva
    # -----------------------------------------------------------------
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
        proxy_pass http://127.0.0.1:8069;
        include conf.d/proxy-params.conf;
        expires 30d;
        add_header Cache-Control "public, immutable";
        # Los headers de seguridad no se aplican aquí (no son HTML)
    }

    # -----------------------------------------------------------------
    # Tráfico general Odoo
    # -----------------------------------------------------------------
    location / {
        limit_req zone=odoo_global burst=100 nodelay;

        proxy_pass http://127.0.0.1:8069;
        include conf.d/proxy-params.conf;
    }
}

El fichero de parámetros de proxy centraliza las directivas comunes y evita repeticiones:

# /etc/nginx/conf.d/proxy-params.conf
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;
proxy_buffering off;           # necesario para longpolling
proxy_read_timeout 720s;
proxy_connect_timeout 720s;
proxy_send_timeout 720s;

Configuración segura de Odoo (odoo.conf)

El propio Odoo tiene opciones de configuración que reducen significativamente la superficie de ataque. Estas son las más importantes para un entorno de producción:

# /etc/odoo/odoo.conf

[options]

; -------------------------------------------------------
; SEGURIDAD: parámetros críticos
; -------------------------------------------------------

; Contraseña maestra de la base de datos
; NUNCA dejar 'admin' o en blanco en producción
; Generar con: python3 -c "import secrets; print(secrets.token_urlsafe(32))"
admin_passwd = REEMPLAZAR_CON_TOKEN_ALEATORIO_SEGURO

; Desactivar el listado de bases de datos disponibles en el login
; Evita que cualquier visitante vea los nombres de tus BDs
list_db = False

; Filtro de base de datos: solo servir una BD específica por dominio
; Evita que rutas maliciosas accedan a otras BDs del servidor
dbfilter = ^odoo_produccion$

; No mostrar errores de traza completa al usuario final
; (los errores detallados van al log, no al navegador)
debug_mode = False

; -------------------------------------------------------
; PROXY: necesario cuando Nginx actúa como proxy reverso
; -------------------------------------------------------
proxy_mode = True

; -------------------------------------------------------
; WORKERS Y RENDIMIENTO
; En producción usar workers múltiples (no 0)
; Regla de referencia: 2 * núcleos_CPU + 1
; -------------------------------------------------------
workers = 5
max_cron_threads = 2

; Límites de memoria para prevenir ataques de agotamiento de recursos
limit_memory_hard = 2684354560   ; 2.5 GB
limit_memory_soft = 2147483648   ; 2 GB
limit_request = 8192
limit_time_cpu = 120
limit_time_real = 240

; -------------------------------------------------------
; BASE DE DATOS
; -------------------------------------------------------
db_host = 127.0.0.1
db_port = 5432
db_user = odoo_prod
db_password = CONTRASENA_BD_SEGURA
db_maxconn = 64

; -------------------------------------------------------
; LOGS
; -------------------------------------------------------
log_level = warn
logfile = /var/log/odoo/odoo.log
logrotate = True

; -------------------------------------------------------
; DIRECTORIOS
; -------------------------------------------------------
addons_path = /opt/odoo/addons,/opt/odoo/custom_addons
data_dir = /var/lib/odoo

Sobre list_db = False: esta opción es especialmente importante. Sin ella, la pantalla de login de Odoo muestra un desplegable con todos los nombres de las bases de datos del servidor PostgreSQL. Un atacante que sabe los nombres de las BDs puede dirigir sus ataques de forma mucho más eficiente.

Sobre dbfilter: el filtro limita qué base de datos se puede seleccionar según el dominio de la petición. Aunque solo tengas una BD, activar este filtro añade una capa adicional: aunque alguien logre bypassear el gestor web, la petición HTTP solo podrá acceder a la BD que coincida con el patrón regex.

Seguridad de PostgreSQL

PostgreSQL tiene su propia configuración de seguridad que va más allá de lo que Odoo gestiona:

# /etc/postgresql/16/main/postgresql.conf

# Solo escuchar en localhost — NUNCA en 0.0.0.0 para producción
listen_addresses = '127.0.0.1'

# Logging de conexiones y sentencias lentas
log_connections = on
log_disconnections = on
log_min_duration_statement = 1000   # loguear queries > 1 segundo
log_line_prefix = '%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h '

# SSL obligatorio para conexiones remotas (si las hubiera)
ssl = on
# /etc/postgresql/16/main/pg_hba.conf
# Política de autenticación: solo md5/scram-sha-256 desde localhost

# TYPE  DATABASE        USER            ADDRESS                 METHOD
local   all             postgres                                peer
local   all             all                                     md5
host    odoo_produccion odoo_prod       127.0.0.1/32            scram-sha-256
# NO añadir líneas con 0.0.0.0/0 ni con trust

Backups cifrados y verificados

Un backup sin cifrar es un riesgo RGPD: si el fichero de backup queda expuesto accidentalmente (en un S3 mal configurado, en un disco extraviado), los datos de todos tus clientes quedan comprometidos. El backup debe estar cifrado en reposo y en tránsito.

#!/bin/bash
# /opt/scripts/backup-odoo-cifrado.sh
# Ejecutar desde cron: 0 2 * * * /opt/scripts/backup-odoo-cifrado.sh

set -euo pipefail

DB_NAME="odoo_produccion"
DB_USER="odoo_prod"
BACKUP_DIR="/opt/backups"
S3_BUCKET="s3://tu-bucket-backups/odoo/"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="${BACKUP_DIR}/odoo_${DB_NAME}_${TIMESTAMP}.dump"
ENCRYPTED_FILE="${BACKUP_FILE}.gpg"
# ID de la clave GPG del receptor (administrador del servidor)
GPG_RECIPIENT="backup@tudominio.com"

mkdir -p "${BACKUP_DIR}"

echo "[$(date)] Iniciando backup de ${DB_NAME}..."

# 1. Dump en formato custom de PostgreSQL (comprimido y resumible)
pg_dump -U "${DB_USER}" -h 127.0.0.1 -Fc "${DB_NAME}" -f "${BACKUP_FILE}"

# 2. Verificar integridad del dump
if ! pg_restore --list "${BACKUP_FILE}" > /dev/null 2>&1; then
    echo "[ERROR] El dump no es válido. Abortando."
    rm -f "${BACKUP_FILE}"
    exit 1
fi

BACKUP_SIZE=$(stat -c%s "${BACKUP_FILE}")
if [ "${BACKUP_SIZE}" -lt 1048576 ]; then
    echo "[ERROR] El dump es sospechosamente pequeño: ${BACKUP_SIZE} bytes."
    exit 1
fi

# 3. Cifrar con GPG (cifrado asimétrico — la clave privada no está en el servidor)
gpg --trust-model always \
    --recipient "${GPG_RECIPIENT}" \
    --output "${ENCRYPTED_FILE}" \
    --encrypt "${BACKUP_FILE}"

# 4. Subir a S3 (las credenciales AWS deben estar en ~/.aws/credentials o en rol IAM)
aws s3 cp "${ENCRYPTED_FILE}" "${S3_BUCKET}" --storage-class STANDARD_IA

# 5. Eliminar el dump sin cifrar localmente
rm -f "${BACKUP_FILE}"

# 6. Limpiar backups locales cifrados de más de 7 días
find "${BACKUP_DIR}" -name "*.dump.gpg" -mtime +7 -delete

echo "[$(date)] Backup completado: ${ENCRYPTED_FILE} (${BACKUP_SIZE} bytes) → ${S3_BUCKET}"

La clave de este proceso es el cifrado asimétrico con GPG: solo el poseedor de la clave privada (el administrador) puede descifrar el backup. Aunque alguien obtenga acceso al bucket S3 o al disco donde se almacenan los ficheros cifrados, los datos son ilegibles.

RGPD y ENS: consideraciones para instancias Odoo en España

Las empresas en España que tratan datos personales en Odoo (clientes, empleados, pacientes en sector salud) están sujetas al Reglamento General de Protección de Datos (RGPD) y, si son proveedores de servicios a las Administraciones Públicas, también al Esquema Nacional de Seguridad (ENS).

Las medidas técnicas descritas en esta guía cubren los controles técnicos del RGPD (artículo 32), específicamente:

  • Seudonimización y cifrado: los backups cifrados y el TLS en tránsito cumplen el requisito de cifrado tanto en reposo como en tránsito.
  • Confidencialidad e integridad: el endurecimiento del servidor (usuario no-root, firewall, PostgreSQL sin exposición externa) garantiza que solo los sistemas autorizados acceden a los datos.
  • Disponibilidad y resiliencia: los backups verificados y el fail2ban que previene ataques de disponibilidad contribuyen a este requisito.
  • Capacidad de restauración: los backups regulares con verificación de integridad cumplen el requisito de poder restaurar la disponibilidad de los datos en caso de incidente.

Adicionalmente, se recomienda:

  • Activar el log de auditoría de Odoo (módulo auditlog de la OCA) para registrar qué usuario modificó qué datos y cuándo. Esencial para demostrar el control de acceso en caso de inspección de la AEPD.
  • Configurar la política de contraseñas en Odoo (Settings > Technical > Security > Password Policy): longitud mínima, complejidad, caducidad.
  • Activar la autenticación de dos factores (2FA) para cuentas de administrador y usuarios con acceso a datos especialmente sensibles.
  • Revisar los permisos de grupos de Odoo periódicamente: el principio de mínimo privilegio implica que un usuario de ventas no debe tener acceso a nóminas, y un usuario de almacén no debe ver datos contables.

Checklist de seguridad: antes de ir a producción

CheckEstado esperado
Odoo corre como usuario no-rootVerificar con ps aux | grep odoo
Puerto 8069/8072 no expuesto en firewallufw status — no debe aparecer 8069
Puerto 5432 no expuesto al exteriorss -tlnp | grep 5432 — solo 127.0.0.1
/web/database/manager bloqueado desde exteriorcurl -I https://tudominio.com/web/database/manager → 403
list_db = False en odoo.confEl login no muestra desplegable de BDs
admin_passwd no es 'admin'Token aleatorio de 32+ caracteres
TLS 1.0 y 1.1 desactivadosnmap --script ssl-enum-ciphers tudominio.com
HSTS activo con max-age=31536000curl -I https://tudominio.com | grep Strict-Transport
server_tokens offEl header Server no debe revelar la versión de Nginx
fail2ban activo y monitorizando Odoofail2ban-client status odoo-login
Backups cifrados y verificados diariamenteCron activo, verificar S3 o destino remoto
2FA activado para cuentas de administradorSettings > Users > Enable 2FA

Conclusión

Hardening no es un proyecto puntual: es una disciplina continua. Las configuraciones de esta guía cubren los vectores de ataque más frecuentes y las exigencias de cumplimiento más habituales en España, pero la seguridad requiere actualizaciones cuando aparecen nuevas vulnerabilidades en Odoo (seguir el Security Advisory de Odoo S.A.), cuando cambian los estándares TLS, o cuando la arquitectura del sistema evoluciona.

Si estás implantando Odoo en un sector regulado (salud, finanzas, administración pública) o simplemente quieres asegurarte de que tu instancia no es el próximo cliente en una nota de prensa de incidente de seguridad, la inversión en hardening correcto desde el primer día es órdenes de magnitud más barata que la respuesta a un incidente.

¿Quieres una auditoría de seguridad de tu instancia Odoo?

Solicitar auditoría de seguridad

Odoo vs SAP vs Dynamics: comparativa honesta para PYMEs españolas 2026
Coste total, tiempo de implantación, flexibilidad y cuándo elegir cada ERP sin vender humo