Guía Lymcon: Matomo Log Analytics en Plesk (sin bots, por logs, multi‑dominio)

Guía Lymcon: Matomo Log Analytics en Plesk (sin bots, por logs, multi‑dominio)

Contenidos ocultar
1 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 idSite en 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

  1. Cron 1 (root) copia logs desde system/<dominio>/logs/ a un staging privado de Matomo.
  2. Cron 2 (root) importa esos logs a Matomo usando import_logs.py.
  3. Antes de importar, se filtran bots por regex propia (no dependemos de la heurística de Matomo).
  4. Tras importar, se elimina el archivo staging para no inflar disco/backups.
  5. Al final, se ejecuta core:archive para 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 idSite a cada uno

Preparar el mapa sites.map

Crea el archivo con el formato:

  • ID DOMINIO

Ejemplo:

TEXT
1 ejemplo1.com
2 ejemplo2.com
3 ejemplo3.com

Ubicación recomendada:

TEXT
/var/www/vhosts/TU_WEBSpace_DE_MATOMO/private/sites.map

Comando (ejemplo):

TEXT
cat > /var/www/vhosts/TU_WEBSpace_DE_MATOMO/private/sites.map <<'EOF'
1 ejemplo1.com
2 ejemplo2.com
3 ejemplo3.com
EOF

Paso 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/):

TEXT
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-import

Importante: 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:

TEXT
ls -lah /opt/plesk/php/*/bin/php

Si php no existe en PATH, crea un symlink a la versión que uses:

TEXT
ln -sf /opt/plesk/php/8.3/bin/php /usr/local/bin/php
php -v

Paso 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):

TEXT
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/token

No 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

TEXT
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.sh

Paso 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:archive para 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:

TEXT
find /var/www/vhosts -type f -path "*misc/log-analytics/import_logs.py" 2>/dev/null

Copia 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

TEXT
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.sh

Paso 7: Prueba manual (antes del cron)

Ver bots vs humanos en el log real

TEXT
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 -l

Ejecutar pipeline completo

TEXT
/usr/local/sbin/lymcon-matomo-stage-logs.sh
/usr/local/sbin/lymcon-matomo-import-archive.sh

Confirmació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:

TEXT
/usr/local/sbin/lymcon-matomo-stage-logs.sh >/dev/null 2>&1

Tarea 2 (importar + archivar)

  • Cada hora
  • Minuto 10

Comando:

TEXT
/usr/local/sbin/lymcon-matomo-import-archive.sh >/dev/null 2>&1

Recomendació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)

TEXT
ls -ld /etc/lymcon /etc/lymcon/matomo
ls -l /etc/lymcon/matomo/token

private/ no accesible por web

TEXT
curl -I https://TU_MATOMO_DOMINIO/private/ | head -n 5

Staging no crece (debería estar vacío tras importar)

TEXT
find /var/www/vhosts/TU_WEBSpace_DE_MATOMO/private/matomo-log-import -type f -name access_ssl_log -ls | head -n 20

Endurecer permisos del staging (opcional pero recomendado)

TEXT
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 || true

Comando 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.

TEXT
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_RE no está eliminando todo

«php: not found»

Asegura symlink o PATH para PHP CLI:

TEXT
ln -sf /opt/plesk/php/8.3/bin/php /usr/local/bin/php
php -v

El importador no encuentra import_logs.py

Localízalo:

TEXT
find /var/www/vhosts -type f -path "*misc/log-analytics/import_logs.py" 2>/dev/null

El cron no ejecuta

Ejecuta manualmente:

TEXT
/usr/local/sbin/lymcon-matomo-stage-logs.sh
/usr/local/sbin/lymcon-matomo-import-archive.sh

Revisa permisos:

TEXT
ls -lah /usr/local/sbin/lymcon-matomo-*.sh
ls -ld /etc/lymcon /etc/lymcon/matomo
ls -l /etc/lymcon/matomo/token

Checklist final

  • Matomo instalado y accesible por HTTPS
  • sites.map creado con idSite 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=0 en el resumen del import