Start free

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:

HTML
<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 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
# 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 / 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
# 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)

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

Report links stay uncounted through a proxy too: clicks from your quietstats reports through to your site are never tracked.