How to automatically choose a free port in ASP.NET Core 3.0
Andrew Lock "Sock"
Posted on May 3, 2020
This post is in response to a discussion I had with a friend recently who was trying out .NET Core. Unfortunately, when they attempted to start their new application they received the following message:
crit: Microsoft.AspNetCore.Server.Kestrel[0]
Unable to start Kestrel.
System.IO.IOException: Failed to bind to address http://127.0.0.1:5000: address already in use.
When you create a new .NET project using a template, it always uses the same URLs, defined in
Unfortunately, the MacBook had a driver installed that was already bound to port 5000, so whenever the .NET Core project attempted to start, the port would conflict, and they'd the see error above. Not a great experience!
In this post I show one way to resolve the problem by randomising the ports ASP.NET Core uses when it starts the application. I'll also show how you can work out which port the application has selected from inside your app.
Randomly selecting a free port in ASP.NET Core
In my previous post, I showed some of the ways you can set the URLs for your ASP.NET Core application. Unfortunately, all of those approaches still require that you choose a port to use. When you're developing locally, you might not care about that, just run the application!
You can achieve exactly this by using the special port 0
when setting the URL to use. For example, to bind to a random http and https port on the loopback (localhost) address, run your application using the following command:
dotnet run --urls "http://[::1]:0;https://[::1]:0"
This will randomly select a pair of ports that aren't currently in use, for example:
info: Microsoft.Hosting.Lifetime[0]
Now listening on: http://[::1]:54213
info: Microsoft.Hosting.Lifetime[0]
Now listening on: https://[::1]:54214
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
Alternatively, instead of binding to the loopback address, you can bind to any IP address (using a random port) with the following command:
dotnet run --urls "http://*:0"
This binds to all IPv4 and IPv6 addresses on a random port.
The
*
isn't actually special, you just need to use something that isn't a valid IPv4 or IPv6 IP address (orlocalhost
). Even a hostname is treated the same as*
i.e. it binds to all IP addresses on the machine.
The downside of choosing at random port at runtime is that you get a different pair of ports every time you run the application. That may or may not be a problem for you.
When is this useful?
On the face of it, having your application listen on a different URL every time you restart it doesn't sound very useful. It would be incredibly irritating to have to type a new URL into your browser (instead of just hitting refresh) every time you restart the app. So why would you do this?
The one time I use this approach is when building worker services that run background tasks in Kubernetes.
But wait, isn't the whole point of worker services that they don't run Kestrel and expose URLs?
Well, yes, but due to the issues in the 2.x implementation of worker services, I typically still use a full WebHost
based ASP.NET Core app, instead of a generic Host
app. Now, in ASP.NET Core 3.0, those problems have been resolved, but I still don't use the generic host…
The problem is, I'm running applications in Kubernetes. An important part of that is having liveness/health checks, that check that the application hasn't crashed. The typical approach is to expose an HTTP or TCP endpoint that the Kubernetes infrastructure can call, to verify the application hasn't crashed.
Exposing an HTTP or TCP endpoint…that means, you guessed it, Kestrel!
An HTTP/TCP health check endpoint is very common for applications, but there are other options. For example you could use a command that checks for the presence of a file, or some other mechanism. I'd be interested to know in the comments if you're using a different mechanism for health checks of your worker services!
When the application is running in Kubernetes, the application obviously needs to use a known URL, so I don't use random port selection running when it's running in production. But when developing locally on my dev machine, I don't care about the port at all. Running locally, I only care that the background service is running, not the health check endpoint. So for those services, the random port selection works perfectly.
How do I found out which port was selected?
For the scenario I've described above, it really doesn't matter which port is selected, as it's not going to be used. But in some cases you may need to determine that at runtime.
You can find out which port (and IP Address) your app is listening on using the IServerAddressesFeature
, using the following approach:
var server = services.GetRequiredService<IServer>();
var addressFeature = server.Features.Get<IServerAddressesFeature>();
foreach(var address in addressFeature.Addresses)
{
_log.LogInformation("Listing on address: " + address);
}
Note that Kestrel logs this information by default on startup, so you shouldn't need to log it yourself. You might need it for other purposes though, to register with Consul for example, so logging is just a simple example.
The question is, where should you write that code? Depending on where you put it, you can get very different answers.
For example, if you put that code in a hosted service in ASP.NET Core 3.0, then the Addresses
collection on addressFeature
will be null! That's because in ASP.NET Core 3.0, the hosted services are started before the middleware pipeline and Kestrel are configured. So that's no good.
You might consider placing it inside Startup.Configure()
, where you can easily access the server features on IApplicationBuilder
:
public class Startup
{
public void Configure(IApplicationBuilder app, ILogger<Startup> log)
{
// IApplicationBuilder exposes an IFeatureCollection property, ServerFeatures
var addressFeature = app.ServerFeatures.Get<IServerAddressesFeature>();
foreach(var address in addressFeature.Addresses)
{
_log.LogInformation("Listing on address: " + address);
}
}
// ... other configuration
}
Unfortunately, that doesn't work either. In this case, Addresses
isn't empty, but it contains the values you provided with the --urls
command, or using the ASPNETCORE_URLS
variable, with the port set to 0:
Listing on address: http://*:0
Listing on address: http://[::1]:0
That's not very useful either, we want to know which ports are chosen!
The only safe place to put the code is somewhere that will run after the application has been completely configured, and Kestrel is handling requests. The obvious place is in an MVC controller, or in middleware.
The following middleware shows how you could create a simple endpoint that returns the addresses being used as a comma delimited string:
public class ServerAddressesMiddleware
{
private readonly IFeatureCollection _features;
public ServerAddressesMiddleware(RequestDelegate _, IServer server)
{
_features = server.Features;
}
public async Task Invoke(HttpContext context)
{
// fetch the addresses
var addressFeature = _features.Get<IServerAddressesFeature>();
var addresses = addressFeature.Addresses;
// Write the addresses as a comma separated list
await context.Response.WriteAsync(string.Join(",", addresses));
}
}
We can add this middleware as an endpoint:
public class Startup
{
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app)
{
app.UseRouting();
app.UseEndpoints(endpoints =>
{
// Create the address endpoint, consisting of our middleware
var addressEndpoint = endpoints
.CreateApplicationBuilder()
.UseMiddleware<ServerAddressesMiddleware>()
.Build();
// Register the endpoint
endpoints.MapGet("/addresses", addressEndpoint);
});
}
}
Now when you hit the /addresses
endpoint, you'll finally get the actual addresses your application is listening on:
Of course, middleware is clearly not the place to be handling this sort of requirement, as you would need to know the URL to call before you call the URL that tells you what URL to call! 🤪 The point is just that this information isn't available until after you can handle requests!
So where can we put this code?
One option is to hook into the IHostApplicationLifetime
lifetime events. These events are triggered at various points in your application's lifetime, and give the option of running a synchronous callback.
For example, the following code registers a callback that waits for Kestrel to be fully configured, and then logs the addresses:
public class Startup
{
public void Configure(IApplicationBuilder app, IHostApplicationLifetime lifetime, ILogger<Startup> logger)
{
// Register a callback to run after the app is fuly configured
lifetime.ApplicationStarted.Register(
()=> LogAddresses(app.ServerFeatures, logger));
// other config
}
// Called after configuration is complete
static void LogAddresses(IFeatureCollection features, ILogger logger)
{
var addressFeature = features.Get<IServerAddressesFeature>();
// Do something with the addresses
foreach(var addresses in addressFeature.Addresses)
{
logger.LogInformation("Listening on address: " + addresses);
}
}
}
This approach gives you access to your application's URLs at one of the earliest points they're available in your application's lifetime. Just be aware that the callback can't be async, so you can't do anything especially fancy there!
Summary
In this post I described how to use the "magic port 0" to tell your ASP.NET Core application to choose a random port to listen on. I use this approach locally when creating background services that I don't need to make HTTP requests to (but which I want to expose an HTTP endpoint for liveness checks in Kubernetes).
I also showed how you can find out the actual URLs your application is listening on at runtime using the IServerAddressesFeature
. I showed that you need to be careful when you call this feature - calling it too early in your application's startup could give you either an empty list of addresses, the requested list of addresses (i.e. the "port 0" addresses), or the actual addresses. Make sure to only use this feature after application configuration is complete, for example from middleware, from an MVC controller, or in the IHostApplicationLifetime.ApplicationStarted
callback.
Posted on May 3, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.