Tunnels HTTPS sécurisés, sous-domaines automatiques et ports TCP — sans installer le moindre client. Auto-hébergé, open source, prêt pour la production.
Simple comme une commande SSH, robuste comme une infrastructure de production.
Certificats Let's Encrypt générés et renouvelés sans intervention. TLS 1.3, partout.
Infrastructure 100% Go, latence minimale, connexions persistantes et pool optimisé.
Sous-domaine aléatoire ou personnalisé : app.localvhost.com.
Bases de données, SSH, RDP, Redis… sur le port de votre choix (1024-65535, honoré tel quel). Plusieurs services TLS peuvent même partager un port, routés par sous-domaine (SNI), chiffrés de bout en bout.
Fournisseur d'identité OIDC complet (SSO), HTTPS/HSTS, en-têtes durcis. Login admin protégé contre le brute-force ; rate-limiting & pare-feu IP intégrés (désactivés en mode haute performance).
Aucune installation : un simple OpenSSH suffit. Compatible Linux, macOS, Windows.
Votre machine ouvre un tunnel SSH sortant. LocalVhost le route vers Internet via Caddy, avec HTTPS automatique.
Composez votre tunnel en quelques clics — la commande se construit en direct, prête à copier.
De zéro à un service public en moins d'une minute. Copiez-collez, c'est prêt.
Une seule commande. Aucun compte, aucun client à installer — juste votre OpenSSH.
ssh -4 -p 443 -R 80:localhost:3000 localvhost.com # → https://<aléatoire>.localvhost.com pointe vers votre localhost:3000
Exposez n'importe quel serveur web local. Le port 80 demande un sous-domaine HTTPS.
# app locale sur le port 3000 ssh -4 -p 443 -R 80:localhost:3000 localvhost.com # port local différent (ex: 5000) ssh -4 -p 443 -R 80:localhost:5000 localvhost.com
Préfixez par le nom souhaité pour obtenir une URL stable et mémorisable.
ssh -4 -p 443 -R monapp:80:localhost:3000 localvhost.com → https://monapp.localvhost.com
Pour les services non-HTTP (bases de données, SSH, VNC…), exposez-les sur le port de votre choix — n'importe quel port de 1024 à 65535. Le port demandé est honoré tel quel : demandez 5432, vous obtenez localvhost.com:5432.
# MySQL / MariaDB — exposé sur le MÊME port ssh -4 -p 443 -R 3306:localhost:3306 localvhost.com # → localvhost.com:3306 # PostgreSQL ssh -4 -p 443 -R 5432:localhost:5432 localvhost.com # → localvhost.com:5432 # Redis ssh -4 -p 443 -R 6379:localhost:6379 localvhost.com # SSH distant (port au choix) ssh -4 -p 443 -R 2022:localhost:22 localvhost.com # puis: ssh -p 2022 user@localvhost.com
rdp.localvhost.com dans mstscPour le Bureau à distance, connectez-vous avec mstsc en tapant simplement un nom — sans indiquer de port (mstsc vise 3389 par défaut). Avec un poste sur le port 3389, le serveur relaie les octets à l'identique (verbatim), TLS + NLA de bout en bout : mode prouvé avec un vrai mstsc. Pour plusieurs postes, donnez à chacun un port dédié (relais verbatim lui aussi) — voir ci-dessous.
rdp.localvhost.com — sans port.rdp2.localvhost.com:33892 (avec le port). Un poste = un port — c'est le PORT qui aiguille, le nom est mémotechnique..rdp déjà configuré, ci-dessous.# Poste principal → nom « rdp » sur 3389 (RDP interne 192.168.1.5:3389) ssh -4 -p 443 -R rdp:3389:192.168.1.5:3389 localvhost.com # → mstsc : rdp.localvhost.com (SANS port) # Poste supplémentaire → PORT DÉDIÉ 33892 (RDP interne 192.168.1.7:3389) ssh -4 -p 443 -R rdp2:33892:192.168.1.7:3389 localvhost.com # → mstsc : rdp2.localvhost.com:33892
.rdp garde le NLA activé et se contente d'afficher cet avertissement (jamais de contournement). Confirmez pour vous connecter.mstsc l'accepte. Correctif en un clic, à lancer en administrateur sur le poste distant : ⬇ rdp-cert-fix-win7.bat — il régénère le certificat en SHA-256 (méthode compatible Windows 7, clé privée générée localement, jamais transmise) et le relie à l'écouteur RDP. Dépannage immédiat, sans rien changer sur le poste : Remmina → « TLS Security Level = 0 », ou xfreerdp /tls:seclevel:0.Plusieurs services TLS (HTTPS internes, API mTLS, TLS natif) partagent un seul port public : le serveur les aiguille par le nom d'hôte (SNI) de leur poignée de main, exactement comme le RDP. La lecture est passive → le TLS/mTLS reste de bout en bout, chaque service garde son propre certificat, le serveur ne déchiffre jamais rien.
# Service A (HTTPS interne) → nom « app1 », sur le port TLS partagé 9443 ssh -4 -p 443 -N -R app1:9443:192.168.1.10:443 localvhost.com # → app1.localvhost.com:9443 # Service B (autre HTTPS interne) → nom « app2 », MÊME port ssh -4 -p 443 -N -R app2:9443:192.168.1.11:443 localvhost.com # → app2.localvhost.com:9443
# chaque nom atteint SON backend, avec SON certificat curl https://app1.localvhost.com:9443/ # → 192.168.1.10:443 (cert du service A) curl https://app2.localvhost.com:9443/ # → 192.168.1.11:443 (cert du service B)
Un seul critère décide : qui parle en premier, et où se trouve le nom d'hôte. Le serveur ne peut router une connexion que s'il trouve un identifiant dans ses premiers octets — soit le port, soit un nom (Host HTTP ou SNI TLS). Voici l'arbre de décision, le comparatif et le catalogue complet.
| Mécanisme | Routé par | Ports publics | TLS bout-en-bout | Pour quoi |
|---|---|---|---|---|
| Tunnel HTTP | sous-domaine (Host) | 443 (partagé) | Non — Caddy termine (cert auto) | sites, API web, webhooks |
| Démux SNI | sous-domaine (SNI) | 9443 (partagé) | Oui — cert du backend | plusieurs HTTPS/TLS internes, mTLS |
| TCP nommé | port (nom cosmétique) | un par service (1024-65535) | Oui — flux opaque | tout TCP : bases, SSH, cache… |
| Démux RDP | nom (SNI) / port | 3389 (ou dédié) | Oui — TLS + NLA | Bureau à distance (mstsc) |
La commande se lance depuis une machine ayant accès au service (le même réseau local). localhost = remplacez par l'hôte/IP interne si le service tourne ailleurs.
| Service | Commande | Adresse publique |
|---|---|---|
| 🌐 Web & applications — tunnel HTTP (cert Let's Encrypt auto + SSO) | ||
| Site web / SPA (Vite, Next…) | ssh -4 -p 443 -R 80:localhost:3000 localvhost.com | https://<aléatoire>.localvhost.com |
| … avec URL stable | ssh -4 -p 443 -R monapp:80:localhost:3000 localvhost.com | https://monapp.localvhost.com |
| API REST / webhook | ssh -4 -p 443 -R api:80:localhost:8080 localvhost.com | https://api.localvhost.com |
| Site WEBDEV (Serveur d'appli PC SOFT) | ssh -4 -p 443 -R webdev:80:192.168.1.20:80 localvhost.com | https://webdev.localvhost.com |
| WordPress / PHP (Apache, Nginx) | ssh -4 -p 443 -R blog:80:localhost:80 localvhost.com | https://blog.localvhost.com |
| Tomcat / Spring Boot (Java) | ssh -4 -p 443 -R app:80:localhost:8080 localvhost.com | https://app.localvhost.com |
| .NET / IIS (ASP.NET, Kestrel) | ssh -4 -p 443 -R dotnet:80:localhost:5000 localvhost.com | https://dotnet.localvhost.com |
| Jenkins / GitLab CI | ssh -4 -p 443 -R ci:80:localhost:8080 localvhost.com | https://ci.localvhost.com |
| Grafana | ssh -4 -p 443 -R grafana:80:localhost:3000 localvhost.com | https://grafana.localvhost.com |
| Jupyter Notebook | ssh -4 -p 443 -R nb:80:localhost:8888 localvhost.com | https://nb.localvhost.com |
| Prometheus (UI) | ssh -4 -p 443 -R prom:80:localhost:9090 localvhost.com | https://prom.localvhost.com |
| 🗄️ Bases de données — tunnel TCP nommé (binaire, bout-en-bout) | ||
| HFSQL Client/Serveur (WinDev — PC SOFT) | ssh -4 -p 443 -R hfsql:4900:192.168.1.20:4900 localvhost.com | hfsql.localvhost.com:4900 |
| MySQL / MariaDB | ssh -4 -p 443 -R 3306:localhost:3306 localvhost.com | localvhost.com:3306 |
| PostgreSQL | ssh -4 -p 443 -R 5432:localhost:5432 localvhost.com | localvhost.com:5432 |
| Microsoft SQL Server | ssh -4 -p 443 -R mssql:1433:192.168.1.30:1433 localvhost.com | mssql.localvhost.com:1433 |
| Oracle Database | ssh -4 -p 443 -R oracle:1521:192.168.1.31:1521 localvhost.com | oracle.localvhost.com:1521 |
| Redis | ssh -4 -p 443 -R 6379:localhost:6379 localvhost.com | localvhost.com:6379 |
| MongoDB | ssh -4 -p 443 -R 27017:localhost:27017 localvhost.com | localvhost.com:27017 |
| Elasticsearch / OpenSearch | ssh -4 -p 443 -R es:9200:localhost:9200 localvhost.com | es.localvhost.com:9200 |
| Firebird | ssh -4 -p 443 -R fb:3050:localhost:3050 localvhost.com | fb.localvhost.com:3050 |
| Cassandra | ssh -4 -p 443 -R cass:9042:localhost:9042 localvhost.com | cass.localvhost.com:9042 |
| CouchDB | ssh -4 -p 443 -R couch:5984:localhost:5984 localvhost.com | couch.localvhost.com:5984 |
| InfluxDB | ssh -4 -p 443 -R influx:8086:localhost:8086 localvhost.com | influx.localvhost.com:8086 |
| Memcached | ssh -4 -p 443 -R mc:11211:localhost:11211 localvhost.com | mc.localvhost.com:11211 |
| 📨 Files, brokers & IoT — tunnel TCP nommé | ||
| RabbitMQ (AMQP) | ssh -4 -p 443 -R amqp:5672:localhost:5672 localvhost.com | amqp.localvhost.com:5672 |
| Apache Kafka | ssh -4 -p 443 -R kafka:9092:localhost:9092 localvhost.com | kafka.localvhost.com:9092 |
| MQTT (Mosquitto, IoT) | ssh -4 -p 443 -R mqtt:1883:localhost:1883 localvhost.com | mqtt.localvhost.com:1883 |
| NATS | ssh -4 -p 443 -R nats:4222:localhost:4222 localvhost.com | nats.localvhost.com:4222 |
| 🖥️ Accès distant & bureau | ||
| SSH / SFTP distant | ssh -4 -p 443 -R 2022:localhost:22 localvhost.com | ssh -p 2022 user@localvhost.com |
| RDP — un poste (« sans port » dans mstsc) | ssh -4 -p 443 -R rdp:3389:192.168.1.5:3389 localvhost.com | mstsc : rdp.localvhost.com |
| RDP — poste supplémentaire (port dédié) | ssh -4 -p 443 -R rdp2:33892:192.168.1.7:3389 localvhost.com | mstsc : rdp2.localvhost.com:33892 |
| VNC | ssh -4 -p 443 -R 5900:localhost:5900 localvhost.com | localvhost.com:5900 |
| ✉️ Messagerie & annuaire — tunnel TCP nommé | ||
| SMTP | ssh -4 -p 443 -R 2525:localhost:25 localvhost.com | localvhost.com:2525 |
| IMAP | ssh -4 -p 443 -R imap:1143:localhost:143 localvhost.com | imap.localvhost.com:1143 |
| POP3 | ssh -4 -p 443 -R pop3:1110:localhost:110 localvhost.com | pop3.localvhost.com:1110 |
| LDAP / Active Directory | ssh -4 -p 443 -R ldap:1389:192.168.1.10:389 localvhost.com | ldap.localvhost.com:1389 |
| 🧰 DevOps & fichiers — tunnel TCP nommé | ||
| Docker Registry | ssh -4 -p 443 -R registry:5000:localhost:5000 localvhost.com | registry.localvhost.com:5000 |
| Git (protocole git://) | ssh -4 -p 443 -R git:9418:localhost:9418 localvhost.com | git.localvhost.com:9418 |
| MinIO / S3 (API) | ssh -4 -p 443 -R s3:9000:localhost:9000 localvhost.com | s3.localvhost.com:9000 |
| FTP (mode passif à configurer) | ssh -4 -p 443 -R ftp:2121:localhost:21 localvhost.com | ftp.localvhost.com:2121 |
| 🔐 Services TLS internes — démux SNI (cert du backend préservé, mTLS OK) | ||
| HTTPS interne (cert propre / mTLS) | ssh -4 -p 443 -N -R app1:9443:192.168.1.10:443 localvhost.com | https://app1.localvhost.com:9443 |
| … un 2ᵉ, même port | ssh -4 -p 443 -N -R app2:9443:192.168.1.11:443 localvhost.com | https://app2.localvhost.com:9443 |
-R hfsql:4900:…:4900) l'expose telle quelle sur hfsql.localvhost.com:4900 : protocole binaire relayé octet pour octet, de bout en bout (aucune interposition), votre application WinDev/WEBDEV/WinDev Mobile s'y connecte comme en local. Un site WEBDEV dynamique (servi par le Serveur d'application WEBDEV derrière IIS/Apache) passe lui par un tunnel HTTP (-R webdev:80:…:80) → https://webdev.localvhost.com avec certificat auto + SSO. Le Centre de Contrôle HFSQL (administration) utilise aussi le 4900.Gardez un service exposé en continu, avec reconnexion automatique — idéal pour la production.
# autossh : relance le tunnel en cas de coupure réseau autossh -M 0 -4 -p 443 -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -N -R monapp:80:localhost:3000 localvhost.com
# service systemd — /etc/systemd/system/tunnel.service [Service] ExecStart=/usr/bin/ssh -4 -p 443 -o ServerAliveInterval=30 -o ExitOnForwardFailure=yes -N -R monapp:80:localhost:3000 localvhost.com Restart=always RestartSec=5 [Install] WantedBy=multi-user.target
Arrière-plan, keep-alive, clé dédiée — identique sous Linux, macOS et Windows.
# arrière-plan (sans shell) + keep-alive ssh -4 -fN -p 443 -o ServerAliveInterval=30 -R 80:localhost:3000 localvhost.com # clé SSH spécifique ssh -4 -p 443 -i ~/.ssh/ma_cle -R 80:localhost:3000 localvhost.com # Windows (OpenSSH intégré, PowerShell) — même commande ssh -4 -p 443 -R 80:localhost:3000 localvhost.com
Démarrez votre app en arrière-plan (&), puis ouvrez le tunnel vers son port.
# React / Vue / Angular (dev server :3000) npm run dev & ssh -4 -p 443 -R 80:localhost:3000 localvhost.com # API Node / Express (:8080) avec sous-domaine 'monapi' ('api' seul est réservé) node server.js & ssh -4 -p 443 -R monapi:80:localhost:8080 localvhost.com # Python / Flask (:5000) flask run --port 5000 & ssh -4 -p 443 -R 80:localhost:5000 localvhost.com
Chaque tunnel dispose d'un tableau de bord live : trafic en direct (WebSocket), carte des visiteurs géolocalisés, journaux de requêtes, statistiques par pays, par chemin et par navigateur.
# votre dashboard personnel https://localvhost.com/dashboard
LocalVhost embarque un fournisseur OpenID Connect complet (signature RS256, sans dépendance externe). Un seul déploiement authentifie tous vos services auto-hébergés — NetBird (VPN mesh), Grafana, headscale, Proxmox, Portainer… — via Authorization Code + PKCE ou Device Flow, avec sessions longues (refresh token roté), déconnexion SSO et révocation inclus.
Configuration par découverte automatique : pointez chaque service sur l'URL de discovery, il résout seul les endpoints et les clés.
# Découverte OIDC (issuer, endpoints, clés) https://localvhost.com/.well-known/openid-configuration
| Endpoint | Rôle |
|---|---|
| /.well-known/openid-configuration | Découverte des métadonnées (issuer, endpoints, algos, scopes) |
| /oidc/jwks.json | Clés publiques de signature — RS256, kid localvhost-1 |
| /oidc/authorize | Autorisation (flux code) — PKCE S256 obligatoire ; auth silencieuse prompt=none / max_age / id_token_hint |
| /oidc/token | Échange code / device_code / refresh_token → id_token + access_token (+ refresh_token si offline_access) |
| /oidc/userinfo | Claims à jour du porteur — révocation effective |
| /oidc/device_authorization | Démarrage du Device Flow (machines sans navigateur) — renvoie user_code + verification_uri_complete (QR-friendly) |
| /oidc/device | Approbation utilisateur — protégée CSRF + same-origin |
| /oidc/logout | Déconnexion RP-initiée (RP-Initiated Logout 1.0) — post_logout_redirect_uri validée |
| /oidc/revoke | Révocation immédiate d'un refresh_token ou access_token (RFC 7009) — effet instantané à /userinfo |
| /oidc/introspect | Introspection de token (RFC 7662) — état/claims d'un access_token ou refresh_token ; auth client requise, binding par client |
Scopes openid profile email offline_access · Claims sub auth_time at_hash preferred_username name email email_verified groups
AuthIssuer: https://localvhost.com AuthAudience: netbird AuthKeysLocation: …/oidc/jwks.json OIDCConfigEndpoint: …/.well-known/openid-configuration
auth_url = …/oidc/authorize token_url = …/oidc/token api_url = …/oidc/userinfo scopes = openid profile email offline_access use_pkce = true use_refresh_token = true role_attribute_path = contains(groups[*],'admins') && 'Admin' || 'Viewer'
issuer: https://localvhost.com client_id: headscale client_secret: <secret> scope: [openid, profile, email] allowed_groups: [users]
Déclarez vos propres services (client_id, secret, redirections) sans recompiler, et pilotez les groupes RBAC par utilisateur :
# Ajouter des clients OIDC (Grafana, headscale, …) OIDC_EXTRA_CLIENTS='[{"id":"grafana","secret":"CHANGEME", "name":"Grafana","redirect_uris":["https://grafana.exemple.com/login/generic_oauth"], "post_logout_redirect_uris":["https://grafana.exemple.com/login"]}]' # Groupes du claim "groups" par utilisateur (RBAC) OIDC_GROUPS_JSON='{"admins":["alice"],"ops":["bob","carol"]}'
Une commande suffit. Pas d'inscription, pas de client, pas d'attente.
ssh -4 -p 443 -R 80:localhost:3000 localvhost.com