DevTunnels.Client 1.1.3-dev6

This is a prerelease version of DevTunnels.Client.
dotnet add package DevTunnels.Client --version 1.1.3-dev6
                    
NuGet\Install-Package DevTunnels.Client -Version 1.1.3-dev6
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="DevTunnels.Client" Version="1.1.3-dev6" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DevTunnels.Client" Version="1.1.3-dev6" />
                    
Directory.Packages.props
<PackageReference Include="DevTunnels.Client" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add DevTunnels.Client --version 1.1.3-dev6
                    
#r "nuget: DevTunnels.Client, 1.1.3-dev6"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package DevTunnels.Client@1.1.3-dev6
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=DevTunnels.Client&version=1.1.3-dev6&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=DevTunnels.Client&version=1.1.3-dev6&prerelease
                    
Install as a Cake Tool

DevTunnels.Client

A .NET client for Microsoft Dev Tunnels. It drives the devtunnel CLI through a typed, async, cancellable API: sign in, create a tunnel with a stable address, host it, and follow the host through disconnects, reconnects and takeovers.

Build License: MIT

Packages

Package Contents
DevTunnels.Client The client, host sessions and the host monitor
DevTunnels.Client.DependencyInjection AddDevTunnelsClient for IServiceCollection
DevTunnels.Client.Installer Installs the devtunnel CLI (winget, Homebrew, or the official script)

Targets .NET 9, 10 and 11. Native AOT compatible.

dotnet add package DevTunnels.Client

Quick start

A tunnel with an ID you choose keeps its address for as long as it exists. Create it once, host it on every run, and the public URL never changes.

using DevTunnels.Client;
using DevTunnels.Client.Hosting;
using DevTunnels.Client.Ports;
using DevTunnels.Client.Tunnels;

DevTunnelsClient client = new();

DevTunnelCliProbeResult probe = await client.ProbeCliAsync();
if (!probe.IsInstalled || !probe.MeetsMinimumVersion)
{
    throw new InvalidOperationException(probe.FailureReason);
}

await client.EnsureLoggedInAsync();

// Create-or-update is idempotent: an existing tunnel keeps its address.
DevTunnelStatus tunnel = await client.CreateOrUpdateTunnelAsync(
    "my-app",
    new DevTunnelOptions { AllowAnonymous = true, ExpirationHours = 720 });

// Use the ID the service returns from here on: it carries the region.
await client.CreateOrReplacePortAsync(
    tunnel.TunnelId,
    5000,
    new DevTunnelPortOptions { Protocol = "http", AllowAnonymous = true });

await using IDevTunnelHostSession session = await client.StartHostSessionAsync(
    new DevTunnelHostStartOptions { TunnelId = tunnel.TunnelId });
await session.WaitForReadyAsync();
Console.WriteLine(session.PublicUrl);

await using DevTunnelHostMonitor monitor = new(client, session);
monitor.StatusChanged += (_, e) =>
    Console.WriteLine($"{e.Current.Connectivity}, taken over: {e.Current.IsTakenOver}");

await session.WaitForExitAsync();

How tunnels behave

Addresses. A port's public URL is https://<tunnel-id>-<port>.<region>.devtunnels.ms/. The tunnel ID is yours to choose; the service picks the region and reports the full ID (my-app.usw2). Pass that reported ID to port and host calls. A tunnel belongs to the signed-in account, so two machines hosting the same ID compete for it: derive the ID from the machine when each machine needs its own address.

Expiration. The service deletes a tunnel after a period without activity: 30 days unless ExpirationHours says otherwise (1 to 720 hours, which is 30 days). The window slides, and every host connection renews it. ExpirationHours = null leaves the service's value, and an existing tunnel's, alone.

Access. Tunnels and ports are private to their owner by default. AllowAnonymous on the tunnel or on a port lets anyone with the URL connect. A browser opening the URL for the first time sees Microsoft's anti-phishing page once and continues from there; non-GET requests, requests that do not accept text/html, and requests with an X-Tunnel-Skip-AntiPhishing-Page header skip it, so webhooks and API clients are unaffected.

Protocols. A port is http, https or auto (the default). The relay terminates TLS, so a local plain-HTTP server uses http. The host rewrites the Host header of forwarded requests to localhost.

Sign-in. Microsoft Entra ID, Microsoft accounts and GitHub accounts. The CLI caches the login in the system keychain for several days; GetLoginStatusAsync tells you when it has lapsed.

Restarts. CreateOrUpdateTunnelAsync and CreateOrReplacePortAsync leave a tunnel, port and access that already match alone, so restarting an app does not drop anonymous access for a moment or change anything a client depends on.

Following a host

IDevTunnelHostSession is the devtunnel host process. DevTunnelHostMonitor follows it for as long as it runs and reports a DevTunnelHostStatus:

Connectivity Meaning
Starting The host has not connected yet
Connected This host holds the relay and serves
Disconnected This host lost the relay. The CLI reconnects by itself, so this can recover
Stopped The host process ended. Start a new session

IsTakenOver is true when this host is disconnected while the service still counts a host on the tunnel: another machine or process on the account holds it. Ports maps each hosted port to its public URL, and HostConnections is the service's count across the whole account.

The monitor reads the host's output first, because that is immediate and local, and asks the service every ReconcileInterval (30 seconds by default) and whenever the output is not understood. WaitForConnectedAsync completes on the first Connected; ReconcileAsync asks the service now.

Disposing the monitor leaves the session running. A host process also outlives a killed parent and keeps a new host off the tunnel while it holds the relay, so record IDevTunnelHostSession.ProcessId and end a leftover host before starting the next one.

Requirements

The devtunnel CLI, version 1.0.1435 or newer. ProbeCliAsync reports whether it is installed and new enough. The client looks for it in this order:

  1. DevTunnelsClientOptions.CliPathOverride
  2. The DEVTUNNEL_CLI_PATH environment variable
  3. Known install locations, which need no PATH entry:
    • Windows: %LOCALAPPDATA%\Microsoft\DevTunnels\, %LOCALAPPDATA%\Microsoft\WinGet\Links\, ~/.devtunnels/bin/, ~/bin/
    • macOS and Linux: ~/bin/, ~/.local/bin/, /usr/local/bin/, /opt/homebrew/bin/
  4. The system PATH

Installing the CLI

using DevTunnels.Client.Installer;

DevTunnelCliInstaller installer = new();
DevTunnelCliInstallResult result = await installer.InstallAsync();
Console.WriteLine(result.Success
    ? $"Installed with {result.InstallerUsed} at {result.InstalledPath}"
    : $"Install failed: {result.FailureReason}");
Platform Installer Command
Windows winget winget install Microsoft.devtunnel (user scope, no elevation)
macOS brew brew install --cask devtunnel
macOS without Homebrew, Linux curl or wget curl -sL https://aka.ms/DevTunnelCliInstall \| bash

Every location an installer writes to is in the client's search list, so ProbeCliAsync finds a fresh install right away without a restart or a PATH change. UninstallAsync removes it again.

Dependency injection

using DevTunnels.Client.DependencyInjection;

builder.Services.AddDevTunnelsClient(options => options.CommandTimeout = TimeSpan.FromSeconds(15));

IDevTunnelsClient is registered as a singleton.

API

Area Members
CLI ProbeCliAsync, GetVersionAsync
Sign-in GetLoginStatusAsync, EnsureLoggedInAsync, LoginAsync, LogoutAsync
Tunnels CreateOrUpdateTunnelAsync, GetTunnelAsync, ListTunnelsAsync, DeleteTunnelAsync
Ports CreateOrReplacePortAsync, UpdatePortAsync, GetPortListAsync, DeletePortAsync
Access GetAccessAsync, CreateAccessAsync, ResetAccessAsync, GetAccessTokenAsync
Hosting StartHostSessionAsync, IDevTunnelHostSession, DevTunnelHostMonitor
Anything else ExecuteRawAsync(["user", "show", "--json", "--nologo"]) runs any CLI command

DevTunnelsClientOptions sets the CLI path, the command timeout (10 seconds), retries (3 attempts, 2 seconds apart), the minimum CLI version and the preferred sign-in provider. CLI failures throw DevTunnelsCliException.

Building

dotnet build DevTunnels.Client.slnx -c Release
dotnet test DevTunnels.Client.slnx -c Release --no-build
dotnet csharpier check .

examples/DevTunnels.Client.Example is an interactive sample.

License

MIT. See LICENSE.txt.

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed.  net11.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on DevTunnels.Client:

Package Downloads
DevTunnels.Client.DependencyInjection

IServiceCollection registration helpers for DevTunnels.Client. Adds IDevTunnelsClient as a singleton via AddDevTunnelsClient() with AOT-safe Action<T> configuration or reflection-based IConfiguration binding.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.3-dev6 110 11/11/2026
1.1.3-dev5 87 11/10/2026
1.1.3-dev4 118 11/3/2026
1.1.3-dev3 97 11/3/2026
1.1.3-dev2 94 11/3/2026
1.1.3-dev1 333 9/23/2026
1.1.2-dev1 143 8/13/2026
1.1.1-dev1 270 6/23/2026
1.1.0 1,508 6/21/2026
1.1.0-dev1 826 4/18/2026
1.0.4 432 4/18/2026
1.0.3 144 4/17/2026
1.0.2 157 4/17/2026
1.0.1 153 4/15/2026
1.0.0 144 4/15/2026
1.0.0-rc1 401 3/27/2026
0.1.1 151 4/15/2026
0.1.0 335 3/13/2026