Mode opératoire — Mesurer l’audience d’un site sans demander le consentement (Matomo), et le mettre en ligne en sécurité
Document autonome. Partie théorique, puis mise en œuvre telle qu’elle a été réalisée et vérifiée les 10 et 11/11/2026 sur le lab : Matomo 5.14.2 sur les serveurs web (
srv-web-01actif,srv-web-02secours, zone WEB), base dans le cluster Galera, répartiteur HAProxysrv-ha(10.0.10.5), site WordPress de GSB (entreprise fictive), publié sur Internet par Pangolin. Phase 18 du suivi (SUIVI-TP.md), étape 72 de la documentation, justification écrite dans Justification-RGPD-Matomo.md. Prérequis : un site WordPress derrière HAProxy (MO-3), un cluster Galera (MO-2), un tunnel Pangolin (MO-10), la supervision (MO-13, MO-17) et Wazuh (MO-19).Aucun mot de passe ni nom de domaine personnel ici :
<domaine>,<mot-de-passe>. Les mots de passe sont dans Bitwarden (registre des comptes, D-15 et W-04).Dernière mise à jour : 11/11/2026
Sommaire
| § | Contenu |
|---|---|
| 1 | Pourquoi mesurer, et pourquoi sans consentement |
| 2 | L’architecture retenue |
| 3 | Installer Matomo en actif / secours |
| 4 | Régler Matomo selon la CNIL |
| 5 | Brancher le site : code de suivi, mentions, case d’opposition |
| 6 | Mettre le site en ligne (Pangolin, WordPress, HAProxy) |
| 7 | La vraie adresse du visiteur |
| 8 | Surveiller le site public |
| 9 | Vérifier depuis Internet |
| 10 | Pièges et checklist |
1 · Pourquoi mesurer, et pourquoi sans consentement
Un éditeur de site veut savoir combien de personnes viennent et quelles pages elles lisent. Pour cela, un petit script s’exécute dans le navigateur du visiteur et envoie la visite à un outil de mesure.
| Question | Réponse |
|---|---|
| Faut-il demander le consentement du visiteur ? | En principe oui (le script dépose un cookie). Exception : la CNIL dispense la mesure d’audience strictement limitée (lignes directrices du 17/09/2020) |
| À quelles conditions ? | finalité limitée à la fréquentation du seul site ; adresse IP tronquée ; cookies de 13 mois au plus ; données gardées 25 mois au plus ; information et moyen simple de s’opposer ; aucun croisement avec d’autres données |
| Qui doit le prouver ? | l’éditeur du site (depuis 2026, plus de liste officielle d’outils « exemptés ») : d’où le document de justification |
| Pourquoi Matomo ? | logiciel libre, hébergé chez soi : rien n’est envoyé à un tiers, et il sait appliquer les réglages de la CNIL |
Principe à retenir : on ne collecte que ce qui sert à la mesure, on tronque l’adresse avant de l’écrire, et on laisse le visiteur dire non. Tout ce qui sert à autre chose (profils, cartes de chaleur, e-commerce) est coupé.
2 · L’architecture retenue
visiteur ─HTTPS─► box ─► Nginx Proxy Manager (réseau domestique) ─► Traefik (Pangolin) ─tunnel─► srv-docker
│
srv-web-01 (actif) ◄──┐ ▼
srv-web-02 (secours) ◄┴── srv-ha (HAProxy : règles du nom public)
WordPress + Matomo base « wp_lab » et base « matomo » dans Galera
| Choix | Pourquoi |
|---|---|
| Matomo à part de WordPress (pas une extension) | moins de code dans le site exposé ; mises à jour séparées |
Actif / secours comme GLPI (copie par lsyncd) | même mécanique que le reste du lab, déjà éprouvée |
Suivi par /stats/ sur l’adresse du site | le navigateur n’appelle que le site lui-même (traceur « de première partie »), l’interface de Matomo n’a pas de nom public |
Base matomo dédiée, compte limité à cette base | un compte compromis ne voit ni WordPress ni GLPI |
3 · Installer Matomo en actif / secours
3.1 La base et le compte (sur un nœud Galera)
CREATE DATABASE matomo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'matomo'@'10.0.10.5' IDENTIFIED BY '<mot-de-passe>';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, CREATE TEMPORARY TABLES, LOCK TABLES ON matomo.* TO 'matomo'@'10.0.10.5';
Le compte est créé pour l’adresse de HAProxy : les serveurs web passent par lui pour joindre la base. Le mot de passe
est saisi sans affichage (read -rsp) et rangé dans Bitwarden.
3.2 Télécharger, vérifier, mettre en place
- Version : la dernière stable (la 6 n’existait qu’en préversion). Taille et empreinte SHA-256 sont publiées par Matomo sur la page de la version (GitHub).
- Sur
srv-web-01: télécharger, comparer l’empreinte, puis seulement extraire.
curl -fsS -o matomo-5.14.2.tar.gz https://builds.matomo.org/matomo-5.14.2.tar.gz
sha256sum matomo-5.14.2.tar.gz # doit être identique à l'empreinte publiée
tar -xzf matomo-5.14.2.tar.gz -C /var/www matomo && chown -R www-data:www-data /var/www/matomo
- Site Apache
stats.sio.internal(HAProxy termine le HTTPS) :
<VirtualHost *:80>
ServerName stats.sio.internal
DocumentRoot /var/www/matomo
php_admin_value memory_limit 256M
<Directory /var/www/matomo>
Options -Indexes
AllowOverride All # les .htaccess de Matomo protègent config/, tmp/, lang/
Require all granted
</Directory>
SetEnvIf X-Forwarded-Proto "^https$" HTTPS=on
</VirtualHost>
- Installeur web (
https://stats.sio.internal) : vérification du système (tout vert), base10.0.10.5/matomo/ adaptateurPDO\MYSQL/ système MariaDB, super utilisateur, site « Site GSB », fuseau Paris, pas e-commerce. - Après l’installeur :
chmod 640 /var/www/matomo/config/config.ini.php # il contient le mot de passe de la base
sudo -u www-data php /var/www/matomo/console config:set --section=General --key=enable_browser_archiving_triggering --value=0
sudo -u www-data php /var/www/matomo/console config:set --section=General --key=browser_archiving_disabled_enforce --value=1
sudo -u www-data php /var/www/matomo/console plugin:deactivate AIAgents # (et AIProviders) : elles peuvent envoyer des données à un service extérieur
Les rapports se calculent alors chaque heure par une tâche planifiée (core:archive, matomo-archivage.timer) sur le seul
serveur actif, et non à chaque visite.
Piège Galera : vérifier que les tables sont en InnoDB et ont une clé primaire (c’est le cas pour les 34 tables de Matomo) ; sinon la recopie entre nœuds échoue.
3.3 Nom, certificat, HAProxy, DNS
| Où | Quoi |
|---|---|
srv-ha | certificat auto-signé dédié stats.pem (comme GLPI), acl est_stats hdr(host),field(1,:) -i stats.sio.internal, use_backend matomo if est_stats |
pool matomo | option httpchk sur /matomo.php (expect status 200), server web-01 … check, server web-02 … check backup |
| pfSense et annuaire | stats → 10.0.10.5 : deux endroits (serveurs : Host Override de pfSense ; postes du domaine : zone de l’annuaire) |
3.4 Le secours (srv-web-02)
Une clé SSH dédiée et confinée (rrsync -wo /var/www/matomo dans authorized_keys), un 3ᵉ bloc lsyncd (délai 30 s,
connexion SSH gardée ouverte, tmp/ exclu), le même site Apache. Ne pas oublier la tâche d’archivage : sur le serveur actif seulement.
Validation — srv-web-01 : systemctl stop apache2 pendant 10 s : Matomo, le site et GLPI répondent toujours (servis par
srv-web-02) ; un fichier créé sur le serveur actif arrive sur le secours en moins d’une minute.
4 · Régler Matomo selon la CNIL
Administration → Confidentialité → Conformité → règle CNIL → Appliquer tous les réglages (confirmation par mot de passe).
| Réglage obtenu | Valeur |
|---|---|
| Adresse IP | masquée (2 octets au minimum : 81.255.74.21 devient 81.255.0.0), avant tout enregistrement |
| Données détaillées | conservées 180 jours, puis chiffres globaux seulement |
| Coupés d’office | journal des visites, profils de visiteurs, identifiant utilisateur, cartes de chaleur, enregistrement de session, e-commerce, modèle d’appareil, résolution d’écran |
| Géolocalisation | base locale (le pays seulement, à partir de l’adresse déjà tronquée) : aucun appel extérieur |
L’écran affiche « conforme » partout, sauf « Désinscription : inconnu » : Matomo ne peut pas vérifier tout seul qu’une page d’opposition existe. C’est l’objet du § 5.
5 · Brancher le site : code de suivi, mentions, case d’opposition
Une extension maison de WordPress (wp-content/mu-plugins/gsb-lab.php, recopiée par lsyncd) fait cinq choses :
- un bandeau « site fictif » sur chaque page ;
- le code de suivi Matomo, seulement pour les visiteurs non connectés (ni l’intranet ni l’administration ne sont mesurés), appelé par
/stats/matomo.jset/stats/matomo.phpsur l’adresse du site ; - les commentaires fermés (rien à saisir pour un visiteur) ;
- un code court
[gsb_opposition_matomo]: une case à décocher qui appelleoptUserOut(); - la réécriture à l’envoi des adresses internes écrites en dur dans les images.
La page « Mentions légales et confidentialité » (page de confidentialité de WordPress) donne : qui est l’éditeur (« un étudiant en BTS SIO »), ce qui est mesuré et pour combien de temps, les journaux techniques de sécurité (14 jours), et la case d’opposition. Le site n’est pas indexé par les moteurs de recherche (Réglages → Lecture).
Côté HAProxy, deux fichiers seulement sont relayés vers Matomo, tout autre /stats/… est refusé :
acl suivi_matomo path /stats/matomo.js /stats/matomo.php
http-request deny deny_status 404 if { path_beg /stats/ } !suivi_matomo !est_stats
http-request set-var(txn.suivi_matomo) bool(true) if suivi_matomo !est_stats !est_glpi !est_glpi_pangolin
http-request set-header Host stats.sio.internal if { var(txn.suivi_matomo) -m bool }
http-request set-path %[path,regsub(^/stats/,/)] if { var(txn.suivi_matomo) -m bool }
use_backend matomo if { var(txn.suivi_matomo) -m bool }
6 · Mettre le site en ligne (Pangolin, WordPress, HAProxy)
6.1 WordPress doit répondre sous deux noms
Son adresse était écrite en dur (https://www.sio.internal). Une simple redirection de nom par Pangolin aurait renvoyé les
visiteurs vers une adresse qu’ils ne peuvent pas joindre (les 19 liens de la page d’accueil). Dans wp-config.php :
$gsb_noms = array('www.sio.internal', 'gsb.pangolin.<domaine>');
$gsb_hote = strtolower($_SERVER['HTTP_HOST'] ?? '');
if (!in_array($gsb_hote, $gsb_noms, true)) { $gsb_hote = 'www.sio.internal'; }
define('WP_HOME', 'https://' . $gsb_hote);
define('WP_SITEURL', 'https://' . $gsb_hote);
Et ServerAlias gsb.pangolin.<domaine> dans le site Apache de WordPress, sur les deux serveurs : sinon un nom inconnu tombe
sur le premier site par ordre alphabétique (ici GLPI).
6.2 Règles de HAProxy pour le nom public
| Demande | Résultat |
|---|---|
pages publiques, /stats/matomo.js | 200 |
/wp-admin, /wp-login.php, /xmlrpc.php, /wp-json, /intranet, /author, ?author=, ?rest_route=, readme.html | 403 |
| tout sauf GET et HEAD (hors suivi Matomo) | 405 |
| plus de 100 requêtes en 10 s d’un même visiteur | 429 (table de suivi stick-table) |
Pour le nom interne, rien ne change : le lab garde l’accès à l’administration.
6.3 La ressource Pangolin
Créée par l’administrateur : cible https / nom interne du site / port 443, non protégée (c’est un site public).
Contrôle de santé : stratégie TCP, hôte sans barre oblique finale, port 443 (voir le piège H-95 : un hôte
nom/ et le port 80 rendaient la cible « malade » et le site affichait no available server).
7 · La vraie adresse du visiteur
Sans elle, tout apparaît venir d’une même machine : pas de pays dans Matomo, limite de débit globale, attaques non attribuables.
Méthode de recherche : relever l’en-tête à chaque étape en envoyant exprès une fausse adresse ; l’étape qui la remplace
est la coupable. Ici, Nginx Proxy Manager voyait la vraie adresse, Traefik l’écrasait (X-Forwarded-For et X-Real-IP).
Correction : un en-tête que Traefik ne touche pas.
- Nginx Proxy Manager, hôte
*.pangolin.<domaine>, onglet Advanced :
more_set_input_headers "X-Client-Real-IP: $remote_addr";
Un
proxy_set_headerau même endroit ne marche pas : nginx n’hérite pas desproxy_set_headerdu niveau « site » dès que la section interne d’un hôte en contient (celle de Nginx Proxy Manager en contient).more_set_input_headersmodifie la requête avant l’envoi et remplace toute valeur envoyée par un visiteur : il ne peut pas falsifier son adresse.
- HAProxy :
http-request set-header X-Forwarded-For %[req.hdr(X-Client-Real-IP)] if est_public { req.hdr(X-Client-Real-IP) -m found }et la limite de débit compte surreq.hdr_ip(X-Client-Real-IP). - Matomo :
proxy_client_headers[] = HTTP_X_FORWARDED_FORetproxy_ips[] = 10.0.30.12(le bastion, ajouté en fin de liste, est ignoré). - Apache : module
remoteip(RemoteIPHeader X-Forwarded-For, proxys de confiance10.0.10.5et10.0.30.12) : les journaux montrent l’adresse réelle.
8 · Surveiller le site public
| Besoin | Réglage |
|---|---|
| Panne | sondes Prometheus site-public (par Internet : toute la chaîne) et matomo ; tableau de production à 11 services ; règle critique « Service injoignable » (générique sur probe_success) |
| Sauvegarde | base matomo dans l’export SQL nocturne (BASES="wp_lab grafana glpi matomo"), puis sauvegarde de la VM par PBS |
| Attaques web | agent Wazuh sur chaque serveur web, journaux wordpress-access.log et wordpress-error.log (<localfile> de format apache) |
| Retour arrière en un clic | désactiver la ressource dans Pangolin coupe tout |
9 · Vérifier depuis Internet
H=https://gsb.pangolin.<domaine>
for u in / /laboratoire/ /contact/ /politique-de-confidentialite/ /stats/matomo.js; do curl -s -o /dev/null -w "%{http_code} $u\n" $H$u; done # 200
for u in /wp-admin/ /wp-login.php /xmlrpc.php /author/x/ /intranet/; do curl -s -o /dev/null -w "%{http_code} $u\n" $H$u; done # 403
curl -s -o /dev/null -w "%{http_code}\n" -X POST $H/ # 405
curl -s -o /dev/null -H "X-Client-Real-IP: 203.0.113.9" $H/ # la fausse adresse doit être remplacée (relever l'en-tête côté HAProxy)
Attendu : les codes ci-dessus ; une visite envoyée à /stats/matomo.php apparaît dans Matomo avec une adresse masquée et le
pays ; trois requêtes d’attaque types (?id=1' UNION SELECT …, ?q=<script>…, /wp-config.php.bak) déclenchent dans Wazuh les règles
31103, 31105 et 31516 avec l’adresse de l’appelant.
10 · Pièges et checklist
| Piège | Symptôme | Où c’est documenté |
|---|---|---|
Contrôle de santé Pangolin mal réglé (hôte nom/, port 80) | no available server alors que la cible répond | H-95 |
| Traefik écrase les en-têtes d’adresse | Matomo sans pays, attaques non attribuables | H-96 |
proxy_set_header ignoré au niveau du site (nginx) | la fausse adresse du test passe telle quelle | H-96 |
| Agent invité bloqué par une commande qui attend | QEMU guest agent is not running | H-93 |
| Nom inconnu servi par le premier site d’Apache | la page de GLPI apparaît sous le nom du site | étape 72.3 |
| Nom de l’éditeur laissé dans les pages | nom personnel sur un site public | étape 72.4 |
base et compte matomo (D-15), mot de passe dans Bitwarden
archive vérifiée par empreinte ; config.ini.php en 640 ; extensions IA coupées ; archivage planifié sur le serveur actif seulement
règle CNIL appliquée ; page de mentions et case d'opposition en ligne ; code de suivi pour les non connectés seulement
secours copié (lsyncd, clé confinée) et bascule testée
WordPress à deux noms ; règles HAProxy du nom public ; contrôle de santé Pangolin en TCP
vraie adresse du visiteur lue dans Matomo, HAProxy et les journaux Apache
sondes, tableau, export SQL, agents Wazuh ; test d'attaque détecté
le nom de l'éditeur n'apparaît nulle part sur le site public
La règle à retenir en une phrase : mesurer l’essentiel, tronquer l’adresse avant de l’écrire, laisser le visiteur refuser, et ne mettre en ligne que ce qu’on sait surveiller.
