Per què la teva Odoo en producció necessita una auditoria periòdica
La gran majoria d'instàncies Odoo en producció a Espanya porten mesos o anys funcionant sense que ningú hagi revisat en profunditat si la configuració continua sent adequada. El sistema va responent, els usuaris treballen, i la sensació és que «funciona». Fins que no funciona.
El problema és que el deteriorament d'una instància Odoo és gradual i invisible. Les consultes que tardaven 200 ms fa dos anys ara triguen 3 segons perquè la taula account_move ha crescut de 50.000 a 800.000 registres i els índexs no s'han revisat. Les còpies de seguretat que es van configurar en l'arrencada porten sis mesos sense verificar-se i l'última còpia vàlida té quatre dies d'antiguitat. S'han instal·lat dotze mòduls de tercers sense auditoria de codi, algun amb un sudo innecessari en un controlador d'API. Els workers d'Odoo estan configurats per al servidor original de 4 nuclis i ara el servidor en té 16.
Una auditoria tècnica no és un projecte de mesos. És una revisió sistemàtica d'entre 4 i 8 hores de treball expert que produeix un informe de troballes prioritzades i un pla d'acció. Aquest article descriu exactament com fer-la.
Bloc 1: Rendiment i configuració de workers
El rendiment d'Odoo depèn de tres variables que han d'estar alineades: els recursos del servidor, la configuració de workers a odoo.conf i el comportament de la base de dades.
1.1 Verificar la configuració de workers
La fórmula de referència d'Odoo per dimensionar workers és:
# 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 instància amb 8 nuclis i 32 GB de RAM, la configuració correcta és aproximadament: workers = 16, limit_memory_hard = 2684354560 (2,5 GB), limit_memory_soft = 2147483648 (2 GB). Si el servidor té aquests recursos i workers = 4 (la configuració per defecte de moltes instal·lacions), s'està deixant un 75% de la capacitat del servidor sense usar.
1.2 Detectar consultes lentes a PostgreSQL
Activa log_min_duration_statement si no està ja actiu i analitza amb 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 Índexs faltants i taules inflades
-- 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;
Bloc 2: Seguretat
La seguretat d'una instància Odoo en producció té quatre capes a auditar: el servidor, Nginx, la configuració d'Odoo i els mòduls personalitzats instal·lats.
2.1 Checklist de seguretat del servidor
- El port 8069 (Odoo) és accessible des d'Internet directament? Ha d'estar bloquejat pel firewall; només Nginx ha de poder connectar-s'hi.
- El port 5432 (PostgreSQL) és accessible des d'Internet? Ha d'estar tancat. Només el servidor d'aplicació ha de poder connectar.
- Estan actualitzats els paquets del sistema?
apt list --upgradable 2>/dev/null | wc -l— si hi ha més de 20 paquets pendents d'actualitzar, revisar. - Hi ha usuaris amb claus SSH per defecte o contrasenyes febles? Audita
/etc/ssh/sshd_config:PasswordAuthentication noha d'estar actiu. - Està configurat fail2ban per al port SSH i per a Nginx? Les IPs que fan força bruta han de ser prohibides automàticament.
2.2 Checklist de seguretat d'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 Auditoria ràpida de mòduls personalitzats: patrons de risc
Els mòduls personalitzats són la superfície d'atac més habitual en instàncies Odoo madures. Revisa amb 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'
Bloc 3: Còpies de seguretat
En la meitat de les auditories que fem, la còpia de seguretat «funciona» en teoria però ningú l'ha restaurada mai per verificar-la. Una còpia de seguretat no verificada no és una còpia: és una esperança.
3.1 Verificar que les còpies existeixen i són recents
# 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 Prova de restauració (el pas que ningú fa)
# 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
Bloc 4: Mòduls personalitzats vs. core — deute tècnic
Un dels majors riscos a llarg termini d'una instància Odoo és l'acumulació de mòduls personalitzats que sobreescriuen el comportament del core sense documentació ni tests. Cada actualització de versió d'Odoo es converteix en un projecte de mesos si hi ha desenes de mòduls personalitzats amb overrides extensos.
4.1 Inventari de mòduls instal·lats
-- 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 Classificar el deute tècnic
| Tipus de deute | Senyal | Risc |
|---|---|---|
| Overrides de mètodes core sense herència correcta | def write(self): sense super() | Alt — trenca en actualitzacions |
| Vistes heretades amb xpath fràgil | //field[@name='x'] en vistes que canvien entre versions | Mitjà |
| Dades de configuració en codi Python | IDs hardcodejats en codi (env.ref('module.record_id')) | Mitjà — depèn de l'entorn |
| Mòduls sense tests | Directori tests/ buit o absent | Mitjà — risc silenciós en actualitzacions |
| Dependències de mòduls de tercers sense manteniment | Darrer commit fa >2 anys a GitHub | Alt per a migracions |
Bloc 5: Infraestructura i sistema operatiu
- Versió del sistema operatiu: Ubuntu 22.04 LTS o Debian 12 són les plataformes recomanades el 2026. Si el servidor funciona amb Ubuntu 18.04 o 20.04, el cicle de suport s'està acabant o ja ha acabat.
- Versió de PostgreSQL: Odoo 16/17 amb PostgreSQL 14 o 15 és correcte. PostgreSQL 12 o anterior té un query planner menys eficient i extensions de seguretat desactualitzades.
- Espai en disc: verifica que hi ha almenys un 30% d'espai lliure. Un disc ple en un servidor Odoo en producció provoca corrupció de dades.
- Swap: ha d'existir i ser almenys igual a la RAM. Si el OOM killer està actiu als logs, la configuració de memòria d'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
Bloc 6: Observabilitat — quines mètriques mirar
Una instància Odoo sense observabilitat és un sistema que només avisa quan ja està trencat. Les mètriques mínimes que han d'estar monitoritzades són:
| Mètrica | Llindar d'alerta | Com mesurar-la |
|---|---|---|
| TTFB (Time To First Byte) d'Odoo | > 2 s en rutes crítiques | Blackbox exporter / curl + temps |
| Workers actius vs. disponibles | > 80% de workers ocupats | Endpoint /web/health + mètriques Nginx |
| Connexions PostgreSQL actives | > 80% de max_connections | SELECT count(*) FROM pg_stat_activity |
| Lag de replicació (si hi ha rèplica) | > 30 segons | pg_stat_replication |
| Consultes bloquejades (locks) | Qualsevol consulta > 5 min bloquejada | pg_stat_activity WHERE wait_event_type='Lock' |
| Espai en disc | > 70% ocupat | node_exporter / df |
| Èxit de les còpies de seguretat | Més de 24 h sense còpia vàlida | Script de verificació + alerta Telegram |
L'eina més ràpida de desplegar per a Odoo és l'stack ELK (Elasticsearch, Logstash, Kibana) amb Filebeat recollint els logs d'Odoo, combinat amb alertes via bot de Telegram. Amb aquesta configuració, qualsevol error 500, consulta lenta o fallada de worker genera un missatge al mòbil del responsable tècnic en temps real.
L'entregable d'una auditoria tècnica
Una auditoria tècnica professional no acaba en una llista de troballes sense prioritzar. L'entregable ha de tenir aquesta estructura:
- Resum executiu (1 pàgina): els 3–5 riscos crítics que requereixen acció immediata, en llenguatge no tècnic per a la direcció.
- Troballes per bloc: rendiment, seguretat, còpies de seguretat, codi, infraestructura, observabilitat. Cada troballa amb severitat (crític / alt / mitjà / baix), evidència concreta i recomanació específica.
- Pla d'acció prioritzat: què fer primer, què pot esperar, quin és el deute tècnic a gestionar a mig termini.
- Estimació d'esforç: hores aproximades per resoldre cada troballa, perquè el client pugui planificar i pressupostar.
- Baseline de mètriques: còpia de les mètriques clau en el moment de l'auditoria (consultes lentes, espai en disc, configuració de workers) per comparar en futures revisions.
Amb quina freqüència fer l'auditoria
La recomanació general depèn de l'ús del sistema:
- Instància crítica (> 50 usuaris, operacions en temps real): revisió trimestral de mètriques + auditoria completa anual.
- Instància mitjana (10–50 usuaris, operativa d'oficina): auditoria completa cada 6 mesos.
- Instància petita (< 10 usuaris): auditoria anual, més sempre abans d'una actualització de versió d'Odoo.
- Després de qualsevol incidència greu (caiguda, corrupció de dades, incident de seguretat): auditoria immediata.
El que una auditoria no pot fer sola
Una auditoria és una fotografia de l'estat actual. Sense un pla de remediació executat, les troballes s'acumulen en un document que ningú torna a llegir. El valor real està en la combinació de diagnòstic + pla d'acció + execució. Per a instàncies crítiques, la recomanació és contractar un retainer tècnic mensual que inclogui la resolució proactiva de les troballes recurrents: optimització de consultes, gestió d'actualitzacions, verificació de còpies de seguretat i monitorització contínua.