Et si le port le plus sûr d’Internet était celui qui n’y est pas ?
Dans ce tutoriel pratique, on déploie une petite app Node.js dont le « hello world » est public, pendant que ses deux autres ports ne répondent que via un VPN WireGuard privé, un Network Group Clever Cloud. En chemin, on va rencontrer (et comprendre) LE piège qui fait perdre une après-midi à tout le monde. À la fin, vous aurez votre propre réseau privé chiffré dans le cloud, laptop compris, en une poignée de commandes.
TL;DR : Un Network Group est un mesh privé et chiffré basé sur WireGuard qui permet aux applications Clever Cloud, aux add-ons et à des machines externes (votre laptop, votre téléphone, un serveur tiers) de se joindre sur n’importe quel port.
Une app Clever Cloud n’expose publiquement qu’un seul port (HTTP, 8080) ; tous les autres ports sur lesquels elle écoute ne sont joignables que via le Network Group.
Ce que ça débloque 🚀
À partir du moment où n’importe quel port peut avoir sa voie privée et chiffrée, toute une catégorie de casse-têtes d’architecture disparaît :
- Une base de données avec zéro exposition publique. Votre add-on PostgreSQL parle à vos apps dans le mesh ; l’Internet public ne sait même pas qu’elle existe.
- Des clusters qui discutent en privé. Les apps multi-instances (un cluster Keycloak par exemple) se synchronisent entre elles sur le port qu’elles veulent.
- De l’accès admin sans bastion. Votre laptop rejoint le mesh et atteint des dashboards, des endpoints de debug ou des métriques qui n’ont jamais été exposés. Pas de tunnel SSH, pas de jump host.
- Des setups hybrides. Ce serveur on-prem legacy peut enfin parler à vos apps cloud à travers un tunnel chiffré.
- Des environnements de test isolés. Montez des bacs à sable privés sur lesquels personne ne peut tomber.
Et le meilleur : les Network Groups sont inclus avec Clever Cloud, sans coût supplémentaire. Voyons ça en action.
Qu’est-ce qu’un Network Group ?
Un Network Group (NG) est un réseau privé virtuel construit sur WireGuard qui relie des ressources entre elles via un mesh chiffré. Ces ressources peuvent être :
- des applications qui tournent sur Clever Cloud,
- des add-ons (bases de données, caches, services…),
- des ressources externes, tout ce qui vit hors de Clever Cloud : votre laptop, un téléphone, un serveur on-prem.
À l’intérieur du groupe, chaque participant peut parler à tous les autres sur n’importe quel port, comme s’ils étaient sur le même LAN. Le trafic est chiffré de bout en bout par WireGuard et ne sort jamais du tunnel.

Members vs Peers
Deux termes sont employés avec précision, et la distinction compte :
| Terme | Définition |
|---|---|
| Member | Une ressource liée au NG (une app, un add-on ou une ressource externe). Chaque member reçoit un nom de domaine dédié à l’intérieur du réseau privé. |
| Peer | Une instance d’un member, avec sa propre adresse IP dans le NG. |
Conséquence clé : un member peut avoir plusieurs peers.
Si une application scale à trois instances, ce member unique a trois peers, chacun avec sa propre IP tirée du CIDR du groupe (par exemple 10.101.0.0/16, alloué à la création du NG).
Le modèle « un seul port public »
Une application Clever Cloud expose publiquement exactement un port, en HTTP.
Le reverse proxy route https://<app>.cleverapps.io vers le port que votre app écoute via la variable d’environnement PORT, 8080 par défaut.
Tout autre port ouvert par votre process est injoignable depuis l’Internet public. C’est exactement là que les Network Groups brillent : ces ports supplémentaires deviennent joignables uniquement par les members et peers du NG.
Internet public
│ (seulement 8080, via HTTPS)
▼
┌─────────────────────────────────────────┐
│ Instance de l'app (peer 10.101.0.7) │
│ :8080 ── public ──────────────────────┼──► reverse proxy → *.cleverapps.io
│ :9457 ── NG only ──┐ │
│ :4598 ── NG only ──┤ │
└───────────────────────┼───────────────────┘
│ mesh WireGuard (chiffré)
┌────────────┼─────────────┐
▼ ▼ ▼
votre laptop votre téléphone autre app/add-on
(10.101.0.6) (10.101.0.8) (peer …)
Ce qu’on va construire
Un seul process Node.js qui ouvre trois serveurs HTTP :
| Port | Joignable depuis | Réponse |
|---|---|---|
8080 | l’Internet public | hello world |
9457 | VPN uniquement | you are on a VPN |
4598 | VPN uniquement | you are still on a VPN, buddy! |
La blague s’écrit toute seule : si vous pouvez lire « you are on a VPN », c’est que vous êtes sur le VPN.
Tout le code est prêt à l’emploi dans le repo compagnon : fredericalix/cc-ng-tutorial.
Prérequis
- Un compte Clever Cloud.
- Clever Tools ≥ 3.12, connecté via
clever login. - Les outils WireGuard en local (
wg,wg-quick),brew install wireguard-toolssur macOS. - Node.js ≥ 20 (uniquement pour tester en local).
- L’ID de votre organisation Clever Cloud (
orga_…).
Listez vos organisations, puis exportez celle avec laquelle vous voulez déployer ; toutes les commandes ci-dessous l’utilisent :
# Lister vos organisations (id + nom)
clever curl https://api.clever-cloud.com/v2/summary \
| jq -r '.organisations[] | "\(.id) \(.name)"'
# Exporter celle avec laquelle déployer
export ORG="orga_xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
Étape 1 : Récupérer l’app
Clonez le repo du tutoriel :
git clone https://github.com/fredericalix/cc-ng-tutorial.git
cd cc-ng-tutorial
Le cœur, c’est server.js, trois serveurs, un par port, chacun renvoyant une chaîne fixe.
Il bind sur 0.0.0.0 pour que le process accepte le trafic à la fois sur le port public et sur l’interface WireGuard :
'use strict';
const http = require('http');
const LISTENERS = [
{ port: Number(process.env.PORT) || 8080, body: 'hello world' },
{ port: 9457, body: 'you are on a VPN' },
{ port: 4598, body: 'you are still on a VPN, buddy!' },
];
for (const { port, body } of LISTENERS) {
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end(body);
});
server.listen(port, '0.0.0.0', () => {
console.log(`Listening on 0.0.0.0:${port} -> "${body}"`);
});
}
Le port public lit
process.env.PORT(Clever le fixe à8080) ; les deux autres sont codés en dur puisqu’ils ne vivent que sur le réseau privé. Clever Cloud démarre l’app avecnpm start(voirpackage.json).
Le .gitignore du repo exclut déjà le matériel WireGuard (*.conf, wg-*.key), les clés privées n’ont rien à faire dans git.
Test en local (prenez un PORT de libre si 8080 est occupé sur votre machine) :
PORT=8085 node server.js &
curl localhost:8085 # hello world
curl localhost:9457 # you are on a VPN
curl localhost:4598 # you are still on a VPN, buddy!
Étape 2 : Déployer sur Clever Cloud
Depuis le dossier cc-ng-tutorial :
# Créer une app Node.js dans votre org (crée le remote git + lie le dossier)
clever create --type node "cc-ports" --org "$ORG"
# Déployer (un git push sous le capot)
clever deploy
À la fin, Clever affiche votre URL : https://app-xxxxxxxx-….cleverapps.io/.
Étape 3 : Prouver le découpage public/privé
# Récupérer le hostname public de l'app avec Clever Tools
APP_HOST=$(clever domain --format json | jq -r '.[0].hostname')
curl -s "https://$APP_HOST/" # -> hello world ✅ public
# Les deux autres ports ne sont PAS exposés par le reverse proxy :
curl -s --max-time 8 "http://$APP_HOST:9457/" \
|| echo "injoignable (attendu)" # -> injoignable ✅
Seul 8080 est routé publiquement. 9457 et 4598 écoutent bien sur l’instance, mais le reverse proxy public ne les forwarde pas.
Parfait, c’est exactement le but.
Étape 4 : Créer le Network Group et lier l’app
# Récupérer l'id de l'app depuis .clever.json (écrit par clever create dans ce dossier)
APP=$(jq -r '.apps[0].app_id' .clever.json)
echo "$APP" # doit être un id app_…
# Créer le Network Group
clever ng create cc-ports-vpn --org "$ORG"
# Vérifier qu'il existe : vous verrez son CIDR (par exemple 10.101.0.0/16), sans member pour l'instant
clever ng get cc-ports-vpn --org "$ORG"
Le NG est en place. Liez maintenant l’app :
clever ng link "$APP" cc-ports-vpn --org "$ORG"
# Vérifier à nouveau : l'app apparaît désormais comme member
clever ng get cc-ports-vpn --org "$ORG"
Étape 5 : ⚠️ Redémarrer l’app (le piège)
Voici la partie qui n’a rien d’évident et qui coûte une après-midi :
Lier l’app ne suffit pas. Une instance déjà en cours d’exécution ne rejoint pas le mesh tant qu’elle n’a pas redémarré, car la configuration WireGuard est chargée au boot.
clever restart
Comment je le sais ? Juste après le link, le peer de l’app affichait 0 o reçus / aucun handshake.
Après clever restart, un nouveau peer avec une nouvelle IP est apparu, et celui-là handshake et sert du trafic.
(La doc officielle ne mentionne le redémarrage qu’après un unlink ; empiriquement, c’est tout aussi vrai pour link.)
Ce qui amène à la seconde règle :
Chaque restart/redéploiement/scale = un nouveau peer avec une nouvelle IP. L’ancien peer est supprimé automatiquement. Ne codez donc jamais l’IP du NG en dur, l’identifiant stable, c’est le nom de domaine du member.
Récupérez l’IP NG de l’instance courante dans une variable, dérivez-la toujours, ne la codez jamais en dur.
last prend le peer alloué le plus récemment, car deux peers peuvent apparaître brièvement juste après un restart, le temps que l’ancienne instance soit nettoyée :
APP_IP=$(clever ng get cc-ports-vpn --org "$ORG" --format json \
| jq -r '[.peers[] | select(.type=="CleverPeer") | .endpoint.ngTerm.host] | last')
echo "App NG IP: $APP_IP" # par exemple 10.101.0.9
Étape 6 : Mettre son laptop sur le VPN
Les peers externes rejoignent le réseau avec une configuration WireGuard standard. Votre clé privée est générée en local et n’est jamais envoyée à Clever Cloud, vous n’enregistrez que la clé publique.
# 1. Générer une paire de clés WireGuard (le umask les garde privées)
umask 077
wg genkey | tee wg-priv.key | wg pubkey > wg-pub.key
# 2. Enregistrer le laptop comme peer externe (avec sa clé PUBLIQUE)
clever ng create external my-laptop cc-ports-vpn "$(cat wg-pub.key)" --org "$ORG"
# 3. Construire la config WireGuard (remplir le placeholder de clé privée)
sed "s|<%PrivateKey%>|$(cat wg-priv.key)|" \
<(clever ng get-config my-laptop cc-ports-vpn --org "$ORG") > wg-ccports.conf
chmod 600 wg-ccports.conf
Montez le tunnel (nécessite sudo) :
sudo wg-quick up ./wg-ccports.conf
Étape 7 : La récompense 🎉
# $APP_IP a été capturée à l'étape 5 (relancez la commande si ce shell est neuf)
curl "http://$APP_IP:9457/" # -> you are on a VPN 🎉
curl "http://$APP_IP:4598/" # -> you are still on a VPN, buddy! 🎉
sudo wg show # vérifier le 'latest handshake' avec le peer de l'app
Deux ports qui ne répondaient pas depuis l’Internet public répondent maintenant, parce que vous êtes sur le VPN. Prenez une seconde pour savourer : votre laptop et une instance cloud sont désormais sur le même LAN privé, et le reste d’Internet ne peut même pas deviner que ces ports existent.
Si un curl reste bloqué, vérifiez que la route passe bien par le tunnel :
route -n get $APP_IP | grep interface # doit être utunX, pas en0
Note DNS (ça piège tout le monde) : chaque member a un nom stable de la forme
<member-id>.m.<ng-id>.cc-ng.cloud, mais il ne résout qu’à l’intérieur du NG (apps et add-ons).Votre laptop n’a pas de resolver, la sortie de
get-configne contient aucune ligneDNS =et le NG n’expose aucune IP de resolver, donc le nom renvoieNXDOMAIN. C’est attendu, pas un bug.Depuis un peer externe, utilisez l’IP (
$APP_IP). Si vous tenez vraiment au nom, ajoutez une entrée/etc/hostspointant vers l’IP courante du peer, mais comme cette IP change à chaque restart, l’accès par IP est généralement plus simple.
Vous venez de construire un réseau zero trust 🛡️
Prenez du recul : sans toucher à un firewall, un VLAN ou une autorité de certification, vous venez d’appliquer les principes fondamentaux du zero trust :
- Rien n’est de confiance par défaut. La seule chose exposée à Internet est le port que vous avez choisi d’exposer. Le reste n’est pas filtré par un firewall, il est invisible.
- Chaque peer a une identité cryptographique. Chaque appareil rejoint le réseau avec sa propre paire de clés WireGuard, et les clés privées ne quittent jamais l’appareil.
- Tout est chiffré, partout. Le trafic entre peers passe par des tunnels WireGuard de bout en bout, y compris à l’intérieur de l’infrastructure de Clever Cloud.
- La confiance se révoque en une commande. Laptop volé ?
clever ng delete external, et cette clé n’ouvre plus rien.
Les architectures zero trust viennent d’habitude avec des tarifs entreprise et des mois d’intégration. Ici, c’est le comportement par défaut d’une fonctionnalité incluse gratuitement.
Nettoyage
sudo wg-quick down ./wg-ccports.conf # couper le tunnel
clever ng delete external my-laptop cc-ports-vpn --org "$ORG" # révoquer un appareil
clever ng delete cc-ports-vpn --org "$ORG" # supprimer le NG
clever delete # supprimer l'app
Révoquer les peers externes quand un appareil ne doit plus avoir accès est la principale frontière de confiance de tout le setup, une commande, c’est réglé.
À retenir
- Une app Clever Cloud n’est publique que sur un seul port (
8080). Tout le reste est privé par construction, parfait pour des services accessibles uniquement en VPN. - Lier une app à un Network Group exige un redémarrage pour prendre effet ; la config WireGuard est chargée au boot.
- Les peers sont liés aux instances et éphémères, chaque boot reçoit une nouvelle IP ; les anciens peers sont nettoyés automatiquement. Adressez les members par leur nom DNS, pas par IP.
- Les noms DNS des members ne résolvent qu’à l’intérieur du NG, les apps/add-ons gérés par Clever peuvent les utiliser ; les peers externes n’ont pas de resolver et doivent passer par l’IP.
- WireGuard vous garde honnête : les clés privées restent en local, seules les clés publiques sont enregistrées, et les
.conf/*.keyn’ont rien à faire dans git.
Bon tunneling. Si vous pouvez lire « you are on a VPN », vous l’avez mérité. 🛡️