Cómo hacer una auditoría técnica de tu instancia Odoo en producción

Checklist completo para auditar rendimiento, seguridad, backups, módulos custom, deuda técnica e infraestructura de una instancia Odoo en producción. Con métricas reales y entregable.

Por qué tu Odoo en producción necesita una auditoría periódica

La gran mayoría de instancias Odoo en producción en España llevan meses o años funcionando sin que nadie haya revisado en profundidad si la configuración sigue siendo adecuada. El sistema va respondiendo, los usuarios trabajan, y la sensación es que “funciona”. Hasta que no funciona.

El problema es que el deterioro de una instancia Odoo es gradual e invisible. Las queries que tardaban 200 ms hace dos años ahora tardan 3 segundos porque la tabla account_move ha crecido de 50.000 a 800.000 registros y los índices no se han revisado. Los backups que se configuraron en el arranque llevan seis meses sin verificarse y el último backup válido tiene cuatro días de antigüedad. Se han instalado doce módulos de terceros sin auditoría de código, alguno con un sudo innecesario en un controlador de API. Los workers de Odoo están configurados para el servidor original de 4 cores y ahora el servidor tiene 16.

Una auditoría técnica no es un proyecto de meses. Es una revisión sistemática de entre 4 y 8 horas de trabajo experto que produce un informe de hallazgos priorizados y un plan de acción. Este artículo describe exactamente cómo hacerla.

Bloque 1: Rendimiento y configuración de workers

El rendimiento de Odoo depende de tres variables que deben estar alineadas: los recursos del servidor, la configuración de workers en odoo.conf y el comportamiento de la base de datos.

1.1 Verificar la configuración de workers

La fórmula de referencia de Odoo para dimensionar workers es:

# workers = (cores * 2) + 1  (recomendación general)
# max_cron_threads = 1 o 2 (nunca 0 en producción)
# limit_memory_hard <= RAM_total / (workers + 2)

# Verificar configuración actual:
cat /etc/odoo/odoo.conf | grep -E 'workers|memory|cpu|limit|thread'

# Ver workers activos en tiempo real:
ps aux | grep odoo | grep -v grep | wc -l

# Ver uso de memoria por proceso:
ps aux --sort=-%mem | grep odoo | head -20

En una instancia con 8 cores y 32 GB de RAM, la configuración correcta es aproximadamente: workers = 16, limit_memory_hard = 2684354560 (2,5 GB), limit_memory_soft = 2147483648 (2 GB). Si el servidor tiene esos recursos y workers = 4 (la configuración por defecto de muchas instalaciones), se está dejando un 75% de la capacidad del servidor sin usar.

1.2 Detectar queries lentas en PostgreSQL

Activar log_min_duration_statement si no está ya activo y analizar con pg_stat_statements:

-- Verificar si pg_stat_statements está activo:
SELECT * FROM pg_extension WHERE extname = 'pg_stat_statements';

-- Si no está, activar (requiere reinicio de PG):
-- shared_preload_libraries = 'pg_stat_statements'  en postgresql.conf
CREATE EXTENSION pg_stat_statements;

-- Top 15 queries más lentas (tiempo total acumulado):
SELECT
    LEFT(query, 120)               AS query_preview,
    calls,
    ROUND(total_exec_time::numeric, 2) AS total_ms,
    ROUND(mean_exec_time::numeric, 2)  AS mean_ms,
    ROUND(stddev_exec_time::numeric, 2) AS stddev_ms,
    rows
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 15;

-- Top queries por llamadas (candidates para optimizar con índices):
SELECT
    LEFT(query, 120) AS query_preview,
    calls,
    ROUND(mean_exec_time::numeric, 2) AS mean_ms
FROM pg_stat_statements
ORDER BY calls DESC
LIMIT 15;

1.3 Índices faltantes y tablas infladas

-- Tablas más grandes (candidatas a revisar vacuum/autovacuum):
SELECT
    schemaname,
    tablename,
    pg_size_pretty(pg_total_relation_size(schemaname||'.'||tablename)) AS total_size,
    pg_size_pretty(pg_relation_size(schemaname||'.'||tablename)) AS table_size,
    n_dead_tup,
    n_live_tup,
    ROUND(n_dead_tup::numeric / NULLIF(n_live_tup + n_dead_tup, 0) * 100, 2) AS dead_pct,
    last_autovacuum,
    last_autoanalyze
FROM pg_stat_user_tables
ORDER BY pg_total_relation_size(schemaname||'.'||tablename) DESC
LIMIT 20;

-- Índices no usados (candidatos a eliminar):
SELECT
    schemaname,
    tablename,
    indexname,
    idx_scan,
    pg_size_pretty(pg_relation_size(indexrelid)) AS index_size
FROM pg_stat_user_indexes
WHERE idx_scan = 0
  AND schemaname = 'public'
ORDER BY pg_relation_size(indexrelid) DESC
LIMIT 20;

Bloque 2: Seguridad

La seguridad de una instancia Odoo en producción tiene cuatro capas que auditar: el servidor, Nginx, la configuración de Odoo y los módulos custom instalados.

2.1 Checklist de seguridad del servidor

  • ¿Está el puerto 8069 (Odoo) accesible desde Internet directamente? Debe estar bloqueado por firewall; solo Nginx debe poder conectar a él.
  • ¿Está el puerto 5432 (PostgreSQL) accesible desde Internet? Debe estar cerrado. Solo el servidor de aplicación debe poder conectar.
  • ¿Están actualizados los paquetes del sistema? apt list --upgradable 2>/dev/null | wc -l — si hay más de 20 paquetes pendientes de actualizar, revisar.
  • ¿Hay usuarios con claves SSH por defecto o contraseñas débiles? Auditar /etc/ssh/sshd_config: PasswordAuthentication no debe estar activo.
  • ¿Está configurado fail2ban para el puerto SSH y para Nginx? Las IPs que hacen fuerza bruta deben banearse automáticamente.

2.2 Checklist de seguridad de Odoo

# Verificar que el master password de Odoo está configurado y es robusto:
grep admin_passwd /etc/odoo/odoo.conf
# Si está vacío o es "admin", CRÍTICO — cualquiera puede acceder a /web/database/manager

# Verificar que el gestor de BBDD está bloqueado o deshabilitado:
curl -s https://tuodoo.com/web/database/manager | grep -c "Database Manager"
# Si devuelve 1, el gestor está expuesto. Añadir: list_db = False en odoo.conf

# Verificar headers de seguridad en Nginx:
curl -sI https://tuodoo.com | grep -E 'X-Frame|X-Content|Strict|Content-Security'

2.3 Auditoría rápida de módulos custom: patrones de riesgo

Los módulos custom son la superficie de ataque más habitual en instancias Odoo maduras. Revisar con grep:

# Buscar uso de sudo() sin justificación en controladores de API:
grep -r 'sudo()' /opt/odoo/custom_addons/ --include='*.py' -l

# Buscar queries SQL construidas por concatenación (riesgo SQLi):
grep -rn "cr.execute.*%s" /opt/odoo/custom_addons/ --include='*.py' | grep -v 'params'

# Buscar eval() o exec() (riesgo de ejecución de código arbitrario):
grep -rn 'eval(\|exec(' /opt/odoo/custom_addons/ --include='*.py'

# Buscar rutas HTTP sin autenticación declarada:
grep -rn "auth.*public\|auth.*none" /opt/odoo/custom_addons/ --include='*.py'

Bloque 3: Backups

En la mitad de las auditorías que hacemos, el backup “funciona” en teoría pero nadie lo ha restaurado nunca para verificarlo. Un backup no verificado no es un backup: es una esperanza.

3.1 Verificar que los backups existen y son recientes

# Verificar backups de PostgreSQL (pg_dump o pgBackRest):
ls -lhrt /var/backups/odoo/ | tail -10
# ¿Cuándo fue el último? ¿Cuántos días de antigüedad tiene?

# Si se usa el backup nativo de Odoo (zip de BBDD + filestore):
ls -lhrt /var/lib/odoo/backups/ | tail -10

# Tamaño del backup vs. tamaño de la BBDD (deben ser comparables):
du -sh /var/backups/odoo/latest_backup.zip
psql -U odoo -c "SELECT pg_size_pretty(pg_database_size('odoo_prod'));"

3.2 Test de restauración (el paso que nadie hace)

# Restaurar el último backup en una BBDD temporal para verificar integridad:
pg_restore -U postgres -d odoo_test_restore \
  --no-owner --no-acl \
  /var/backups/odoo/backup_$(date +%Y%m%d).dump

# Verificar que la restauración tiene las tablas esperadas:
psql -U odoo -d odoo_test_restore -c "
SELECT COUNT(*) FROM sale_order;
SELECT COUNT(*) FROM account_move;
SELECT MAX(write_date) FROM res_partner;
"
# Si los conteos son cero o la fecha es antigua, el backup está corrupto

Bloque 4: Módulos custom vs. core — deuda técnica

Uno de los mayores riesgos a largo plazo de una instancia Odoo es la acumulación de módulos custom que sobreescriben comportamiento del core sin documentación ni tests. Cada actualización de versión de Odoo se convierte en un proyecto de meses si hay decenas de módulos custom con overrides extensivos.

4.1 Inventario de módulos instalados

-- Módulos instalados que NO son de Odoo ni de OCA:
SELECT
    name,
    author,
    installed_version,
    state,
    category
FROM ir_module_module
WHERE state = 'installed'
  AND author NOT ILIKE '%Odoo%'
  AND author NOT ILIKE '%OCA%'
ORDER BY name;

-- Módulos con muchos modelos heredados (señal de deuda técnica alta):
SELECT
    module,
    COUNT(*) AS inherited_models
FROM ir_model_inherit
GROUP BY module
ORDER BY inherited_models DESC
LIMIT 20;

4.2 Clasificar la deuda técnica

Tipo de deudaSeñalRiesgo
Overrides de métodos core sin herencia correctadef write(self): sin super()Alto — romper en actualizaciones
Vistas heredadas con xpath fragil//field[@name='x'] en vistas que cambian en versionesMedio
Datos de configuración en código PythonIDs hardcodeados en código (env.ref('module.record_id'))Medio — depende del entorno
Módulos sin testsDirectorio tests/ vacío o ausenteMedio — riesgo silencioso en actualizaciones
Dependencias de módulos de terceros sin mantenimientoÚltimo commit hace >2 años en GitHubAlto para migraciones

Bloque 5: Infraestructura y sistema operativo

  • Versión del sistema operativo: Ubuntu 22.04 LTS o Debian 12 son las plataformas recomendadas en 2026. Si el servidor corre Ubuntu 18.04 o 20.04, el ciclo de soporte está terminando o terminado.
  • Versión de PostgreSQL: Odoo 16/17 con PostgreSQL 14 o 15 es correcto. PostgreSQL 12 o anterior tiene query planner menos eficiente y extensiones de seguridad desactualizadas.
  • Espacio en disco: verificar que hay al menos un 30% de espacio libre. Un disco lleno en un servidor Odoo en producción provoca corrupción de datos.
  • Swap: debe existir y ser al menos igual a la RAM. Si hay OOM killer activo en los logs, la configuración de memoria de Odoo (limit_memory_hard) está mal dimensionada.
# Verificaciones de infraestructura en un comando:
echo "=== DISCO ==="
df -h | grep -v tmpfs
echo "=== MEMORIA ==="
free -h
echo "=== SWAP ==="
swapon --show
echo "=== OOM KILLS (últimas 48h) ==="
dmesg --since -48h | grep -i 'oom\|killed process' | tail -10
echo "=== VERSIÓN PG ==="
psql --version
echo "=== UPTIME ==="
uptime
echo "=== CARGA SISTEMA ==="
sar -u 1 3 2>/dev/null || top -bn1 | head -5

Bloque 6: Observabilidad — qué métricas mirar

Una instancia Odoo sin observabilidad es un sistema que solo avisa cuando ya está roto. Las métricas mínimas que deben estar monitorizadas son:

MétricaUmbral de alertaCómo medirla
TTFB (Time To First Byte) de Odoo> 2 s en rutas críticasBlackbox exporter / curl + tiempo
Workers activos vs. disponibles> 80% de workers ocupadosEndpoint /web/health + métricas Nginx
Conexiones PostgreSQL activas> 80% de max_connectionsSELECT count(*) FROM pg_stat_activity
Lag de replicación (si hay réplica)> 30 segundospg_stat_replication
Queries bloqueadas (locks)Cualquier query > 5 min bloqueadapg_stat_activity WHERE wait_event_type='Lock'
Espacio en disco> 70% ocupadonode_exporter / df
Éxito de backupsMás de 24h sin backup válidoScript de verificación + alerta Telegram

La herramienta más rápida de desplegar para Odoo es el stack ELK (Elasticsearch, Logstash, Kibana) con Filebeat recogiendo los logs de Odoo, combinado con alertas vía bot de Telegram. Con esa configuración, cualquier error 500, query lenta o fallo de worker genera un mensaje al móvil del responsable técnico en tiempo real.

El entregable de una auditoría técnica

Una auditoría técnica profesional no termina en una lista de hallazgos sin priorizar. El entregable debe tener esta estructura:

  1. Resumen ejecutivo (1 página): los 3-5 riesgos críticos que requieren acción inmediata, en lenguaje no técnico para dirección.
  2. Hallazgos por bloque: rendimiento, seguridad, backups, código, infraestructura, observabilidad. Cada hallazgo con severidad (crítico / alto / medio / bajo), evidencia concreta y recomendación específica.
  3. Plan de acción priorizado: qué hacer primero, qué puede esperar, qué es deuda técnica a gestionar a medio plazo.
  4. Estimación de esfuerzo: horas aproximadas para resolver cada hallazgo, para que el cliente pueda planificar y presupuestar.
  5. Baseline de métricas: copia de las métricas clave en el momento de la auditoría (queries lentas, espacio en disco, configuración de workers) para comparar en futuras revisiones.

Con qué frecuencia hacer la auditoría

La recomendación general depende del uso del sistema:

  • Instancia crítica (> 50 usuarios, operaciones en tiempo real): revisión trimestral de métricas + auditoría completa anual.
  • Instancia media (10-50 usuarios, operativa de oficina): auditoría completa cada 6 meses.
  • Instancia pequeña (< 10 usuarios): auditoría anual, más siempre antes de una actualización de versión de Odoo.
  • Tras cualquier incidencia grave (caída, corrupción de datos, incidente de seguridad): auditoría inmediata.

Lo que una auditoría no puede hacer sola

Una auditoría es una fotografía del estado actual. Sin un plan de remediación ejecutado, los hallazgos se acumulan en un documento que nadie relee. El valor real está en la combinación de diagnóstico + plan de acción + ejecución. Para instancias críticas, la recomendación es contratar un retainer técnico mensual que incluya la resolución proactiva de los hallazgos recurrentes: optimización de queries, gestión de actualizaciones, verificación de backups y monitorización continua.

¿Quieres que revisemos tu Odoo en producción?

Solicitar auditoría técnica gratuita

Pipeline CI/CD para Odoo con GitHub Actions y Docker: guía completa
Arquitectura real de un pipeline de integración y despliegue continuo para módulos Odoo en producción