Guía Lymcon: Matomo Log Analytics en Plesk (sin bots, por logs, multi‑dominio)
Introducción
En muchos servidores con Plesk, las estadísticas «clásicas» (AWStats/Webalizer) se quedan cortas: cuesta filtrar, cuesta comparar periodos y, sobre todo, los números suelen venir inflados por bots y automatizaciones. Por otro lado, Google Analytics y similares dependen de cookies y de JavaScript; en Europa mucha gente no acepta cookies, y entonces los números «bajan» y no reflejan el tráfico real.
Esta guía está pensada para resolver ese problema con un enfoque muy directo:
- Usar Matomo como panel (tipo Analytics) instalado en tu propio servidor.
- Alimentarlo con logs del servidor (sin cookies, sin script en la web).
- Filtrar bots antes de importar usando una regla determinista por User‑Agent (para que no dependa de heurísticas y no se «cuelen» automatizaciones camufladas).
El resultado: un panel moderno, multi‑dominio, con importación automática por cron, que refleja mejor las visitas reales «server‑side».
Objetivo
Montar un panel de analítica server‑side en Matomo (sin cookies, sin JS) alimentado por logs de Plesk, con:
- Multi‑dominio (varios
idSiteen un único Matomo) - Importación automática por cron (cada hora o diario)
- Métricas limpias (sin bots ni automatizaciones camufladas)
- Seguridad: token fuera del webspace, scripts en rutas del sistema, staging temporal en
private/y borrado tras importar
Para quién es esta guía
- Agencias / administradores que gestionan varios dominios en un mismo servidor y quieren dar a cada cliente su panel.
- Propietarios de webs que quieren números más reales sin depender de cookies.
- Equipos técnicos que prefieren una solución basada en logs (fiable, auditable y sin scripts en front).
Si nunca has tocado Matomo o cron, no pasa nada: seguimos un camino seguro y repetible.
Requisitos
Requisitos técnicos
- Servidor Linux con Plesk
- Matomo On‑Premise instalado y funcionando (HTTPS recomendado)
- Acceso SSH como
root - Python 3 disponible
- PHP CLI disponible (en Plesk suele estar en
/opt/plesk/php/<version>/bin/php)
Requisitos de datos
Logs de Plesk por dominio disponibles (normalmente):
- HTTPS:
/var/www/vhosts/system/DOMINIO/logs/access_ssl_log - HTTP:
/var/www/vhosts/system/DOMINIO/logs/access_log
Nota: esta guía usa HTTPS (
access_ssl_log). Si tu tráfico real está en HTTP, ajusta la ruta.
Conceptos rápidos (en español llano)
Antes de entrar en comandos, conviene entender tres piezas:
- Logs: son los registros que el servidor escribe por cada petición (URL, fecha, IP, User‑Agent…).
- Importador (import_logs.py): un script que «traduce» esas líneas de log y las manda a Matomo.
- Archivado (core:archive): Matomo calcula informes (día/semana/mes) y los deja listos para el panel.
La parte clave de esta guía es que filtramos bots antes de importar. Así el panel queda limpio, aunque existan bots que se camuflen como navegadores.
Arquitectura recomendada (segura)
Flujo de datos
- Cron 1 (root) copia logs desde
system/<dominio>/logs/a un staging privado de Matomo. - Cron 2 (root) importa esos logs a Matomo usando
import_logs.py. - Antes de importar, se filtran bots por regex propia (no dependemos de la heurística de Matomo).
- Tras importar, se elimina el archivo staging para no inflar disco/backups.
- Al final, se ejecuta
core:archivepara calcular informes.
Carpetas y rutas (hardened)
- Staging (privado, dentro del webspace de Matomo):
/var/www/vhosts/TU_WEBSpace_DE_MATOMO/private/matomo-log-import/<dominio>/access_ssl_log
- Scripts (solo root, ruta de binarios del sistema):
/usr/local/sbin/lymcon-matomo-stage-logs.sh/usr/local/sbin/lymcon-matomo-import-archive.sh
- Token (solo root, fuera del webspace):
/etc/lymcon/matomo/token
Paso 1: Crear sitios (idSite) en Matomo
Matomo funciona como un «contenedor» donde cada web tiene su propio identificador interno (idSite). Piensa en ello como si fuese una propiedad/medición de Analytics: cada dominio es un «sitio» dentro del mismo Matomo.
Crear un sitio por dominio
En Matomo:
- Configuración → Sitios web (Measurables) → Añadir nuevo
- Añade cada dominio
- Matomo asigna un
idSitea cada uno
Preparar el mapa sites.map
Crea el archivo con el formato:
ID DOMINIO
Ejemplo:
1 ejemplo1.com
2 ejemplo2.com
3 ejemplo3.comUbicación recomendada:
/var/www/vhosts/TU_WEBSpace_DE_MATOMO/private/sites.mapComando (ejemplo):
cat > /var/www/vhosts/TU_WEBSpace_DE_MATOMO/private/sites.map <<'EOF'
1 ejemplo1.com
2 ejemplo2.com
3 ejemplo3.com
EOFPaso 2: Preparar staging privado en el webspace de Matomo
En Plesk, lo correcto es guardar archivos sensibles fuera del directorio público (httpdocs). Por eso usamos private/: ahí el staging de logs no es accesible por URL.
Crea las carpetas (no en httpdocs, sino en private/):
mkdir -p /var/www/vhosts/TU_WEBSpace_DE_MATOMO/private/matomo-log-import
chmod 750 /var/www/vhosts/TU_WEBSpace_DE_MATOMO/private
chmod 750 /var/www/vhosts/TU_WEBSpace_DE_MATOMO/private/matomo-log-importImportante:
private/es el lugar adecuado para datos no públicos.
Paso 3: Asegurar PHP CLI en consola
Matomo funciona en web con PHP (FPM), pero el archivado se ejecuta por consola (CLI). Si el comando php no está disponible, el archivado fallará. Este paso garantiza que el PHP CLI existe.
En Plesk, PHP CLI suele estar en /opt/plesk/php/<version>/bin/php.
Ejemplo de comprobación:
ls -lah /opt/plesk/php/*/bin/phpSi php no existe en PATH, crea un symlink a la versión que uses:
ln -sf /opt/plesk/php/8.3/bin/php /usr/local/bin/php
php -vPaso 4: Token de Matomo (solo para import)
El importador necesita autenticarse contra Matomo para enviar los datos. Por seguridad, usamos un token guardado en un fichero solo‑root. Así evitamos que quede expuesto en scripts o en el webspace.
Crea un usuario dedicado (recomendado) en Matomo para importación y copia su token.
Guárdalo en (ruta hardened):
mkdir -p /etc/lymcon/matomo
chmod 700 /etc/lymcon
chmod 700 /etc/lymcon/matomo
echo 'PON_AQUI_TU_TOKEN_AUTH' > /etc/lymcon/matomo/token
chmod 600 /etc/lymcon/matomo/tokenNo metas tokens dentro de scripts ni en rutas del webspace.
Paso 5: Script 1 — Copiar logs (root)
Este script no importa nada todavía: solo copia los logs de cada dominio al staging privado. Es rápido y sirve para que el importador trabaje siempre con copias dentro del webspace de Matomo (sin dar acceso directo a los logs del sistema).
Guarda como:
/usr/local/sbin/lymcon-matomo-stage-logs.sh
cat > /usr/local/sbin/lymcon-matomo-stage-logs.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
SITES_MAP="/var/www/vhosts/TU_WEBSpace_DE_MATOMO/private/sites.map"
STAGE_BASE="/var/www/vhosts/TU_WEBSpace_DE_MATOMO/private/matomo-log-import"
while read -r IDSITE DOMAIN; do
[[ -z "${IDSITE:-}" || -z "${DOMAIN:-}" ]] && continue
[[ "${IDSITE:0:1}" == "#" ]] && continue
SRC="/var/www/vhosts/system/${DOMAIN}/logs/access_ssl_log"
DST_DIR="${STAGE_BASE}/${DOMAIN}"
DST="${DST_DIR}/access_ssl_log"
mkdir -p "${DST_DIR}"
chmod 750 "${DST_DIR}" || true
# Copia el log actual (si existe). Si no existe, se salta.
if [[ -f "${SRC}" ]]; then
cp -f "${SRC}" "${DST}"
chmod 640 "${DST}"
fi
done < "${SITES_MAP}"
EOF
chmod 700 /usr/local/sbin/lymcon-matomo-stage-logs.sh
chown root:root /usr/local/sbin/lymcon-matomo-stage-logs.shPaso 6: Script 2 — Importar + archivar (root)
Este es el «corazón» del sistema:
- Lee cada dominio desde
sites.map. - Toma el log staging.
- Filtra bots/automatización por regex antes de que Matomo lo procese.
- Importa el resultado en Matomo.
- Borra el staging para no inflar backups.
- Ejecuta
core:archivepara que el panel se actualice.
Guarda como:
/usr/local/sbin/lymcon-matomo-import-archive.sh
Definir el importador
El importador suele estar en:
.../misc/log-analytics/import_logs.py
Localízalo con:
find /var/www/vhosts -type f -path "*misc/log-analytics/import_logs.py" 2>/dev/nullCopia esa ruta en la variable IMPORTER.
Filtro determinista de bots/automatización (regex)
Este enfoque garantiza que lo que el log «marca» como bot/automatización por UA quede fuera de Matomo.
Script completo
cat > /usr/local/sbin/lymcon-matomo-import-archive.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
# Evitar solapamientos
LOCK="/tmp/lymcon-matomo-import-archive.lock"
exec 9>"$LOCK"
flock -n 9 || exit 0
MATOMO_URL="https://TU_MATOMO_DOMINIO"
SITES_MAP="/var/www/vhosts/TU_WEBSpace_DE_MATOMO/private/sites.map"
STAGE_BASE="/var/www/vhosts/TU_WEBSpace_DE_MATOMO/private/matomo-log-import"
# Ruta real del importador
IMPORTER="/RUTA/REAL/misc/log-analytics/import_logs.py"
# Token solo root
TOKEN="$(cat /etc/lymcon/matomo/token)"
# Asegura PHP CLI
export PATH="/opt/plesk/php/8.3/bin:/usr/local/bin:/usr/bin:/bin:$PATH"
# Regex determinista para filtrar bots/automatizaciones por User-Agent
BOT_RE='bot|spider|crawler|crawl|slurp|scanner|monitor|googlebot|bingbot|duckduckbot|yandexbot|baiduspider|applebot|adsbot|ahrefs|semrush|mj12|uptimerobot|montastic|chatgpt-user|gptbot|perplexity|claudebot|go-http-client|axios|python-requests|httpclient|curl|wget|java|libwww|urllib|woocommerce|geedoshopproductfinder|candidatecrawler'
while read -r IDSITE DOMAIN; do
[[ -z "${IDSITE:-}" || -z "${DOMAIN:-}" ]] && continue
[[ "${IDSITE:0:1}" == "#" ]] && continue
LOG="${STAGE_BASE}/${DOMAIN}/access_ssl_log"
if [[ -f "${LOG}" ]]; then
echo
echo "== Importing idSite=${IDSITE} domain=${DOMAIN} =="
# Importar SOLO humano (filtramos antes y alimentamos por stdin)
python3 "${IMPORTER}" \
--url="${MATOMO_URL}" \
--idsite="${IDSITE}" \
--token-auth="${TOKEN}" \
- < <(grep -viE "${BOT_RE}" "${LOG}" || true) || true
# Borrar staging para no inflar disco/backups
rm -f "${LOG}"
fi
done < "${SITES_MAP}"
# Archivar informes (procesa dashboards)
cd /var/www/vhosts/TU_WEBSpace_DE_MATOMO
php ./console core:archive --force-all-websites --url="${MATOMO_URL}"
EOF
chmod 700 /usr/local/sbin/lymcon-matomo-import-archive.sh
chown root:root /usr/local/sbin/lymcon-matomo-import-archive.shPaso 7: Prueba manual (antes del cron)
Ver bots vs humanos en el log real
BOT_RE='bot|spider|crawler|crawl|slurp|scanner|monitor|googlebot|bingbot|duckduckbot|yandexbot|baiduspider|applebot|adsbot|ahrefs|semrush|mj12|uptimerobot|montastic|chatgpt-user|gptbot|perplexity|claudebot|go-http-client|axios|python-requests|httpclient|curl|wget|java|libwww|urllib|woocommerce|geedoshopproductfinder|candidatecrawler'
LOG="/var/www/vhosts/system/DOMINIO/logs/access_ssl_log"
echo "Total lines:"; wc -l < "$LOG"
echo "Bot lines:"; grep -Eai "$BOT_RE" "$LOG" | wc -l
echo "Human lines:"; grep -Evai "$BOT_RE" "$LOG" | wc -lEjecutar pipeline completo
/usr/local/sbin/lymcon-matomo-stage-logs.sh
/usr/local/sbin/lymcon-matomo-import-archive.shConfirmación esperada
En cada bloque del import:
requests done by bots, search engines...debe aparecer como 0
Porque los bots ya no llegan al importador.
Paso 8: Programar Cron en Plesk (root)
En Plesk → Herramientas y configuración → Tareas programadas (Cron):
Tarea 1 (copiar logs)
- Cada hora
- Minuto 00
Comando:
/usr/local/sbin/lymcon-matomo-stage-logs.sh >/dev/null 2>&1Tarea 2 (importar + archivar)
- Cada hora
- Minuto 10
Comando:
/usr/local/sbin/lymcon-matomo-import-archive.sh >/dev/null 2>&1Recomendación: separar 5–10 minutos entre ambos para evitar carreras.
Notas importantes
Por qué filtrar antes (grep) y no depender de Matomo
Matomo detecta bots «estándar», pero automatizaciones camufladas (axios, Go-http-client, monitores, scrapers) pueden colarse como «humanos».
Con este enfoque:
- La regla de bots es determinista y controlada por el equipo.
- Matomo ya no «decide»: solo procesa lo que le damos.
Por qué borrar staging
- Reduce tamaño de disco
- Evita que backups crezcan
- El dato final queda en la BD de Matomo
Ajustar la regex
Si aparecen nuevas firmas en logs, añade términos al BOT_RE (una sola línea).
Rendimiento
Cada hora está bien para empezar. Si el import crece, se puede pasar a:
- Cada 2–4 horas
- O diario (nocturno)
Verificación de seguridad (recomendada)
Token protegido (solo root)
ls -ld /etc/lymcon /etc/lymcon/matomo
ls -l /etc/lymcon/matomo/tokenprivate/ no accesible por web
curl -I https://TU_MATOMO_DOMINIO/private/ | head -n 5Staging no crece (debería estar vacío tras importar)
find /var/www/vhosts/TU_WEBSpace_DE_MATOMO/private/matomo-log-import -type f -name access_ssl_log -ls | head -n 20Endurecer permisos del staging (opcional pero recomendado)
MATOMO_WS="/var/www/vhosts/TU_WEBSpace_DE_MATOMO"
STAGE="$MATOMO_WS/private/matomo-log-import"
chmod 750 "$MATOMO_WS/private" "$STAGE"
find "$STAGE" -type d -exec chmod 750 {} \;
find "$STAGE" -type f -exec chmod 640 {} \; 2>/dev/null || trueComando todo‑en‑uno (hardening + verificación + ejecución)
Este comando endurece permisos del staging, comprueba que private/ está bloqueado, ejecuta stage + import, verifica que staging queda limpio y muestra un resumen por dominio.
bash -lc '
set -euo pipefail
MATOMO_WS="/var/www/vhosts/TU_WEBSpace_DE_MATOMO"
STAGE="$MATOMO_WS/private/matomo-log-import"
SYNC="/usr/local/sbin/lymcon-matomo-stage-logs.sh"
IMP="/usr/local/sbin/lymcon-matomo-import-archive.sh"
echo "==[1/5] Endureciendo permisos staging (750 dirs, 640 files) =="
chmod 750 "$MATOMO_WS/private" "$STAGE"
find "$STAGE" -type d -exec chmod 750 {} \;
find "$STAGE" -type f -exec chmod 640 {} \; 2>/dev/null || true
echo "==[2/5] Chequeo web: private/ bloqueado =="
echo -n "TU_MATOMO_DOMINIO/private -> "
curl -sI https://TU_MATOMO_DOMINIO/private/ | head -n 1
echo "==[3/5] Ejecutando stage + import/archive =="
"$SYNC"
OUT="$(mktemp)"
set +e
"$IMP" | tee "$OUT"
RC=$?
set -e
echo "exit_code=$RC"
echo "==[4/5] Verificando staging limpio (sin access_ssl_log) =="
if find "$STAGE" -type f -name access_ssl_log -print -quit | grep -q .; then
echo "WARNING: Hay access_ssl_log en staging (debería borrarse):"
find "$STAGE" -type f -name access_ssl_log -ls | head -n 20
else
echo "OK: staging limpio (no hay access_ssl_log)"
fi
echo "==[5/5] Resumen por dominio (importados) =="
awk "
/^== Importing idSite=/ { dom=\$0; sub(/.*domain=/,\"\",dom); gsub(/ ==.*/,\"\",dom) }
/requests imported successfully/ { print dom \": \", \$1, \": imported\" }
" "$OUT" || true
rm -f "$OUT"
echo "== Listo =="
'Solución de problemas
No se importan datos (0 importados)
- Verifica que el dominio tiene tráfico real
- Verifica que el log correcto es
access_ssl_log - Comprueba que el filtro
BOT_REno está eliminando todo
«php: not found»
Asegura symlink o PATH para PHP CLI:
ln -sf /opt/plesk/php/8.3/bin/php /usr/local/bin/php
php -vEl importador no encuentra import_logs.py
Localízalo:
find /var/www/vhosts -type f -path "*misc/log-analytics/import_logs.py" 2>/dev/nullEl cron no ejecuta
Ejecuta manualmente:
/usr/local/sbin/lymcon-matomo-stage-logs.sh
/usr/local/sbin/lymcon-matomo-import-archive.shRevisa permisos:
ls -lah /usr/local/sbin/lymcon-matomo-*.sh
ls -ld /etc/lymcon /etc/lymcon/matomo
ls -l /etc/lymcon/matomo/tokenChecklist final
- Matomo instalado y accesible por HTTPS
sites.mapcreado conidSite dominio- Staging en
private/matomo-log-import/ - Token en
/etc/lymcon/matomo/token(600) - Scripts en
/usr/local/sbin/(700) - Cron root: stage (min 00) + import/archive (min 10)
- Verificado en consola:
bots=0en el resumen del import







