Aller au contenu

Proxy first-party (anti-adblock)

Les domaines d’analytics connus finissent dans EasyPrivacy : 25 à 40 % des visiteurs tech sont invisibles sans proxy. La parade : servir le script et poster les événements depuis le domaine du site suivi, qui reverse-proxie vers Elvn. Quasi imblocable sans casser le site.

Deux réécritures, avec des noms neutres (/stats/… par convention — n’importe quel path neutre convient, éviter track, analytics, collect) :

GET /stats/t.js -> https://elvn.live/js/t.js
POST /stats/e -> https://elvn.live/api/e

Snippet correspondant (le dashboard le génère dans Settings → Tracking, onglet « proxy ») :

<script defer src="/stats/t.js" data-site="SITE_PUBLIC_ID" data-api="/stats/e"></script>

Deux exigences côté proxy :

  1. Transmettre l’en-tête Origin de la requête entrante (comportement par défaut de tous les proxys ci-dessous) : le tracker envoie u en path relatif, le Worker valide alors l’origine via ce header contre le domaine déclaré du site.
  2. Transmettre le User-Agent entrant (défaut partout) : il sert au hash visiteur et aux dimensions navigateur/OS/appareil.

Le Worker de collecte lit l’IP de connexion et la géolocalisation Cloudflare de la requête qui lui parvient. Derrière un proxy serveur, c’est l’IP du serveur du site :

  • les visiteurs uniques sont dégradés (tous les visiteurs partagent l’IP du proxy : ils ne sont plus distingués que par leur User-Agent) ;
  • la géolocalisation affichée est celle du serveur, pas du visiteur ;
  • le rate limiting (par site + IP) s’applique à l’IP du proxy, donc à tout le trafic proxifié du site.

Aucun en-tête X-Forwarded-For n’est lu (il serait falsifiable sans mécanisme de confiance). Le proxy first-party est donc un compromis : plus de couverture, moins de précision uniques/geo. La variante « Worker route sur la zone du site » a exactement la même limite, sauf pour un relais de confiance.

Un relais Worker peut transmettre l’IP, le pays, la région, la ville et le réseau (ASN) du visiteur dans des en-têtes X-Elvn-Client-*, accompagnés d’une clé partagée X-Elvn-Relay-Key fournie par Elvn. Sans clé valide, ces en-têtes sont ignorés : personne ne peut s’en servir pour usurper une IP. La clé se demande à l’équipe Elvn ; elle reste côté serveur, jamais dans le navigateur.

location = /stats/t.js {
proxy_pass https://elvn.live/js/t.js;
proxy_set_header Host elvn.live;
proxy_ssl_server_name on;
proxy_ssl_name elvn.live;
# cache local optionnel : le script est déjà servi avec max-age=86400 + ETag
}
location = /stats/e {
proxy_pass https://elvn.live/api/e;
proxy_set_header Host elvn.live;
proxy_ssl_server_name on;
proxy_ssl_name elvn.live;
}

Origin et User-Agent sont transmis par défaut (nginx ne réécrit que Host et Connection).

next.config.js (ou .mjs/.ts) :

/** @type {import('next').NextConfig} */
const nextConfig = {
async rewrites() {
return [
{ source: "/stats/t.js", destination: "https://elvn.live/js/t.js" },
{ source: "/stats/e", destination: "https://elvn.live/api/e" },
];
},
};
module.exports = nextConfig;

Puis le snippet proxy dans le layout :

<Script defer src="/stats/t.js" data-site="SITE_PUBLIC_ID" data-api="/stats/e" />

(ou une balise <script> classique dans app/layout.tsx / _document.tsx.)

Variante Worker route (site déjà sur une zone Cloudflare)

Section intitulée « Variante Worker route (site déjà sur une zone Cloudflare) »

Un petit Worker déployé sur la zone du site suivi, avec la route www.exemple.fr/stats/* :

// stats-proxy — Worker sur la zone du site suivi
export default {
async fetch(request) {
const url = new URL(request.url);
if (request.method === "GET" && url.pathname === "/stats/t.js") {
return fetch("https://elvn.live/js/t.js", {
headers: { "User-Agent": request.headers.get("User-Agent") ?? "" },
cf: { cacheTtl: 86400, cacheEverything: true },
});
}
if (request.method === "POST" && url.pathname === "/stats/e") {
// Transmet le body et les en-têtes entrants (Origin, User-Agent, Sec-GPC…)
return fetch("https://elvn.live/api/e", {
method: "POST",
headers: request.headers,
body: request.body,
});
}
return new Response(null, { status: 404 });
},
};

wrangler.jsonc du proxy :

{
"name": "stats-proxy",
"main": "src/index.js",
"compatibility_date": "2026-07-01",
"routes": [{ "pattern": "www.exemple.fr/stats/*", "zone_name": "exemple.fr" }],
}
exemple.fr {
handle /stats/t.js {
rewrite * /js/t.js
reverse_proxy https://elvn.live {
header_up Host elvn.live
}
}
handle /stats/e {
rewrite * /api/e
reverse_proxy https://elvn.live {
header_up Host elvn.live
}
}
# ... reste du site
}
Fenêtre de terminal
# le script doit revenir en 200 avec un ETag
curl -sI https://www.exemple.fr/stats/t.js | head -5
# l'endpoint doit répondre 202 (même pour un payload vide : réponse silencieuse par design)
curl -s -o /dev/null -w "%{http_code}\n" -X POST https://www.exemple.fr/stats/e --data '{}'

Puis visiter le site : la page Settings → Tracking du dashboard passe au vert à la réception du premier événement.