Aller au contenu

Mon serveur perso, invisible de tout le monde sauf de moi

Le point de départ

J’héberge mes petits projets sur un serveur Linux à la maison : de vieux sites d’archives, un phpMyAdmin, Webmin, quelques conteneurs Docker oubliés dans un coin. Rappelez-vous de mon article : Comment monter un serveur d’archives PHP sans pleurer …

Mais j’ai réalisé quelque chose.

Le problème, c’est que chaque service est une porte. Et chaque porte ouverte sur Internet est une porte que des robots viennent tester toute la journée : il suffit de regarder les logs de connexion SSH d’un serveur exposé pour s’en convaincre.

Mon objectif : que ce serveur ne soit accessible que depuis mes propres machines, où que je sois. Pas « protégé par un mot de passe », pas « caché sur un port bizarre » : invisible de l’extérieur.

Le résultat est plus simple que ce que je craignais. Je vous raconte comment je m’y suis prise, et les bêtises que vous pouvez éviter.

L’idée : un tunnel privé

Là je remercie mes collaborateurs IT de mon client pour le conseil. Imaginez votre serveur comme une maison. Aujourd’hui, elle a plein de fenêtres qui donnent sur la rue : un site web, un panneau d’admin, un accès SSH. On pourrait mettre un verrou sur chaque fenêtre. Ou alors murer toutes les fenêtres et ne garder qu’une seule porte, dont seules quelques personnes ont la clé.

Cette porte unique, c’est un VPN (réseau privé virtuel). Concrètement : un tunnel chiffré entre mon ordinateur et mon serveur. Une fois le tunnel ouvert, les deux machines se parlent comme si elles étaient sur le même réseau local, même si je suis dans un train.

J’ai choisi WireGuard, pour trois raisons : c’est léger, la configuration tient en quelques lignes, et l’authentification se fait par paires de clés (comme SSH) plutôt que par mot de passe. Seules les machines dont j’ai déclaré la clé peuvent entrer. Les autres ne reçoivent même pas de réponse.

Ce qu’il faut

  • Un serveur Linux (le mien tourne sous Ubuntu)
  • Une box avec une vraie adresse IP publique
  • Un nom de domaine dont on gère les DNS
  • Un pare-feu (UFW chez moi)
  • Un ordinateur client : Mac, Windows ou téléphone, tout marche

Une vérification avant de commencer : votre box a-t-elle une vraie IP publique ? Comparez l’adresse que votre box affiche comme « IP WAN » avec celle que vous voyez depuis Internet (par exemple avec curl -4 ifconfig.me). Si elles sont identiques, c’est bon. Si votre box affiche une adresse comprise entre 100.64 et 100.127, vous êtes derrière un CGNAT : le tunnel ne pourra pas arriver jusqu’à chez vous, et il faudra une autre approche.

Étape 1 : installer WireGuard et fabriquer les clés

Sur le serveur :

sudo apt install wireguard

Chaque machine, le serveur comme les clients, possède une paire de clés :

  • une clé privée, qui ne quitte jamais la machine ;
  • une clé publique, que l’on donne aux autres.
wg genkey | tee private.key | wg pubkey > public.key

La clé publique se partage sans danger. La clé privée, jamais : ni dans un message, ni sur un forum, ni dans une conversation avec un assistant. (Oui, c’est écrit exprès.)

Ensuite, un fichier de configuration côté serveur. Il donne au serveur une adresse dans le tunnel, un petit réseau privé à part. Choisissez une plage qui ne ressemble pas à celle de votre box : sinon les adresses se télescopent quand vous vous connectez depuis un endroit qui utilise la même.

[Interface]
Address = 10.8.0.1/24
ListenPort = <PORT>
PrivateKey = <clé privée du serveur>

[Peer]
# mon ordinateur
PublicKey = <clé publique de l'ordinateur>
AllowedIPs = 10.8.0.2/32

Le bloc [Peer] dit : « cette clé publique a le droit d’entrer, et elle aura toujours l’adresse 10.8.0.2 ». Il en faut un par machine autorisée.

Étape 2 : retrouver sa maison quand l’IP change

Ma box n’a pas d’IP fixe : elle change de temps en temps. Si mon ordinateur est configuré avec « connecte-toi à telle adresse », le jour où l’adresse change, plus rien ne marche, et je suis dehors, au sens propre.

La solution : un nom plutôt qu’une adresse. Beaucoup d’hébergeurs de noms de domaine proposent un enregistrement DNS dynamique (DynHost chez certains, DynDNS ou No-IP ailleurs). Le principe :

  1. On crée un sous-domaine dédié à cet usage.
  2. Un petit programme sur le serveur (j’utilise ddclient) vérifie toutes les quelques minutes l’adresse publique, et met le DNS à jour si elle a changé.
  3. Les clients WireGuard se connectent à ce nom, jamais à l’adresse brute.

Dans la configuration du client, ça donne :

[Interface]
PrivateKey = <clé privée du client>
Address = 10.8.0.2/32

[Peer]
PublicKey = <clé publique du serveur>
Endpoint = nom-choisi.mon-domaine.tld:<PORT>
AllowedIPs = 10.8.0.1/32
PersistentKeepalive = 25

La ligne AllowedIPs compte : seul le trafic à destination du serveur passe par le tunnel. Le reste de mon Internet (vidéos, mails, navigation) continue de sortir normalement, sans ralentissement.

Étape 3 : ouvrir une seule porte dans la box

Deux réglages dans l’interface de la box :

  • Réserver l’adresse locale du serveur (un « bail DHCP statique »). Sans ça, le serveur peut changer d’adresse locale après un redémarrage, et la redirection de port pointera dans le vide.
  • Rediriger un seul port, en UDP, vers le serveur : celui de WireGuard.

C’est tout. Les autres redirections que j’avais, héritées de vieilles expériences, sont décochées : il ne passe plus rien, à part le tunnel. Et comme WireGuard ne répond jamais à un paquet qui n’est pas signé par une clé connue, un scanner qui frappe à cette porte n’obtient aucune réponse. Pour lui, elle n’existe pas.

N’oubliez pas d’autoriser ce port UDP dans le pare-feu du serveur lui-même.

Étape 4 : des noms privés avec dnsmasq

Le tunnel marche, mais taper des adresses numériques pour atteindre mes sites d’archives n’est pas très agréable. Avant, ces sites avaient des sous-domaines publics. Je les ai supprimés chez mon hébergeur de noms de domaine : il ne reste plus aucune trace publique de leur existence.

Pour continuer à y accéder par leur nom, j’ai installé dnsmasq, un tout petit serveur DNS, sur le serveur. Il n’écoute que sur l’adresse du tunnel : personne d’autre ne peut l’interroger. Sa configuration tient en quelques lignes :

listen-address=10.8.0.1
bind-dynamic
resolv-file=/run/systemd/resolve/resolv.conf

address=/mon-site.mon-domaine.tld/10.8.0.1
address=/autre-site.mon-domaine.tld/10.8.0.1

Chaque ligne address= dit : « ce nom, c’est le serveur ». Toutes les autres questions DNS sont transmises à un DNS classique, donc Internet continue de fonctionner normalement.

Dans la configuration WireGuard du client, une seule ligne à ajouter pour utiliser ce DNS quand le tunnel est ouvert :

DNS = 10.8.0.1

Résultat : tunnel fermé, mes sites n’existent pas. Tunnel ouvert, ils s’affichent comme avant. Et pour en ajouter un, une ligne dans un seul fichier sur le serveur, au lieu de modifier le fichier hosts de chaque machine.

Deux pièges à connaître :

  • Après avoir modifié le fichier, il faut redémarrer dnsmasq. Moi, j’ai oublié, et j’ai cherché un moment pourquoi mon nouveau site ne se résolvait pas.
  • Ne déclarez pas dans dnsmasq le nom que vos clients utilisent pour joindre la box. Sinon ils chercheraient l’adresse du tunnel avant que le tunnel existe, et ne le monteraient jamais.

Étape 5 : refermer le pare-feu

C’est la partie la plus satisfaisante, et aussi celle où l’on peut scier la branche sur laquelle on est assis. La méthode que j’ai suivie, service par service :

  1. Ajouter une règle qui autorise le service depuis le réseau du tunnel et depuis mon réseau local.
  2. Tester depuis un ordinateur connecté au tunnel.
  3. Seulement ensuite, supprimer la règle qui l’ouvrait à tout le monde.
sudo ufw allow from 10.8.0.0/24 to any port <PORT> proto tcp
sudo ufw allow from 192.168.1.0/24 to any port <PORT> proto tcp
# on teste, et seulement après :
sudo ufw delete allow <PORT>/tcp

Adaptez bien sûr les plages d’adresses à votre propre réseau.

Sur mon serveur, j’avais laissé ouverts à tout Internet le web, une variante du web sur un autre port, et même le HTTPS alors qu’aucun site n’en avait besoin. Tout cela n’est désormais accessible que depuis le tunnel et depuis la maison.

Gardez toujours un accès de secours : le mien, c’est un écran et un clavier que je peux brancher directement sur la machine. L’accès SSH est la dernière porte que je compte refermer. Je le ferai en dernier, avec une deuxième session déjà ouverte pour m’assurer de ne pas m’enfermer dehors.

Les pièges que j’ai rencontrés

1. Fail2ban m’a bannie moi-même. Mon ordinateur, une fois dans le tunnel, arrive sur le serveur avec une adresse du tunnel. Quelques mots de passe ratés sur mes vieux sites protégés, et fail2ban a banni cette adresse. Résultat : plus rien ne répondait, alors que le tunnel marchait très bien. La parade : lui indiquer que le réseau du tunnel et celui de la maison sont des adresses à ne jamais bannir (le réglage ignoreip).

2. Docker contourne le pare-feu. Docker écrit ses propres règles réseau, avant celles d’UFW. Un conteneur qui publie un port reste joignable même si le pare-feu n’a aucune règle pour lui. Vérifiez avec docker ps quels ports sont publiés sur toutes les interfaces, et ne comptez pas sur UFW seul. Chez moi, ces ports ne sont pas redirigés par la box, donc invisibles d’Internet, mais ils restent accessibles depuis mon réseau local : à surveiller.

3. Les vieux Apache n’ont pas la même syntaxe que les récents. En retirant un mot de passe sur un site servi par Apache 2.4, il faut une ligne Require all granted, sinon on obtient une erreur 403. Mais dans un vieux conteneur avec Apache 2.2, cette même ligne provoque une erreur : c’est l’ancienne syntaxe (Order allow,deny et Allow from all) qu’il faut garder. Avant de recharger, apache2ctl configtest est votre ami.

4. Le cache DNS de l’ordinateur. Après avoir ajouté un nom, mon Mac continuait de répondre « introuvable ». Vider le cache DNS règle ça.

Le test final : depuis dehors

Tester depuis son salon prouve peu de choses : la box accepte de se rappeler elle-même, et le réseau local laisse tout passer. Le vrai test se fait depuis l’extérieur. J’ai mis un moment à y penser, mais la solution est dans la poche : le partage de connexion du téléphone.

  1. Connecter l’ordinateur au Wi-Fi du téléphone, tunnel désactivé. Vérifier avec curl -4 ifconfig.me que l’adresse n’est plus celle de la maison.
  2. Essayer d’atteindre le serveur directement : il ne doit rien répondre (pas de page, pas de SSH).
  3. Activer le tunnel, puis faire un ping vers l’adresse du serveur dans le tunnel, ouvrir une session SSH, et charger un des sites.

Chez moi : sans tunnel, silence radio. Avec tunnel, tout répond, avec quelques dizaines de millisecondes de latence.

En bref

Le serveur de la maison n’est plus qu’une porte (un seul port UDP) dont seules mes machines ont la clé. Je n’ai rien eu à réécrire ni à migrer : les vieux sites, les conteneurs oubliés et les panneaux d’admin sont toujours là, mais ils ne sont plus visibles que de l’intérieur.

Il me reste à ajouter mon deuxième ordinateur, à refermer SSH au tunnel, et à retirer les mots de passe devenus inutiles sur les archives.

La leçon que j’en retiens : ajouter d’abord, tester, supprimer ensuite. Dans cet ordre-là, on ne se retrouve jamais dehors.

Retour en haut