Publie le 19 aout 2026 · Tutoriel PHP

Coder son propre rotator PHP en round-robin equitable

Le code source complet d'un rotator PHP qui distribue le trafic entre plusieurs URLs en tournant equitablement. 30 lignes utiles, aucune dependance, deployable sur n'importe quel hebergement PHP mutualise en 3 etapes. Utilise en production pour absorber du trafic de test et repartir les hits sans favoritisme.

Ce que tu vas trouver ici

Sommaire : Le concept · Le code complet · Explique ligne par ligne · Deploiement · Extensions · Alternative sans code · FAQ

Le concept en une phrase

Un rotator round-robin est un script serveur qui, a chaque requete recue, choisit la prochaine URL dans une liste et redirige le visiteur vers elle en HTTP 302. L'ordre est cyclique : URL 1, URL 2, URL 3, puis retour a la 1. Chaque URL recoit donc exactement la meme part du trafic, sans arbitrage ni favoritisme.

Cas d'usage typiques : repartir du trafic de test entre plusieurs pages, distribuer des visites publicitaires, faire du load balancing simple entre landings, ou executer un point d'entree unique qui masque les URLs finales.

Le code complet

Trois fichiers, tous dans le meme dossier. Le rotator lui-meme fait 30 lignes utiles. Aucune dependance externe, aucune base de donnees, aucun framework.

rotator.php
<?php
// Rotator PHP round-robin equitable
// Chaque requete redirige vers la prochaine URL de la liste, en boucle.

$urls_file    = __DIR__ . '/urls.json';
$pointer_file = __DIR__ . '/pointer.json';
$fallback_url = 'https://example.com/';

// Headers : jamais indexable, jamais cache
header('X-Robots-Tag: noindex, nofollow, noarchive, nosnippet');
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');
header('Referrer-Policy: no-referrer');

// Charge la liste d'URLs
$urls = @json_decode(@file_get_contents($urls_file), true);
if (!is_array($urls) || empty($urls)) {
    header('Location: ' . $fallback_url, true, 302);
    exit;
}

// Lock exclusif sur un fichier sidecar pour eviter les collisions
$lock = fopen($pointer_file . '.lock', 'cb');
flock($lock, LOCK_EX);

// Lit le pointeur actuel, calcule le suivant, sauvegarde
$ptr  = @json_decode(@file_get_contents($pointer_file), true) ?: ['idx' => -1];
$next = (((int)$ptr['idx']) + 1) % count($urls);
file_put_contents($pointer_file, json_encode(['idx' => $next]));

flock($lock, LOCK_UN);
fclose($lock);

// Cible + forwarding du query string
$target = $urls[$next];
$qs     = $_SERVER['QUERY_STRING'] ?? '';
if ($qs !== '') {
    $sep    = (strpos($target, '?') === false) ? '?' : '&';
    $target .= $sep . $qs;
}

header('Location: ' . $target, true, 302);
exit;
urls.json
[
  "https://exemple.com/page-1",
  "https://exemple.com/page-2",
  "https://exemple.com/page-3"
]
pointer.json
{"idx": -1}
Note : le -1 initial est volontaire. Le premier hit calcule (-1 + 1) % N = 0, il tape donc bien la premiere URL de la liste. Sans cela, la premiere URL serait sautee au demarrage.

Explique ligne par ligne

Les headers de securite (lignes 8-11)

X-Robots-Tag: noindex empeche Google (et les autres) d'indexer le rotator. Cache-Control: no-store empeche les CDN et navigateurs de mettre en cache la redirection : sinon le meme visiteur retomberait toujours sur la meme URL. Referrer-Policy: no-referrer masque l'origine cote cible.

Le chargement de la liste (lignes 14-18)

On lit urls.json, on decode. Si le fichier est absent ou vide, on redirige vers un fallback (par defaut la home d'exemple). C'est le filet de securite : le service ne tombe jamais en erreur 500.

Le verrou fichier (lignes 21-22)

flock(LOCK_EX) pose un verrou exclusif sur un fichier sidecar (pointer.json.lock). Si deux requetes arrivent en meme temps, la deuxieme attend que la premiere ait fini d'ecrire le pointeur. Sans ce verrou, on aurait des collisions et deux visiteurs pourraient recevoir la meme URL au lieu de deux differentes.

Le calcul du prochain index (lignes 25-27)

((int)$ptr['idx'] + 1) % count($urls) : on incremente et on prend le modulo pour boucler. Si la liste a 5 URLs et que l'index actuel est 4, le suivant est (4+1) % 5 = 0, retour a la premiere.

Le forwarding du query string (lignes 34-38)

Si l'URL entrante avait des parametres (?utm_source=email&campaign=aout), on les concatene a l'URL cible. Le separateur est ? si l'URL n'en avait pas, & sinon. Utile pour transmettre les UTM ou les identifiants de campagne.

La redirection finale (lignes 40-41)

Un simple Location: HTTP 302. Le 302 (temporaire) est prefere au 301 (permanent) car les navigateurs et proxies ne le mettent pas en cache aussi agressivement, ce qui garantit que chaque hit passe bien par le rotator.

Deploiement en 3 etapes

  1. Uploade les 3 fichiers (rotator.php, urls.json, pointer.json) dans un sous-dossier de ton hebergement, par exemple /rotator/. Assure-toi que les fichiers JSON sont en permissions 644 et le dossier en 755.
  2. Modifie urls.json avec tes vraies URLs (une par ligne, entre guillemets, avec virgule apres chaque sauf la derniere).
  3. Modifie $fallback_url en tete de rotator.php vers ta home ou une page qui a du sens comme fallback.

Ton rotator est actif a l'URL https://ton-domaine.tld/rotator/rotator.php. Pour raccourcir en /rotator/, renomme rotator.php en index.php.

Test rapide : ouvre l'URL du rotator dans plusieurs onglets ou avec curl -I. Chaque hit doit renvoyer un Location: different qui suit l'ordre de ta liste, puis boucler.

Extensions possibles

Compteurs par URL

Ajoute un fichier counters.json qui stocke le nombre de hits par URL. Avant le header('Location: ...'), incremente le compteur correspondant. Meme pattern de verrou fichier.

Quotas et desactivation

Chaque URL a un quota max par jour. Filtre la liste avant round-robin en excluant celles qui ont atteint le plafond. Reset du compteur a minuit.

Fallback intelligent

Si toutes les URLs sont down (test de reachability), passe automatiquement au fallback. Un simple curl -I --max-time 2 avant redirection suffit.

Multi-liste avec categories

Au lieu d'un seul urls.json, plusieurs listes (urls-fr.json, urls-en.json, etc.) selectionnees selon un parametre ?list=fr dans l'URL entrante.

Passage a SQLite pour du volume

Au-dela de 100 hits/seconde, le lock fichier devient un goulot. Bascule sur SQLite avec une transaction : les lectures et ecritures sont bien plus rapides et supportent la concurrence sans verrou explicite.

L'alternative si tu ne veux rien coder

Si tu ne veux pas hebergsr le rotator toi-meme, gerer les fichiers JSON et deployer sur ton FTP, mon service farm.julienctr.fr fait exactement ce que fait ce code, avec en plus :

Acces sur invitation par email a contact@julienctr.fr.

Decouvrir farm.julienctr.fr

Questions frequentes

Qu'est-ce qu'un rotator round-robin ?
Un script serveur qui, a chaque requete recue, choisit la prochaine URL cible dans une liste et redirige le visiteur en HTTP 302. Round-robin signifie que la selection tourne dans l'ordre, puis boucle. Distribution equitable garantie.
Pourquoi PHP plutot que Node.js ou Python ?
Un rotator est un script minuscule (30 lignes utiles) et PHP tourne nativement sur 99 pour cent des hebergements mutualises sans configuration. Node.js ou Python demandent un runtime, un supervisor et un reverse proxy. Pour un rotator, PHP est le plus simple.
Le code fonctionne-t-il en mutualise o2switch ou OVH ?
Oui, sans modification. Aucune extension exotique : file_get_contents, json_decode, flock, header sont dans le noyau PHP depuis la version 5.0. Tourne sur o2switch, OVH, Hostinger, Infomaniak, PlanetHoster ou n'importe quel mutualise PHP 7 et plus.
Comment gerer plusieurs milliers de hits par jour ?
En dessous de 100 hits par seconde, le lock fichier tient sans probleme. Au-dela, bascule sur SQLite avec transaction, ou Redis. Pour 10 000 a 100 000 hits par jour, la version fichier decrite ici suffit largement.
Le rotator est-il indexable par Google ?
Non. Le code envoie X-Robots-Tag noindex nofollow et Cache-Control no-store. Google voit la 302 mais ne l'indexe pas et ne suit pas le lien vers la cible.
Puis-je transferer les query strings a la cible ?
Oui, le code inclut un bloc qui recupere le QUERY_STRING de la requete entrante et le concatene a l'URL cible avec le bon separateur. UTM et parametres de campagne sont preserves.
Y a-t-il un service pret a l'emploi ?
Oui, farm.julienctr.fr fait exactement cela, avec interface web, compteurs, historique et repartition entre membres. Acces sur invitation par email a contact@julienctr.fr.
Julien CTR
Julien CTR
Passionne de black hat SEO · @CTRBooster75

Je publie mes tests et methodes SEO black hat sur ce site et sur X. Pour une mission ou un audit : consultant black hat SEO.