Matomo raus, Umami rein: sechs Jahre Web-Analytik in einer Nacht umgebaut
Matomo hat auf einer unserer Maschinen sechs Jahre lang jeden Besuch mitgezählt. Am 21. Oktober 2026 war Schluss, die Container sind ausgestoppt, der Port lauscht nicht mehr. Ersetzt haben wir sie durch eine Umami-Instanz, die zwei Marken unserer Gruppe bedient. Der Anlass war keine Unzufriedenheit über die Auswertung, sondern die Frage, wie viel Angriffsfläche wir öffentlich betreiben wollen.
Was Matomo zum Schluss gemessen hatte
Vor dem Abschalten haben wir die Zahlen gezählt, damit später niemand raten muss. Letzter Stand der Datenbank: 136.672 Besuche, 266.883 einzeln erfasste Aktionen, 47.981 verschiedene Seitentitel und Adressen, 5.529 Conversionen. Der erste Besuch stammte vom 25. Oktober 2020, der letzte vom 21. Oktober 2026. Vier Anlagen waren angelegt, zwei davon haben jemals sinnvoll gezählt.
Die ehrliche Pointe an diesen Zahlen: Eine der vier Anlagen hatte nie einen Zählcode im Template. Ihre 4.152 Besuche waren Anfragen von Bots und Zählproben, kein Verkehr. Wer eine alte Statistik überlebt, sollte vor dem Trauern nachprüfen, was sie überhaupt erfasst hat. Wir hätten diese Anlage sechs Jahre für Daten gehalten, die es nicht gab.
Warum ersetzt und nicht angehoben
Matomo ist GPL-3.0 lizenziert, 21.966 Sterne auf GitHub am Tag unserer Messung, ein ausgereiftes Produkt. Nur bringt die Distribution einen Apache mit mod_php mit, und diese Kombination stand bei uns öffentlich im Netz. Wir hatten am selben Tag gegen eine PHP-Unauth-RCE-Meldung die eigene Flotte gemessen, und die Antwort war unangenehm: Die letzte öffentlich erreichbare mod_php-Installation im Haus war genau diese Analytik. Einen Tracker betreibt man nicht mit dem Hinweis, die Patches mögen schneller kommen, wenn die Anwendung selbst das Risiko ist.
Umami ist MIT lizenziert, 39.513 Sterne am selben Tag, eine Next.js-Anwendung mit PostgreSQL dahinter. Im ausgelieferten Bild steckt kein PHP-Interpreter. Das ist kein Qualitätsurteil über Matomo. Es ist eine Entscheidung über die Betriebsart. Ein Tracker soll klein, langweilig und ohne Runtime-Dienste im öffentlichen Pfad laufen.
Der ausgelieferte Zählcode ist die härteste Währung in dieser Frage, also haben wir beide Dateien nachgemessen. Matomos matomo.js war 68.253 Bytes groß, das Umami-script.js sind 4.733 Bytes, unkomprimiert ausgeliefert. Das alte Skript hat Cookies und Gerätespeicher beschrieben, im neuen steht kein einziger setItem-Aufruf, und es liest die Do-Not-Track-Vorgabe des Browsers. Genau das ist der Unterschied, den eine Datenschutzerklärung tragen kann.
Was wir nicht migriert haben
Die Historie wandert nicht in ein anderes Zählmodell. Der Abend endete mit einem SQL-Dump von 89.222.354 Bytes komprimiert, 485 Tabellen-Definitionen und 391 Datenblöcke, die Prüfsumme ist protokolliert. Eine zweite Kopie liegt auf einem anderen Rechner, ihre Prüfsumme ist bytegleich verglichen. Mehr Historie wollten wir nicht, denn die Modelle sind verschieden: Matomo zählt Besucher über eine rohe IP-Adresse, Umami führt Sitzungen ohne IP in der Datenbank. Ein Import hätte einen Datensatz nachgebaut, den wir abgeschafft haben.
Dafür sind die Conversion-Ziele verloren gegangen. Matomo hatte acht davon, fünf liefen wirklich: der Besuch der Kontaktseite, ein Klick auf die Telefonnummer, einer auf die E-Mail-Adresse, das Absenden des Kontaktformulars und die Produktanfrage. Umami kennt URL-Filter und benannte Ereignisse, also müssen wir diese fünf nachbauen. Die Klickmessungen selbst haben wir schon von Matomo auf benannte Ereignisse umgeschrieben, die Auswertung in der Maske fehlt noch.
Der 403, der keiner war
Nach dem Umzug funktionierten zwei Dinge nicht: In der Statistik landete nur noch ein Bruchteil der Besuche, und der Zähl-Endpunkt /api/send antwortete auf manchen Verkehr mit 403. Zwei Vermutungen hielten sich lange, beide waren falsch. Eine dritte Behauptung über eine Filterliste mussten wir ausdrücklich zurücknehmen, weil es diese Variable im ausgelieferten Bild nicht gibt.
Der wahre Grund sass in einer Härtungsvariable, die wir selbst gesetzt hatten. Der Mechanismus prüft den ersten Eintrag einer Forwarded-for-Angabe gegen eine Liste von Adressen, die er ablehnt. Der Verkehr einer internen Anwendung, der über die eigene Firewall in dasselbe Netz zurückläuft, erscheint dort mit der Adresse dieser Firewall. Wir haben das mit sechs Anfragen durchgemessen: fremde Adresse an erster Stelle, 200; unsere eigene Adresse an erster Stelle, 403; gar keine Angabe, 403. Die Gegenprobe lief mit, sonst wäre das Urteil nichts wert.
Die Korrektur war eine Liste interner Bereiche, und zwar mit Bedacht geschnitten. Aus dem /24 des Providers wurde ein /27, damit nicht versehentlich ein ganzes ISP-Netz von der Zählung verschwindet. Die Geo-Angaben zeigen seither wieder Brauchbares: Die Testanfrage von einer fremden Maschine in einem deutschen Rechenzentrum landete mit Land und Region in der Sitzungstabelle.
Ein Rest bleibt stehen und steht hier öffentlich: Der erste Eintrag einer Forwarded-for-Angabe ist schreibbar. Wer die Länderzuordnung frisieren will, kann das. Personenbezogen ist daran wenig, weil keine IP gespeichert wird, und die Härtung am Rand ist vorbereitet. Den zweiten Neustart des Webservers aber gehört nicht uns.
Aufbewahrung ist ein Job, keine Konfiguration
Unsere Datenschutzerklärung sagt jetzt: Rohdaten 14 Monate. Diese Zeile ist eine Behauptung, solange niemand löscht, und das ausgelieferte Bild bringt keinen Löschjob mit. Also gibt es jetzt ein Skript mit einer einzigen Grenze pro Lauf, das pro Tabelle zuerst zählt und erst dann löscht, mit Sperre gegen Doppel-Läufe. Der Zeitstempel wird einmal pro Lauf als Epoche berechnet, damit Zählung und Löschung dieselbe Bedingung benutzen.
Zwei Fallen stecken in so einem Job. Der Container ist Alpine, der Scheduler rechnet in lokaler Zeit und kennt keine Zeitzone. Die Crontab-Zeile läuft deshalb zweimal pro Nacht, und ein stundenbasiertes Tor im Skript sorgt dafür, dass Sommer- und Winterzeit auf denselben UTC-Moment treffen. Die zweite Falle war mein eigener Test: Eine Prozessesuche mit exaktem Namen übersieht den Scheduler dieses Systems, obwohl er läuft. Den Nachweis führen Prozessliste und Dienststatus.
Gemessen haben wir die Aufbewahrung mit einer gealterten Kontrollzeile, die wir selbst gesetzt haben: eine Sitzung und ein Ereignis auf Januar 2025. Nach dem Lauf waren beide weg, die acht jungen Sessions desselben Tages blieben unverändert. Der wirklich geplante Nachttick steht noch aus, er fällt in der Nacht zum 22. Oktober um 05:15 Ortszeit, das sind 03:15 UTC.
Was übrig bleibt
Offen sind die fünf Conversion-Ziele in der neuen Maske, ein zweiter Blick auf die Logistik-Website, die bisher gar keinen Zählcode trägt, und die Umschreibung einer Auswertungsplattform, die noch auf die alte Matomo-Schnittstelle zeigt. Zwei Zugänge zur Oberfläche brauchen einen eigenen Betrachter, bevor die Zweifaktor-Pflicht global eingeschaltet werden kann, sonst sperren wir uns selbst aus. Und die Datenbank-Zugangsdaten der stillgelegten Anwendung drehen wir noch, weil sie im Klartext in einer Konfiguration standen.
Was bleibt, ist eine kleinere, langweiligere Anlage mit gemessenem Verhalten: 4.733 Bytes Zählcode, keine Cookies, kein PHP im öffentlichen Pfad, eine Datenbank ohne IP-Spalte und ein Job, der nachweislich löscht. Für eine Anwendung, die niemandem gehören muss, ist das ein guter Ort zum Parken.
Weiterführende Quellen
- Umami, Projektseite mit den Angaben zu Cookies, personenbezogenen Daten und Self-Hosting
- Umami-Installation und Ereignis-API in der offiziellen Dokumentation
- Umami auf GitHub, MIT-Lizenz, und Matomo auf GitHub, GPL-3.0
- Wie wir Browser-Anteile aus unserer eigenen Umami-Datenbank gemessen haben
- Warum ein Backup erst mit der zweiten Kopie zählt
Braucht Web-Analytik ohne Cookie überhaupt eine Einwilligung?+
Die Einwilligung ist nicht die schwierige Frage, der Speicherzugriff ist es. Unser Zustimmungsbanner steuert weiterhin einen Script-Slot: Wer ablehnt, bekommt weder das Skript noch eine Anfrage an den Zähler. Die Datenschutzerklärung haben wir von Matomo auf Umami umgeschrieben, inklusive Aufbewahrungsfrist.
Warum haben wir die alten Matomo-Zahlen nicht übernommen?+
Weil die Zählmodelle nicht zusammenpassen. Matomo führt Besucher über eine rohe IP-Adresse, Umami führt Sitzungen ohne IP in der Datenbank. Ein Import würde einen Datensatz nachbauen, den wir bewusst abgeschafft haben. Sechs Jahre bleiben deshalb als geprüfter SQL-Dump erhalten.
Läuft Umami bei uns damit ohne Personenbezug?+
In der Datenbank steht keine IP-Adresse, Land, Region und Stadt entstehen nur in der Sitzungstabelle aus der Anfrage. Was wir nicht belegen können, ist die Restwahrscheinlichkeit einer Re-Identifikation über Sitzungsmuster. Die Aufbewahrung ist auf 14 Monate begrenzt, und der Löschjob ist mit einer gealterten Kontrollzeile gemessen.
senn-tech