Fransys

Blog technique — Architecture, Cloud & DevOps

BlogServicesContactÀ propos

Suivez-moi

githubGitHublinkedinLinkedinmailMail

© 2026 Fransys • Fransys

Fransys

Catégories

  • Tous les articles
  • Tags
  • productivite10
  • nas10
  • ia8
  • securite7
  • linux6
  • claude-code6
  • auto-hebergement6
  • neovim5
  • docker5
  • editeur4
  • mcp3
  • vpn3
  • reseau3
  • lua2
  • terminal2
vpnsecuritereseauauto-hebergementheadscale

VPN souverain : monter son propre serveur avec Headscale en Suisse

Publié le
5 mars 2026·22 min de lecture
Avatar François GUERLEZFrançois GUERLEZ

Pourquoi auto-héberger son VPN

Dans la première partie, on a vu les solutions rapides pour contourner la censure : changer de DNS, utiliser un VPN commercial, Tor. Elles fonctionnent, mais elles ont toutes le même défaut : tu dépends d'un tiers.

Proton VPN peut décider de changer ses conditions. Cloudflare peut couper WARP dans certaines régions. Les IP des VPN commerciaux sont connues et listées — un gouvernement motivé peut les bloquer une par une.

La solution souveraine : ton propre serveur VPN, sur une machine que tu contrôles, dans un pays dont tu choisis la juridiction. Personne ne peut couper ton accès sans couper la connexion à ton serveur spécifiquement. Et pour ça, il faut connaître ton IP — ce qui est nettement plus compliqué que de bloquer les plages d'adresses de Proton ou NordVPN.

Pourquoi Headscale et pas WireGuard brut

WireGuard est le protocole VPN le plus performant et le plus simple qui existe. Mais le configurer manuellement sur chaque appareil (génération de clés, échange de clés publiques, configuration des peers) devient vite pénible quand on a plus de deux machines.

Tailscale résout ce problème en ajoutant une couche de coordination au-dessus de WireGuard : gestion des clés automatique, NAT traversal, découverte des peers. Mais Tailscale est un service cloud — tes métadonnées de connexion passent par leurs serveurs.

Headscale est une implémentation open-source et auto-hébergée du serveur de coordination Tailscale. Tu gardes tout ce qui fait la force de Tailscale (facilité, WireGuard, NAT traversal) mais le serveur de coordination tourne chez toi. Tes appareils utilisent le client Tailscale officiel — seul le serveur change.

WireGuard brutTailscaleHeadscale
ProtocoleWireGuardWireGuardWireGuard
Gestion des clésManuelleAutomatiqueAutomatique
NAT traversalNonOui (STUN/DERP)Oui (STUN/DERP)
Serveur de coordinationAucunTailscale cloudAuto-hébergé
CoûtGratuitGratuit (3 users) / payantGratuit
SouverainetéTotaleLimitéeTotale

Pourquoi la Suisse

La Suisse n'est pas dans l'UE. Elle n'est pas membre des 14 Eyes. Sa législation sur la protection des données (nLPD) est parmi les plus strictes au monde. Et géographiquement, c'est à ~10ms de latence de la France ou l'Allemagne — imperceptible au quotidien.

Pour l'hébergement, j'ai choisi Infomaniak : hébergeur suisse, datacenters à Genève alimentés à 100% en énergie renouvelable, soumis exclusivement au droit suisse. Un VPS Cloud à ~5€/mois suffit largement.

Prérequis

  • Un VPS sous Debian 12 ou 13 (1 vCPU, 1 Go RAM suffisent)
  • Un nom de domaine (on utilisera hs.example.io pour Headscale)
  • Un accès SSH au VPS
  • Les ports 80, 443 (TCP) et 3478 (UDP) ouverts dans le pare-feu du VPS
  • nginx (installé à l'étape 3 pour le routage SNI sur le port 443)

Étape 1 : installer Headscale

On installe la dernière version stable depuis le dépôt officiel :

# Ajouter le dépôt Headscale
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://pkgs.headscale.net/stable/debian/pubkey.asc | sudo gpg --dearmor -o /etc/apt/keyrings/headscale.gpg
echo "deb [signed-by=/etc/apt/keyrings/headscale.gpg] https://pkgs.headscale.net/stable/debian bookworm main" | sudo tee /etc/apt/sources.list.d/headscale.list

# Installer
sudo apt update
sudo apt install -y headscale

Vérifie l'installation :

headscale version
# headscale version v0.28.0

Étape 2 : DNS

Crée un enregistrement DNS de type A pointant vers l'IP de ton VPS :

hs.example.io → A → 203.0.113.x

Headscale va utiliser ce domaine pour :

  • Son API de coordination (les clients Tailscale s'y connectent)
  • Le certificat TLS (Let's Encrypt)
  • Le serveur DERP relay intégré

Étape 3 : configurer Headscale

Le fichier de configuration principal est /etc/headscale/config.yaml. Voici la config complète commentée :

# URL publique du serveur — ce que les clients utilisent pour se connecter
# Les clients se connectent via nginx sur le port 443
server_url: https://hs.example.io:443
# Headscale écoute en local — nginx route le trafic vers lui via SNI
listen_addr: 127.0.0.1:4443
metrics_listen_addr: 127.0.0.1:9090
grpc_listen_addr: 127.0.0.1:50443
grpc_allow_insecure: false

# Clé privée Noise (générée automatiquement au premier lancement)
noise:
  private_key_path: /var/lib/headscale/noise_private.key

# Plages d'adresses du réseau Tailscale
prefixes:
  v4: 100.64.0.0/10
  v6: fd7a:115c:a1e0::/48
  allocation: sequential

# Serveur DERP intégré — relais pour les connexions
# qui ne peuvent pas établir de lien direct
derp:
  server:
    enabled: true
    region_id: 999
    region_code: 'myrelay'
    region_name: 'My DERP CH'
    verify_clients: true
    stun_listen_addr: '0.0.0.0:3478'
    private_key_path: /var/lib/headscale/derp_server_private.key
    automatically_add_embedded_derp_region: true
    # IMPORTANT : mets l'IP publique de ton VPS ici
    ipv4: 203.0.113.x
  urls:
    # On garde les DERP publics de Tailscale en fallback
    - https://controlplane.tailscale.com/derpmap/default
  paths: []
  auto_update_enabled: true
  update_frequency: 3h

disable_check_updates: true
ephemeral_node_inactivity_timeout: 30m

# Base de données locale SQLite
database:
  type: sqlite
  sqlite:
    path: /var/lib/headscale/db.sqlite
    write_ahead_log: true

# Certificat TLS automatique via Let's Encrypt
acme_url: https://acme-v02.api.letsencrypt.org/directory
acme_email: contact@example.io
tls_letsencrypt_hostname: hs.example.io
tls_letsencrypt_cache_dir: /var/lib/headscale/cache
tls_letsencrypt_challenge_type: HTTP-01
tls_letsencrypt_listen: ':http'

log:
  level: info
  format: text

# Politique d'accès (ACL)
policy:
  mode: file
  path: /etc/headscale/acl.json

# DNS interne du réseau Tailscale
dns:
  magic_dns: true
  base_domain: tail.example.io
  override_local_dns: true
  nameservers:
    global:
      - 1.1.1.1
      - 1.0.0.1

unix_socket: /var/run/headscale/headscale.sock
unix_socket_permission: '0770'

# Pas d'envoi de télémétrie à Tailscale
logtail:
  enabled: false

randomize_client_port: false

Points importants

  • listen_addr: 127.0.0.1:4443 : Headscale écoute en local uniquement. C'est nginx qui expose le port 443 et route le trafic vers Headscale via SNI (voir étape 3bis). Ça permet de partager le port 443 avec d'autres services (Xray).
  • server_url: https://hs.example.io:443 : les clients se connectent toujours sur le port 443 standard — c'est nginx qui dispatche.
  • tls_letsencrypt_challenge_type: HTTP-01 : le challenge Let's Encrypt utilise le port 80. Headscale écoute sur :http pour ça.
  • derp.server.ipv4 : mets l'IP publique réelle de ton VPS. Sans ça, les clients ne trouveront pas le relais DERP.
  • logtail.enabled: false : on ne veut rien envoyer aux serveurs Tailscale.

Étape 4 : configurer les ACL

Les ACL (Access Control Lists) définissent qui peut parler à qui dans le réseau. Crée /etc/headscale/acl.json :

{
  "tagOwners": {
    "tag:exit": ["mon-user@"]
  },
  "autoApprovers": {
    "exitNode": ["tag:exit"]
  },
  "acls": [
    {
      "action": "accept",
      "src": ["*"],
      "dst": ["*:*"]
    }
  ]
}

L'important ici c'est autoApprovers.exitNode : ça permet aux nœuds tagués tag:exit de s'annoncer automatiquement comme exit nodes. Sans ça, il faudrait approuver manuellement chaque exit node.

Étape 4bis : nginx SNI routing sur le port 443

On veut faire cohabiter Headscale et Xray (qu'on installera plus tard) sur le même port 443. On utilise le module stream de nginx pour router le trafic TCP en fonction du SNI (Server Name Indication) — le nom de domaine envoyé par le client dans le handshake TLS.

# Installer nginx avec le module stream
sudo apt install -y nginx libnginx-mod-stream

Crée le fichier de configuration SNI :

sudo tee /etc/nginx/modules-enabled/90-stream-sni.conf << 'EOF'
stream {
    map $ssl_preread_server_name $backend {
        hs.example.io   headscale;
        default         xray;
    }

    upstream headscale {
        server 127.0.0.1:4443;
    }

    upstream xray {
        server 127.0.0.1:8443;
    }

    server {
        listen 443;
        listen [::]:443;
        proxy_pass $backend;
        ssl_preread on;
        proxy_protocol off;
    }
}
EOF

Le principe :

  • Tout le trafic arrive sur nginx :443
  • nginx lit le SNI (sans déchiffrer le TLS)
  • Si le SNI est hs.example.io → route vers Headscale sur :4443
  • Sinon (tout le reste) → route vers Xray sur :8443
# Vérifier la config et redémarrer
sudo nginx -t && sudo systemctl restart nginx

Important : nginx ne fait aucun déchiffrement TLS. C'est un proxy TCP pur. Headscale gère son propre TLS (Let's Encrypt), et Xray gère le sien (Reality). nginx ne fait que lire le SNI en clair dans le ClientHello et router en conséquence.

Étape 5 : activer l'IP forwarding

Pour que le VPS puisse router le trafic des autres appareils (fonctionner comme exit node), il faut activer le forwarding IP :

cat << EOF | sudo tee /etc/sysctl.d/99-tailscale.conf
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
EOF

sudo sysctl -p /etc/sysctl.d/99-tailscale.conf

Étape 6 : créer un utilisateur et démarrer

# Démarrer Headscale
sudo systemctl enable --now headscale

# Créer un utilisateur
sudo headscale users create mon-user

# Vérifier que le service tourne
sudo systemctl status headscale

Au premier démarrage, Headscale génère les clés cryptographiques et obtient le certificat Let's Encrypt. Vérifie que https://hs.example.io répond (tu devrais voir une page de Headscale).

Étape 7 : connecter le VPS comme exit node

Le VPS lui-même doit rejoindre le réseau Tailscale et s'annoncer comme exit node :

# Installer le client Tailscale
curl -fsSL https://tailscale.com/install.sh | sh

# Trouver l'ID numérique de ton utilisateur
sudo headscale users list
# → ID: 1, Name: mon-user

# Générer une clé d'authentification avec le tag exit
# Le flag --user prend l'ID numérique (pas le nom)
# Le flag --tags assigne le nœud à un user spécial "tagged-devices"
sudo headscale preauthkeys create \
  --user 1 \
  --reusable \
  --expiration 24h \
  --tags tag:exit

# La commande affiche la clé, note-la

# Connecter le VPS à son propre Headscale
sudo tailscale up \
  --login-server https://hs.example.io:443 \
  --authkey MA_CLE_PREAUTHKEY \
  --hostname vps-ch \
  --advertise-exit-node

# Vérifier
sudo tailscale status

Le VPS est maintenant :

  1. Le serveur de coordination Headscale (gestion du réseau)
  2. Un nœud du réseau Tailscale (comme les autres appareils)
  3. Un exit node (capable de router tout le trafic vers internet)

Étape 8 : connecter ses appareils

Linux

# Installer Tailscale
curl -fsSL https://tailscale.com/install.sh | sh

# Se connecter au serveur Headscale
sudo tailscale up --login-server https://hs.example.io:443

# Tailscale affiche une URL d'enregistrement
# → copie-la et enregistre le nœud côté serveur :
# --user prend l'ID numérique (pas le nom)
sudo headscale nodes register --key nodekey:abc123... --user 1

Windows

  1. Télécharge Tailscale depuis tailscale.com/download/windows
  2. Ouvre un terminal en administrateur :
# Déconnecter du cloud Tailscale si nécessaire
tailscale logout

# Se connecter à ton Headscale
tailscale up --login-server https://hs.example.io:443
  1. Copie l'URL affichée et enregistre le nœud côté serveur.

Android

  1. Installe Tailscale depuis le Play Store
  2. Dans l'app, va dans le menu ⋮ → Use an alternate server
  3. Entre l'URL : https://hs.example.io:443
  4. L'app ouvre un navigateur avec une URL d'enregistrement
  5. Côté serveur : sudo headscale nodes register --key nodekey:abc123... --user 1

Vérification

Depuis le serveur, liste tous les nœuds :

sudo headscale nodes list

Tu devrais voir tous tes appareils avec leur IP Tailscale (100.64.0.x) et leur statut online.

Étape 9 : activer le routage via la Suisse

Maintenant que tout le monde est connecté, on active l'exit node pour router tout le trafic via le VPS suisse.

Linux

sudo tailscale set --exit-node=vps-ch --exit-node-allow-lan-access

--exit-node-allow-lan-access permet de garder l'accès au réseau local (imprimante, NAS, etc.) même quand le trafic internet passe par le VPS.

On utilise tailscale set plutôt que tailscale up pour modifier un paramètre sans réinitialiser les autres (comme --login-server).

Windows

tailscale set --exit-node=vps-ch --exit-node-allow-lan-access

Ou dans l'interface graphique Tailscale : clic sur le menu → Exit Node → sélectionne vps-ch.

Android

Dans l'app Tailscale : menu ⋮ → Use exit node → sélectionne vps-ch.

Vérification

Teste depuis chaque appareil :

curl -s https://ipinfo.io

Tu dois voir l'IP de ton VPS suisse, la ville, le pays (CH) et le nom de ton hébergeur.

Architecture finale

┌──────────────────────────────────────────────────────────┐
│                       Internet                            │
│                          ▲                                │
│                          │                                │
│              ┌───────────┴───────────┐                    │
│              │     VPS Suisse :443    │                    │
│              │                       │                    │
│              │   nginx (SNI router)  │                    │
│              │     ┌─────┴─────┐     │                    │
│              │     │           │     │                    │
│              │  Headscale   Xray     │                    │
│              │   :4443     :8443     │                    │
│              │  (Tailscale) (VLESS)  │                    │
│              └──┬────┬────┬────┬────┘                    │
│     WireGuard   │    │    │    │  VLESS+Reality            │
│   ┌─────────────┘    │    │    └──────────────┐           │
│   │                  │    │                   │           │
│ ┌─┴──────┐  ┌───────┴┐  ┌┴────────┐  ┌──────┴──────┐   │
│ │ Linux  │  │Windows │  │ Android │  │  v2rayN /   │   │
│ │Tailscale│  │Tailscale│  │Tailscale│  │  v2rayNG   │   │
│ │100.64  │  │100.64  │  │100.64  │  │  (censure) │   │
│ │ .0.2   │  │ .0.3   │  │ .0.4   │  │            │   │
│ └────────┘  └────────┘  └────────┘  └────────────┘   │
└──────────────────────────────────────────────────────────┘

Usage quotidien : tout le trafic est chiffré par WireGuard via Tailscale, transite par le VPS en Suisse, et sort sur internet avec l'IP suisse. Le FAI local ne voit qu'une connexion WireGuard vers une IP unique.

En pays censuré : on bascule sur v2rayN/v2rayNG qui se connecte au même VPS via VLESS+Reality. Le FAI ne voit qu'une connexion HTTPS vers www.microsoft.com — impossible à distinguer ou à bloquer.

Option anti-DPI : VLESS+Reality (Xray)

WireGuard a un défaut : son trafic est identifiable par Deep Packet Inspection. Le handshake, la taille des paquets, le format UDP — tout est reconnaissable. En Russie, en Chine, en Iran, WireGuard brut est bloqué. Headscale/Tailscale ne fonctionnera pas dans ces pays.

La solution : ajouter un proxy VLESS+Reality (Xray) sur le même VPS, à côté de Headscale. Contrairement aux VPN obfusqués qui masquent le trafic, VLESS+Reality le fait ressembler à une connexion HTTPS légitime vers un vrai site web (par exemple www.microsoft.com). Le DPI ne peut pas le bloquer sans bloquer l'accès au site imité.

L'idée : utiliser Headscale/Tailscale au quotidien (réseau mesh, exit node, performance), et basculer sur VLESS+Reality uniquement quand on se trouve dans un pays qui bloque les VPN.

Installer Xray sur le VPS

# Installation officielle
sudo bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install

Générer les clés Reality

xray x25519
# → Private key: eE6MDfDF1JliKiDijcPojrOJB4-GsA_ux7InREW7hEg
# → Public key:  _kP9S_vKqSksfj9MXNn0pULtphbzRVuNq5DYYafYpz8

Note les deux clés. La clé privée va dans la config serveur, la clé publique dans la config client.

Génère aussi un UUID pour identifier le client :

xray uuid
# → 8672f031-7aaa-4a63-8835-6cd7f58ea703

Et un short ID (8 à 16 caractères hex) :

openssl rand -hex 8
# → e318e30924f77899

Configurer le serveur Xray

Édite /usr/local/etc/xray/config.json :

{
  "log": {
    "loglevel": "warning",
    "access": "/var/log/xray/access.log",
    "error": "/var/log/xray/error.log"
  },
  "inbounds": [
    {
      "listen": "127.0.0.1",
      "port": 8443,
      "protocol": "vless",
      "settings": {
        "clients": [
          {
            "id": "8672f031-7aaa-4a63-8835-6cd7f58ea703",
            "flow": "xtls-rprx-vision"
          }
        ],
        "decryption": "none"
      },
      "streamSettings": {
        "network": "tcp",
        "security": "reality",
        "realitySettings": {
          "dest": "www.microsoft.com:443",
          "serverNames": ["www.microsoft.com", "microsoft.com"],
          "privateKey": "eE6MDfDF1JliKiDijcPojrOJB4-GsA_ux7InREW7hEg",
          "shortIds": ["e318e30924f77899"]
        }
      },
      "sniffing": {
        "enabled": true,
        "destOverride": ["http", "tls", "quic"]
      }
    }
  ],
  "outbounds": [
    { "protocol": "freedom", "tag": "direct" },
    { "protocol": "blackhole", "tag": "block" }
  ]
}

Points clés :

  • listen: 127.0.0.1, port: 8443 : Xray écoute en local. nginx route le trafic du port 443 vers lui (voir étape 4bis).
  • dest: www.microsoft.com:443 : le site que Reality imite. Quand un client non-VLESS se connecte, il est redirigé vers le vrai microsoft.com — le serveur est indistinguable d'un proxy microsoft.com légitime.
  • privateKey : la clé privée générée avec xray x25519.
  • flow: xtls-rprx-vision : le mode XTLS Vision qui offre les meilleures performances.
# Créer le dossier de logs et démarrer
sudo mkdir -p /var/log/xray
sudo systemctl enable --now xray

Cohabitation avec Headscale sur le port 443

C'est là que l'architecture prend tout son sens. Grâce au routage SNI configuré à l'étape 4bis, nginx dispatche le trafic sur le port 443 :

Internet → nginx (:443)
  ├── SNI: hs.example.io  → Headscale (:4443) → clients Tailscale
  └── SNI: * (default)    → Xray (:8443)      → clients VLESS+Reality

Les clients Tailscale se connectent à hs.example.io:443 et sont routés vers Headscale. Les clients VLESS se connectent à l'IP du VPS sur le port 443 sans SNI spécifique (ou avec www.microsoft.com comme SNI) et sont routés vers Xray. Pour un observateur, tout le trafic sur le port 443 ressemble à du HTTPS normal.

Configurer les clients

Lien de partage VLESS

Le moyen le plus simple de configurer un client est d'utiliser un lien VLESS :

vless://UUID@IP_VPS:443?encryption=none&flow=xtls-rprx-vision&security=reality&sni=www.microsoft.com&fp=chrome&pbk=CLE_PUBLIQUE&sid=SHORT_ID&type=tcp#Mon-VPS-Reality

Ce lien s'importe directement dans v2rayN (Windows), v2rayNG (Android), Streisand (iOS).

Linux (Xray client)

# Installer Xray
sudo bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install

Configurer /usr/local/etc/xray/config.json :

{
  "log": { "loglevel": "warning" },
  "inbounds": [
    {
      "listen": "127.0.0.1",
      "port": 10818,
      "protocol": "socks",
      "settings": { "udp": true },
      "tag": "socks-in"
    },
    {
      "listen": "127.0.0.1",
      "port": 10819,
      "protocol": "http",
      "tag": "http-in"
    }
  ],
  "outbounds": [
    {
      "protocol": "vless",
      "settings": {
        "vnext": [
          {
            "address": "203.0.113.42",
            "port": 443,
            "users": [
              {
                "id": "8672f031-7aaa-4a63-8835-6cd7f58ea703",
                "flow": "xtls-rprx-vision",
                "encryption": "none"
              }
            ]
          }
        ]
      },
      "streamSettings": {
        "network": "tcp",
        "security": "reality",
        "realitySettings": {
          "serverName": "www.microsoft.com",
          "publicKey": "_kP9S_vKqSksfj9MXNn0pULtphbzRVuNq5DYYafYpz8",
          "shortId": "e318e30924f77899",
          "fingerprint": "chrome"
        }
      },
      "tag": "vless-out"
    },
    { "protocol": "freedom", "tag": "direct" }
  ]
}

Le client crée un proxy SOCKS5 local (127.0.0.1:10818) et HTTP (127.0.0.1:10819). Configure ton navigateur ou tes apps pour utiliser ce proxy.

# Tester
curl -x socks5h://127.0.0.1:10818 https://ifconfig.me
# → doit afficher l'IP de ton VPS

Windows (v2rayN)

  1. Télécharge v2rayN (version v2rayN-windows-64.zip)
  2. Extrais dans un dossier (par exemple C:\v2rayN)
  3. Lance v2rayN.exe
  4. Menu → Servers → Import from clipboard → colle le lien VLESS
  5. Sélectionne le serveur → clique sur Start
  6. v2rayN active le proxy système automatiquement

Conflit avec Tailscale : ne lance pas v2rayN et Tailscale (exit node) en même temps. Les deux veulent router tout le trafic et créent une boucle. Utilise Tailscale au quotidien, v2rayN uniquement dans les pays censurés.

Android (v2rayNG)

  1. Installe v2rayNG depuis le Play Store ou GitHub
  2. Scanne le QR code ou importe le lien VLESS
  3. Appuie sur le bouton de connexion

iOS (Streisand)

  1. Installe Streisand depuis l'App Store
  2. Importe le lien VLESS
  3. Connecte-toi

Quand utiliser quoi

SituationSolution
Usage quotidien (France, Allemagne, etc.)Tailscale via Headscale — exit node VPS, réseau mesh
Voyage en Russie, Chine, IranVLESS+Reality via v2rayN/v2rayNG — anti-DPI
Les deux sont disponiblesTailscale par défaut, VLESS en backup

Tu n'as pas besoin de VLESS+Reality au quotidien. Tailscale est plus performant (WireGuard natif, connexions directes entre peers, réseau mesh). VLESS+Reality est l'arme anti-censure — à activer uniquement quand Tailscale est bloqué.

Astuce : ne configure pas v2rayN en démarrage automatique sur Windows. Tu ne veux l'utiliser que ponctuellement. Tailscale, lui, peut rester actif en permanence.

Vérifier que tout est étanche

Un VPN mal configuré peut fuiter ton IP réelle sans que tu le saches. Voici les vérifications à faire systématiquement.

1. IP publique — le test de base

# Doit retourner l'IP de ton VPS, pas celle de ton FAI
curl -s https://ipinfo.io/json | jq '{ip, city, country, org}'

Résultat correct (tu sors par ton VPS suisse) :

{
  "ip": "203.0.113.42",
  "city": "Genève",
  "country": "CH",
  "org": "AS29222 Mon Hébergeur SA"
}

Fuite (c'est ton FAI local qui apparaît) :

{
  "ip": "86.xxx.xxx.xxx",
  "city": "Paris",
  "country": "FR",
  "org": "AS3215 Orange S.A."
}

Si tu vois ton FAI local, l'exit node n'est pas actif (tailscale set --exit-node=vps-ch).

2. Fuite IPv6 — le piège classique

Même avec un exit node actif, le trafic IPv6 peut contourner le tunnel et sortir directement par ton FAI. C'est la fuite la plus courante et la plus discrète.

# Vérifie avec un service qui teste IPv6 en priorité
curl -s https://ipleak.net/json/ | jq '{ip, country_name, isp_name}'

Résultat correct (IPv4 du VPS, pas de fuite IPv6) :

{
  "ip": "203.0.113.42",
  "country_name": "Switzerland",
  "isp_name": "Mon Hébergeur SA"
}

Fuite IPv6 (ton FAI apparaît via ton adresse IPv6 locale) :

{
  "ip": "2a02:xxxx:xxxx:xxxx::1",
  "country_name": "Germany",
  "isp_name": "Vodafone GmbH"
}

Si le résultat montre ton FAI local, tu as une fuite IPv6. Corrige en désactivant IPv6 sur l'interface réseau :

# Identifier l'interface réseau principale
ip route show default
# → default via 192.168.x.x dev eth0 ...

# Désactiver IPv6 sur cette interface
sudo sysctl -w net.ipv6.conf.eth0.disable_ipv6=1

# Rendre permanent
cat << EOF | sudo tee /etc/sysctl.d/99-disable-ipv6.conf
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.eth0.disable_ipv6 = 1
EOF

Remplace eth0 par le nom de ton interface (visible dans la sortie de ip route).

3. Fuite DNS — le FAI voit tes requêtes

Même si ton trafic passe par le VPN, tes requêtes DNS peuvent encore partir vers le résolveur de ton FAI.

# Vérifie quel résolveur DNS est utilisé
# Le résultat doit montrer l'IP de ton VPS ou Cloudflare (1.1.1.1), pas ton FAI
curl -s https://am.i.mullvad.net/json | jq '{ip, country, organization, mullvad_exit_ip}'

Résultat correct (IP du VPS, pas un exit Mullvad) :

{
  "ip": "203.0.113.42",
  "country": "Switzerland",
  "organization": "Mon Hébergeur SA",
  "mullvad_exit_ip": false
}

Headscale configure automatiquement le DNS interne via MagicDNS. Si tu vois quand même le DNS de ton FAI, force le DNS dans la config Headscale (dns.override_local_dns: true dans config.yaml).

4. Triple vérification — croiser les sources

Ne fais jamais confiance à un seul service. Croise au moins trois sources indépendantes :

echo "=== ipinfo.io ==="
curl -s https://ipinfo.io/json | jq '{ip, city, country}'

echo "=== ipleak.net ==="
curl -s https://ipleak.net/json/ | jq '{ip, country_name}'

echo "=== mullvad ==="
curl -s https://am.i.mullvad.net/json | jq '{ip, country}'

Les trois doivent retourner la même IP — celle de ton VPS. Si un service montre une IP différente, tu as une fuite.

=== ipinfo.io ===
203.0.113.42 — Genève, CH (AS29222 Mon Hébergeur SA)
=== ipleak.net ===
203.0.113.42 — Switzerland (Mon Hébergeur SA)
=== mullvad ===
203.0.113.42 — Switzerland (Mon Hébergeur SA)

Trois fois la même IP suisse = aucune fuite.

5. Vérifier VLESS+Reality (Xray)

Vérifie que Xray tourne et que le tunnel fonctionne :

# Sur le VPS — vérifier que Xray est actif
sudo systemctl status xray

# Vérifier les logs (les connexions réussies apparaissent ici)
sudo tail -f /var/log/xray/access.log

Depuis un client configuré :

# Via le proxy SOCKS5 local (Linux)
curl -x socks5h://127.0.0.1:10818 https://ifconfig.me
# → doit afficher l'IP de ton VPS

# Via le proxy HTTP (alternatif)
curl -x http://127.0.0.1:10819 https://ifconfig.me

Sur Windows avec v2rayN actif, ouvre simplement un navigateur et va sur ifconfig.me — tu dois voir l'IP de ton VPS.

Pour vérifier que le trafic ressemble bien à du HTTPS normal (et non à un VPN) :

# Capture du trafic vers le VPS
sudo tcpdump -i eth0 -c 20 host 203.0.113.42 and port 443 -w /tmp/capture.pcap

# Analyser — tu dois voir du TLS 1.3 classique, pas de signature VPN
tcpdump -r /tmp/capture.pcap -v | head -30

Le trafic VLESS+Reality est indistinguable d'une connexion HTTPS vers www.microsoft.com. Pas de handshake WireGuard caractéristique, pas de signature UDP — uniquement du TCP/TLS standard sur le port 443.

Sécurisation

Quelques mesures supplémentaires pour durcir le setup :

Pare-feu du VPS

Ne laisse pas tous les ports ouverts. Restreins au strict nécessaire :

# Installer ufw
sudo apt install -y ufw

# Règles
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp    # SSH
sudo ufw allow 80/tcp    # Let's Encrypt challenge
sudo ufw allow 443/tcp   # nginx → Headscale + Xray (SNI routing)
sudo ufw allow 3478/udp  # STUN (NAT traversal)
sudo ufw allow 41641/udp # WireGuard direct connections

sudo ufw enable

SSH

Désactive l'authentification par mot de passe, n'autorise que les clés :

# /etc/ssh/sshd_config
PasswordAuthentication no
PubkeyAuthentication yes
PermitRootLogin no

Mises à jour automatiques

sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades

Coût total

ComposantCoût mensuel
VPS Infomaniak (1 vCPU, 1 Go)~5€
Nom de domaine~1€/mois (amorti)
HeadscaleGratuit (open-source)
Tailscale clientGratuit
Let's EncryptGratuit
Total~6€/mois

C'est le prix d'un abonnement VPN commercial, sauf que tu contrôles tout : le serveur, les logs, la juridiction, les règles d'accès.

VPN commercial vs auto-hébergé : le verdict

CritèreVPN commercialHeadscale + Xray auto-hébergé
Mise en place2 minutes2-3 heures
MaintenanceAucuneMises à jour à faire
Nombre de serveursDizaines de pays1 (ton VPS)
ConfianceTu fais confiance au providerTu fais confiance à toi-même
Résistance au blocageFaible (IP connues)Forte (IP unique + VLESS+Reality anti-DPI)
SouverainetéAucuneTotale
Coût5-10€/mois~6€/mois
Multi-appareilsLimité selon le planIllimité
Anti-censure (DPI)Variable (Stealth, etc.)VLESS+Reality — éprouvé Russie/Chine

L'auto-hébergement n'est pas pour tout le monde. Si tu veux juste débloquer YouTube au Gabon, installe Proton VPN et passe à autre chose. Mais si tu veux une infrastructure réseau que tu contrôles, que personne ne peut te couper, et qui résiste même au DPI le plus agressif — Headscale + Xray sur un VPS suisse est la réponse.


Cet article fait partie d'une série en deux parties. La première partie couvre les solutions immédiates (DNS, VPN commercial, Tor, VLESS+Reality). Cette deuxième partie couvre l'approche souveraine avec Headscale et Xray.

Les techniques présentées ici visent à préserver l'accès à l'information, un droit fondamental reconnu par l'Article 19 de la Déclaration universelle des droits de l'homme. Utilisez-les de manière responsable et en connaissance des lois locales.

Article précédent

← MCP data.gouv.fr : interroger l open data français depuis Claude Code

Article suivant

Créer une skill Claude Code pour vérifier les affirmations scientifiques→
← Retour au blog

Sommaire

  • Pourquoi auto-héberger son VPN
  • Pourquoi Headscale et pas WireGuard brut
  • Pourquoi la Suisse
  • Prérequis
  • Étape 1 : installer Headscale
  • Étape 2 : DNS
  • Étape 3 : configurer Headscale
  • Points importants
  • Étape 4 : configurer les ACL
  • Étape 4bis : nginx SNI routing sur le port 443
  • Étape 5 : activer l'IP forwarding
  • Étape 6 : créer un utilisateur et démarrer
  • Étape 7 : connecter le VPS comme exit node
  • Étape 8 : connecter ses appareils
  • Linux
  • Windows
  • Android
  • Vérification
  • Étape 9 : activer le routage via la Suisse
  • Linux
  • Windows
  • Android
  • Vérification
  • Architecture finale
  • Option anti-DPI : VLESS+Reality (Xray)
  • Installer Xray sur le VPS
  • Générer les clés Reality
  • Configurer le serveur Xray
  • Cohabitation avec Headscale sur le port 443
  • Configurer les clients
  • Quand utiliser quoi
  • Vérifier que tout est étanche
  • 1. IP publique — le test de base
  • 2. Fuite IPv6 — le piège classique
  • 3. Fuite DNS — le FAI voit tes requêtes
  • 4. Triple vérification — croiser les sources
  • 5. Vérifier VLESS+Reality (Xray)
  • Sécurisation
  • Pare-feu du VPS
  • SSH
  • Mises à jour automatiques
  • Coût total
  • VPN commercial vs auto-hébergé : le verdict