Anyone who wants to operate web tracking in compliance with data protection and GDPR regulations can't avoid Matomo. As an open-source alternative to Google Analytics, Matomo offers full control over user data – especially with self-hosting. However, especially when used in data protection-sensitive environments, an additional trick is recommended: the use of a Matomo proxy.
Readtime: 3 min, 38 s
What is a Matomo proxy?
A Matomo proxy acts as a kind of "intermediary" between the website visitor's browser and the actual Matomo server. Instead of loading the tracking script (usually matomo.js) and the tracking endpoint (matomo.php) directly from the Matomo server, they are delivered via the website's own web server. Technically, this is a simple redirection or mirroring.
Why should you use a Matomo proxy?
1. Improve data protection:
The proxy makes all tracking traffic appear as if it came from your own web server. This prevents connections to third-party domains and reduces the risk of, for example, ad blockers or browser settings blocking tracking. This is particularly relevant for the cookie-free configuration, which Matomo enables in compliance with GDPR without consent.
2. Bypass ad blockers:
Many ad blockers detect the domain of the Matomo server and automatically block tracking requests. However, if the script and requests run through the main domain (e.g., example.com/matomo-proxy/matomo.js), detection is significantly more difficult.
3. Optimize performance:
Static files like matomo.js can be delivered via the proxy with appropriate cache headers. This can improve page loading times—a benefit, especially when there are many visitors.
4. Fallback / Caching (Optional)
In the event of a failure, malfunction or maintenance of the effective Matomo server, the data can be temporarily stored on the web server and later replayed.
How to set up a Matomo proxy:
Matomo itself offers an official proxy script on GitHub.
The setup is simple:
- Download the repository and copy the files (e.g.,
piwik.php,matomo.js, .htaccess) to a subdirectory of your website, e.g.,example.com/matomo-proxy/.
2 . If necessary, adjust the proxy.config.php and specify the URL of your Matomo server.
3 . Change the tracking code on your website to load the script via the proxy:
var _paq = window._paq || [];
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="//trackedsite.com/";
_paq.push(["setTrackerUrl", u+"matomo.php"]);
_paq.push(["setSiteId", "tracked-site-id-here"]);
var d=document, g=d.createElement("script"), s=d.getElementsByTagName("script")[0];
g.type="text/javascript"; g.async=true; g.defer=true; g.src=u+"matomo.php"; s.parentNode.insertBefore(g,s);
})();
Optional: Fallback
This part must be inserted manually into the script proxy.php, at approximately line 148:
// Tracking Fallback to SQLite
forwardToMatomoWithFallback($httpStatus, $_GET, $extraQueryParams, $user_agent);
At the end of the script proxy.php add the following part:
function forwardToMatomoWithFallback($httpCode, $httpGet, $extraQueryParams, $userAgent ) {
if (str_contains($httpCode, 204) || str_contains($httpCode, 200)) {
echo "Status code 200 or 204 found, skip processing.";
} else {
$db = new SQLite3(__DIR__ . '/matomo-failed.db');
$db->busyTimeout(5000);
$db->exec("CREATE TABLE IF NOT EXISTS failed_requests (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp INTEGER,
query TEXT,
user_agent TEXT,
accept_language TEXT
)");
$query = http_build_query(array_merge($extraQueryParams, $httpGet));
$acceptLang = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? 'unknown';
// Exclude requests to CoreAdminHome matomo-opt-out
if (!str_contains($query, 'module=CoreAdminHome')) {
// Save SQLite-Fallback
$stmt = $db->prepare("INSERT INTO failed_requests (timestamp, query, user_agent, accept_language)
VALUES (:ts, :query, :ua, :lang)");
$stmt->bindValue(':ts', time(), SQLITE3_INTEGER);
$stmt->bindValue(':query', $query, SQLITE3_TEXT);
$stmt->bindValue(':ua', $userAgent, SQLITE3_TEXT);
$stmt->bindValue(':lang', $acceptLang, SQLITE3_TEXT);
$stmt->execute();
$stmt->close();
}
}
}
Thus the connection data is written into a sqlite.
With this script replay.php the data in the collected sqlite can be sent and imported to the Matomo server via the interface.
<?php
if (file_exists(__DIR__.'/config.php')) {
include __DIR__.'/config.php';
}
date_default_timezone_set('UTC');
$db = new SQLite3(__DIR__.'/matomo-fallback.db');
$rows = $db->query("SELECT * FROM failed_requests ORDER BY timestamp ASC");
while ($row = $rows->fetchArray(SQLITE3_ASSOC)) {
$url = $MATOMO_URL . 'matomo.php?' . $row['query'];
$cdt = date('Y-m-d H:i:s', $row['timestamp']); // Convert timestamp to human-readable format for cdt
$url .= '&cdt=' . urlencode($cdt);
$opts = [
'http' => [
'header' =>
"User-Agent: {$row['user_agent']}\r\n" .
"Accept-Language: {$row['accept_language']}\r\n"
]
];
$context = stream_context_create($opts);
$res = @file_get_contents($url, false, $context);
if ($res !== false) {
$db->exec("DELETE FROM failed_requests WHERE id = " . (int)$row['id']);
}
}
The timestamp is transferred to Matomo using the
cdt. TheUser-AgentandAccept-Languageare also important so that an existing user can add them as an update.
Fazit
A Matomo proxy is quick to set up and offers several advantages: better data protection, ad blocker bypass, and increased performance, as well as the option to implement a fallback. For anyone who uses Matomo seriously—whether for small blogs or large portals—using a proxy is a useful addition.