Own-domain script
Serve the quietstats script and collector from your own domain, so browsers and ad blockers treat them as part of your site. Same script, still no cookies and nothing stored about visitors.
How it works
Your site forwards two addresses to quietstats: /qs/script.js (the script) and /qs/api/event (where visits are sent). You can use any path, or a subdomain such as stats.example.com. Then you point your tag at your own address:
<script data-host="https://example.com/qs" data-dnt="true" src="https://example.com/qs/script.js" id="YOUR_SITE_ID" async defer></script>The script reads data-host and sends visits to data-host + /api/event, so nothing in the script changes (it stays 845 bytes).
Pick your host
Cloudflare (Worker route)
// Cloudflare Worker. Add a route for example.com/qs/* (Workers Routes) and paste this in.
export default {
async fetch(request) {
const url = new URL(request.url);
const path = url.pathname.slice("/qs".length);
const target = { '/script.js': '/js/script.js', '/events.js': '/js/events.js', '/api/event': '/api/event' }[path];
if (!target) return new Response('Not found', { status: 404 });
const headers = new Headers(request.headers);
headers.delete('cookie'); // your site's cookies never leave your site
headers.set('x-qs-proxy', 'cloudflare');
headers.set('x-qs-client-ip', request.headers.get('cf-connecting-ip') || '');
if (request.cf && request.cf.country) headers.set('x-qs-country', request.cf.country);
if (request.cf && request.cf.region) headers.set('x-qs-region', request.cf.region);
const r = await fetch('https://quietstats.co.uk' + target, {
method: request.method, headers,
body: request.method === 'POST' ? request.body : undefined,
cf: path === '/api/event' ? undefined : { cacheTtl: 3600, cacheEverything: true },
});
const out = new Response(r.body, r);
out.headers.delete('set-cookie');
return out;
},
};Netlify
# netlify.toml (or the two lines in _redirects without the headers)
[[redirects]]
from = "/qs/script.js"
to = "https://quietstats.co.uk/js/script.js"
status = 200
force = true
[[redirects]]
from = "/qs/api/event"
to = "https://quietstats.co.uk/api/event"
status = 200
force = true
headers = { X-QS-Proxy = "netlify" }Vercel / Next.js
// vercel.json (Next.js: the same two entries in rewrites() in next.config.js)
{
"rewrites": [
{ "source": "/qs/script.js", "destination": "https://quietstats.co.uk/js/script.js" },
{ "source": "/qs/api/event", "destination": "https://quietstats.co.uk/api/event" }
]
}nginx
# nginx: inside your server { } block
location = /qs/script.js {
proxy_pass https://quietstats.co.uk/js/script.js;
proxy_set_header Host quietstats.co.uk;
proxy_ssl_server_name on;
proxy_set_header Cookie "";
proxy_hide_header Set-Cookie;
}
location = /qs/api/event {
proxy_pass https://quietstats.co.uk/api/event;
proxy_set_header Host quietstats.co.uk;
proxy_ssl_server_name on;
proxy_set_header Cookie "";
proxy_hide_header Set-Cookie;
proxy_set_header X-QS-Proxy nginx;
proxy_set_header X-QS-Client-IP $remote_addr; # behind Cloudflare: $http_cf_connecting_ip
proxy_set_header X-QS-Country $http_cf_ipcountry; # if your site is behind Cloudflare; else leave out
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}WordPress (mu-plugin)
<?php
/*
* Plugin Name: quietstats own-domain proxy
* Description: Serves the quietstats script and collector from /qs/ on this site. Save as
* wp-content/mu-plugins/quietstats-proxy.php (no settings, no cookies).
*/
add_action('init', function () {
$path = parse_url($_SERVER['REQUEST_URI'] ?? '', PHP_URL_PATH);
if ($path === '/qs/script.js') {
$js = get_transient('quietstats_script');
if ($js === false) {
$r = wp_remote_get('https://quietstats.co.uk/js/script.js', array('timeout' => 5));
if (is_wp_error($r) || wp_remote_retrieve_response_code($r) !== 200) { status_header(502); exit; }
$js = wp_remote_retrieve_body($r);
set_transient('quietstats_script', $js, HOUR_IN_SECONDS);
}
header('Content-Type: application/javascript; charset=utf-8');
header('Cache-Control: public, max-age=3600');
echo $js; exit;
}
if ($path === '/qs/api/event' && $_SERVER['REQUEST_METHOD'] === 'POST') {
$h = array(
'Content-Type' => 'text/plain',
'User-Agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
'Origin' => $_SERVER['HTTP_ORIGIN'] ?? home_url(),
'X-QS-Proxy' => 'wordpress',
'X-QS-Client-IP' => $_SERVER['HTTP_CF_CONNECTING_IP'] ?? $_SERVER['REMOTE_ADDR'] ?? '',
);
if (!empty($_SERVER['HTTP_CF_IPCOUNTRY'])) $h['X-QS-Country'] = $_SERVER['HTTP_CF_IPCOUNTRY'];
if (!empty($_SERVER['HTTP_DNT'])) $h['DNT'] = $_SERVER['HTTP_DNT'];
if (!empty($_SERVER['HTTP_SEC_GPC'])) $h['Sec-GPC'] = $_SERVER['HTTP_SEC_GPC'];
$r = wp_remote_post('https://quietstats.co.uk/api/event', array('headers' => $h, 'body' => file_get_contents('php://input'), 'timeout' => 3));
if (is_wp_error($r)) { status_header(202); exit; }
status_header(wp_remote_retrieve_response_code($r));
foreach (array('content-type', 'x-quietstats', 'x-qs-via', 'x-qs-country') as $k) {
$v = wp_remote_retrieve_header($r, $k);
if ($v !== '') header($k . ': ' . $v);
}
echo wp_remote_retrieve_body($r); exit;
}
});Check it
In Settings → Own-domain script, pick the site, enter your proxy address and press Check my setup. It checks that the script comes from your domain without a cookie, that a test visit reaches quietstats (nothing is recorded), and that the visitor’s address and country are forwarded.
Visitor address and country
- Through a proxy, quietstats would otherwise see your server’s address for every visitor, so all visitors would look like one. The configs forward the visitor’s address in
X-QS-Client-IP(orX-Forwarded-For). quietstats only accepts a valid public address, skips private and Cloudflare addresses, and uses it the same way as always: inside the daily-changing visitor hash, never stored. - The country comes from
X-QS-Country(Cloudflare, or nginx behind Cloudflare) or Vercel’s country header. If your host doesn’t send one, those visits show the country as Unknown rather than your server’s country. - Your site’s own cookies are stripped by the configs, and quietstats ignores cookies anyway. Do Not Track and Global Privacy Control headers pass through and are still honoured.
Report links stay uncounted through a proxy too: clicks from your quietstats reports through to your site are never tracked.