• Blog
  • Bio

Eric Boumendil

Router certaines URL hors VPN avec un script PAC et px

24 juin 2026 // soho kubernetes

J’utilise une connexion VPN sortante depuis mon pare-feu routeur (pfSense): tous les périphériques qui se connectent dans mon réseau local passent donc par ce VPN de façon indiscriminée (ou presque). Je le fais parce que ça embête un peu les trackers. Ceux qui utilisent la localisation de l’adresse IP m’affichent leur publicité (quand elle n’est pas simplement bloquée) en allemand ou en néerlandais, en fonction du VPN utilisé (il y en a plusieurs). C’est particulièrement utile sur certains sites où le contenu est mélangé à de la publicité, le changement de langue le rend immédiatement visible (je ne parle ni allemand, ni néerlandais).

Certains sites ne fonctionnent pas bien cependant, pour des raisons plus ou moins légitimes. Comme c’était assez rare jusqu’à maintenant, je me débrouillais en bricolant, avec diverses astuces. Cela devient de plus en plus un besoin récurrent et je teste une nouvelle solution: px et un script PAC (les deux combinés ou juste le script PAC selon le cas d’utilisation).

Ce qu’il est possible de faire dans pfSense

Dans pfSense, il est possible de créer des règles de pare-feu pour que certaines connexions ne passent pas par la gateway par défaut (sur laquelle le VPN est configuré), mais ces règles sont assez bas niveau: c’est principalement en fonction de l’IP de destination (après résolution DNS), du port réseau, de l’IP source (l’appareil qui envoie la requête)… Pour une configuration qui dépend de l’URL avant la résolution DNS, c’est plus compliqué et plus fragile, donc peu pratique si on doit y toucher régulièrement.

La solution: squid, un script PAC, px

L’idée est d’installer un proxy sur une machine identifiable par son IP auprès de pfSense. Dans mon cas, j’utilise un Raspberry Pi 4. Le proxy est installé dessus, et pfSense a une règle qui indique que toute connexion qui vient de l’IP de ce Raspberry Pi 4 ne passe pas par le VPN, mais sort directement sur la box Internet.

Pour le proxy, on peut utiliser squid. Je ne vais pas parler davantage de squid car ce n’est pas l’objet de l’article. Le site web propose également un wiki très complet. Je considère qu’on a déjà un proxy opérationnel, cet article s’attache principalement au script PAC et à l’outil px. Le rôle de squid est uniquement de faire rebond sur une machine (le Raspberry Pi 4), ce qui permet de créer une règle très simple de routage sur pfSense (pour sortir par la box Internet directement au lieu de passer par la route par défaut qui est le VPN).

On peut ensuite configurer ce proxy dans son navigateur ou via les variables d’environnement http_proxy et https_proxy, mais l’inconvénient est que toutes les connexions vont passer par le proxy, ce qui n’est pas ce qu’on souhaite.

PAC est un fichier JavaScript avec une fonction FindProxyForURL en point d’entrée, qui doit retourner une chaîne “DIRECT” ou “PROXY x.x.x.x:yyyy” selon que la connexion à utiliser pour l’URL doit être directe ou par proxy.

Exemple d’un tel script:

function FindProxyForURL(url, host) {
  // Override proxy for a selection of URLs (to bypass router's proxy)

  if (shExpMatch(host, "*.boursobank.com") || host == "boursobank.com")
    return "PROXY 192.168.0.10:3128";

  // DIRECT = no client-side proxy
  return "DIRECT";
}

Dans l’exemple ci-dessus, toute requête à https://boursobank.com ou un de ses sous-domaines passera par le proxy 192.168.0.10:3128. Évidemment, cela suppose d’avoir un proxy prêt à prendre en charge les requêtes à cette adresse.

Firefox permet d’utiliser un tel script dans sa configuration (actuellement dans Vie privée et sécurité, Sécurité logicielle et des connexions, Configurer le proxy, Adresse de configuration automatique du proxy). Il faut cependant le servir en HTTP/S, il faut donc un serveur local pour cela. Plus bas dans l’article, on préférera modifier les paramètres proxy de Windows et laisser le navigateur utiliser ces derniers, ça évitera de devoir configurer plusieurs navigateurs, voire d’autres applications qui s’appuient sur les paramètres réseau de Windows le cas échéant.

Noter qu’il est théoriquement possible de filtrer par URL (c’est bien l’argument passé à la fonction), mais que son respect va dépendre du cas d’utilisation: Firefox et je suppose la plupart des navigateurs retiennent la décision par domaine (et probablement par port). À moins de tester plusieurs URL sous le même domaine dans des sessions de navigation privées indépendantes, le résultat obtenu ne sera probablement pas le bon: le navigateur n’évalue pas strictement le script pour chaque URL (en tout cas Firefox).

Voici un diagramme Mermaid qui récapitule le dispositif:

flowchart TD
  AppPac["Navigateur ou application avec script PAC"]
  AppEnv["Application avec http_proxy / https_proxy"]
  AppWin["Application avec paramètres proxy Windows"]
  Px["px local<br>127.0.0.1:3128"]
  Pac["Script PAC<br>FindProxyForURL"]
  Squid["squid<br>Raspberry Pi 4<br>192.168.0.10:3128"]
  PfSense["pfSense"]
  Vpn["VPN<br>route par defaut"]
  Box["Box Internet<br>sortie directe"]
  Internet["Internet"]

  AppPac --> Pac
  AppEnv --> Px
  AppWin --> Px
  Px --> Pac

  Pac -->|DIRECT| PfSense
  Pac -->|PROXY 192.168.0.10:3128| Squid
  Squid --> PfSense

  PfSense -->|Client standard| Vpn
  Vpn --> Internet
  PfSense -->|IP source = Raspberry Pi| Box
  Box --> Internet

Servir un script PAC avec un serveur nginx

Voici des exemples de manifestes Kubernetes pour un déploiement nginx et le script PAC sous la forme d’un ConfigMap.

Cette partie est un exemple d’hébergement, n’importe quel serveur HTTP peut convenir.

apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-static-pac
data:
  proxy.pac: |
    function FindProxyForURL(url, host) {
        
        // Override proxy for a selection of URLs (to bypass router's proxy)

        if (shExpMatch(host, "*.boursobank.com") || host == "boursobank.com")
            return "PROXY 192.168.0.10:3128";
        
        // DIRECT = no client-side proxy
        return "DIRECT";
    }
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-static-config
data:
  default.conf: |
    server {
      listen 80;
      server_name _;

      location = /proxy.pac {
        alias /usr/share/nginx/html/proxy.pac;
        default_type application/x-ns-proxy-autoconfig;
        add_header Cache-Control "max-age=300, must-revalidate";
      }
    }
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-static
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-static
  template:
    metadata:
      labels:
        app: nginx-static
    spec:
      containers:
        - name: nginx
          image: nginx:1.31.2-alpine-slim
          ports:
            - containerPort: 80
              name: http
          volumeMounts:
            - name: pac-files
              mountPath: /usr/share/nginx/html
            - name: nginx-config
              mountPath: /etc/nginx/conf.d
      volumes:
        - name: pac-files
          configMap:
            name: nginx-static-pac
        - name: nginx-config
          configMap:
            name: nginx-static-config
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-static-svc
spec:
  selector:
    app: nginx-static
  ports:
    - name: http
      port: 80
      targetPort: http
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: nginx-static-ingress-https
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`static.my-domain.com`)
      kind: Rule
      services:
        - name: nginx-static-svc
          port: 80
          scheme: http
  tls:
    certResolver: acme

Une fois déployé, notre script PAC est disponible en téléchargement sur l’URL https://static.my-domain.com/proxy.pac. J’utilise HTTPS avec ma configuration Traefik et Let’s Encrypt mais une URL en HTTP devrait pouvoir fonctionner aussi bien pour servir ce script PAC.

Maintenant, si on configure cette URL dans Firefox, on devrait voir qu’il est utilisé. Pour le tester, on peut ajouter des sites témoins au script PAC, par exemple deux sites qui permettent d’obtenir notre adresse IP publique, l’un avec une connexion directe, l’autre avec le proxy. En testant dans Firefox, on devrait voir respectivement l’adresse IP du VPN et notre adresse IP publique de box internet. La moindre erreur dans le script JavaScript rendra la configuration inopérante (= connexion directe en fallback).

On peut également configurer le script PAC non pas dans chaque navigateur, mais dans les paramètres réseau et Internet de Windows: Paramètres, Réseau et Internet, Proxy, Utiliser un script de configuration. Le script sera utilisé par toutes les applications qui utilisent les paramètres proxy de Windows.

En revanche, certaines applications ignorent les paramètres proxy de Windows, et ne permettent pas non plus de configurer un script PAC. En dernier recours, on peut espérer qu’elles supportent les variables d’environnement http_proxy et https_proxy qui sont devenues une forme de standard de facto.

px

Comme on ne peut pas mettre un script PAC directement dans les variables d’environnement http_proxy et https_proxy, l’alternative est de faire tourner un (autre) proxy de rebond local qui supporte les scripts PAC. C’est le cas de px.

Un autre avantage intéressant de px est qu’il supporte un fichier PAC local, donc le serveur nginx peut être évité. Dans mon cas, je préfère malgré tout le conserver, car il reste utile pour centraliser la configuration des proxies sur plusieurs périphériques, dont l’iPhone.

Pour l’installation, voir la documentation.

Dans mon cas, je l’ai installé via winget install genotrance.px.

Une fois installé, on peut créer un fichier de configuration “px.ini” (le nom importe peu, car on passera son chemin en ligne de commande):

[proxy]
pac = https://static.my-domain.com/proxy.pac
port = 3128
listen = 127.0.0.1
gateway = 0
hostonly = 1
noproxy = localhost,127.0.0.1

[settings]
foreground = 0
log = 4

Si le proxy squid requiert une authentification, on peut ajouter les paramètres username = <USER> et auth = BASIC sous la section [proxy]. Attention, il faudra sauver le mot de passe dans le gestionnaire de Windows en lançant une première fois la commande de px avec l’option supplémentaire --password pour avoir un prompt dans lequel saisir le mot de passe. J’ai fini par supprimer l’authentification à squid car ce n’est pas géré par toutes les applications. En gros, c’est principalement bien géré par les navigateurs et par tout ce qui passe par px. En revanche, sur iPhone notamment, si on configure le script PAC comme décrit plus bas, l’authentification pourra gêner le fonctionnement de certaines applications.

Le paramètre settings.log = 4 sert à afficher les logs dans la sortie console de px, ce qui est bien utile pour confirmer le bon fonctionnement, mais on pourra ensuite mettre la valeur 0 pour les désactiver.

Pour le lancer (adapter le chemin complet vers le fichier px.ini créé précédemment):

px --config=px.ini

Dans les paramètres proxy de Windows, on peut remplacer la configuration PAC par un proxy simple: localhost, port 3128. Ne pas oublier de mettre dans le champ texte qui gère les exclusions le domaine qui sert le script PAC pour éviter une boucle (dans mon exemple: static.my-domain.com). On pourra aussi ajouter tout ce qu’on mettra dans la variable no_proxy (voir plus bas).

Peu de temps après, on doit voir pas mal de logs s’afficher dans la sortie console de px.

Lancement de px à l’ouverture de session Windows

Pour démarrer px automatiquement avec Windows:

px --config=px.ini --install

Cela crée une clé dans le registre Windows à l’emplacement HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run: à l’ouverture de session Windows, la commande px --config=px.ini sera lancée.

Pour retirer cette clé, utiliser l’option --uninstall.

Noter que l’installation ne fait que créer la clé, mais ne lance pas px. Il faut donc le relancer si on ne souhaite pas rouvrir sa session pour déclencher ce lancement.

La commande indiquée va ouvrir une fenêtre console. Si on préfère le mode arrière-plan, il faut remplacer px par pxw. Si on veut stopper pxw qui tourne en arrière-plan, la commande est pxw --quit. Le mode arrière-plan est celui à privilégier au jour le jour, mais pour des tests, il peut être utile de suivre la sortie console en lançant l’outil en premier-plan.

Variables http_proxy, https_proxy et no_proxy

Maintenant que px est opérationnel, on peut définir ces trois variables d’environnement de manière globale pour l’utilisateur avec ces valeurs:

http_proxy=http://localhost:3128
https_proxy=http://localhost:3128
no_proxy=localhost,127.0.0.1,host.docker.internal,gateway.docker.internal,kubernetes.docker.internal

Ainsi, toutes les applications qui respectent ces variables passeront également par px quand le domaine (ou le sous-domaine) ne fait pas partie des domaines listés dans no_proxy.

Cela n’est jamais une garantie, mais la plupart des outils en ligne de commande respectent cette convention. Pour tester le bon fonctionnement, il y a les logs côté px (via sa sortie console si on le lance manuellement), les logs côté squid (sudo tail -f /var/log/squid/access.log), ou éventuellement le test de résolution de son adresse IP publique si elle diffère selon le chemin de la requête (ce qui est le cas pour un VPN sortant).

Ces variables ne sont pas un standard bien défini, donc il peut être utile de prendre connaissance de l’article mentionné plus haut: We need to talk: Can we standardize NO_PROXY? car il y a des choses à savoir. Très succinctement, préférer la version minuscules à la version majuscules (http_proxy et non HTTP_PROXY), des domaines simples dans no_proxy (éviter les wildcard sauf pour des tests spécifiques car leur interprétation varie).

Pour la valeur de no_proxy, je me suis basé sur mon fichier hosts sur Windows. Dans cet article j’ai laissé les domaines liés à Docker car assez répandus sur un poste de développeur, mais j’ai omis certains domaines plus spécifiques à mon installation, que j’utilise bien dans cette variable. En cas de mise à jour du fichier hosts sur le poste sur lequel px tourne, il peut être préférable d’adapter la variable no_proxy en conséquence.

Configurer le script PAC sur iPhone

Sur iPhone, on peut configurer l’URL du script PAC dans les réglages du réseau Wi-Fi.

Je suppose que sur Android, il existe une configuration équivalente, mais je n’ai pas testé.

Prise en compte des changements dans le script

L’exemple plus haut dans la configuration nginx indique aux clients un délai d’expiration de 5 minutes au-delà duquel ils doivent revalider le script.

Je ne sais pas dans quelle mesure ce délai est respecté par les différents clients (il me semble probable que cela varie d’un système à l’autre), mais dans le pire des cas, si la prise en compte rapide est très importante, il devrait suffire soit de redémarrer la machine (ou l’iPhone), soit de modifier l’URL du script en ajoutant une version en paramètre d’URL, par exemple ?v=2 pour invalider la précédente version.

Sur Windows, si on définit les paramètres proxy sur px (http://localhost:3128), il suffit probablement de relancer px pour forcer un rafraichissement du script PAC. Comme tout passe par px (que ce soit via les paramètres proxy de Windows ou via les variables d’environnement http_proxy), c’est pratique à gérer. Si on préfère configurer le script PAC dans les paramètres de Windows (pour éviter le rebond inutile par px), je suppose qu’on peut modifier légèrement l’URL du script comme indiqué avant.

La longueur de l’article peut laisser penser que c’est un peu laborieux, mais c’est en fait relativement simple et pratique à configurer une fois en place. Évidemment, en cas de défaillance du serveur nginx ou de px pour les clients qui passent par lui, il faudra se rappeler la présence de cette couche d’indirection (ça n’apparaîtra pas dans le message d’erreur). J’ai déjà eu besoin de redémarrer px car il était parti dans les choux: pxw --quit, puis pxw --config=px.ini dans un terminal et c’est reparti (en adaptant le chemin vers le fichier de configuration).

← 
OpenTelemetry Collector: OTTL et stanza operators à la rescousse d'un parsing imparfait
 
© 2026 Eric Boumendil
Haut de page