An ASP.NET Core app can be told where to listen in at least five places, and when two of them disagree the winner is not always the one you expect. This post creates an empty web app on .NET 10, runs every way of setting the port one at a time, then sets them against each other, and quotes the "Now listening on" line from each run. Read it once and the "why is it on 5000?" question goes away for good.
dotnet new web -n PortLaband openProperties\launchSettings.jsonto see the ports the template chose for youdotnet run, thendotnet run --no-launch-profile: the port and the environment both changedotnet run --urls http://localhost:15700, then the same withASPNETCORE_URLSset in the shell- Set both at once and see which wins; then add a
Kestrel:Endpointssection to appsettings.json and watch the warning - Bind to
localhost,0.0.0.0and*and test each with curl on 127.0.0.1, [::1] and your LAN address dotnet publish -c Release -o outand run the dll: no launchSettings, port 5000, Production
The template's random ports
dotnet new web wrote a launchSettings.json with two profiles, and the ports in
it are picked at random when the project is created; the documentation says HTTP from 5000 to 5300
and HTTPS from 7000 to 7300. This project got:
"applicationUrl": "http://localhost:5216",
"applicationUrl": "https://localhost:7129;http://localhost:5216",
Both profiles also set ASPNETCORE_ENVIRONMENT to Development. That file is for
dotnet run and the IDE only: the documentation says it is used on the local development
machine and is not deployed, and the publish step at the end confirms it.
Five ways, one at a time
| Command | Now listening on | Hosting environment |
|---|---|---|
dotnet run | http://localhost:5216 | Development |
dotnet run --launch-profile https | https://localhost:7129 and http://localhost:5216 | Development |
dotnet run --no-launch-profile | http://localhost:5000 | Production |
dotnet run --urls http://localhost:15700 | http://localhost:15700 | Development |
$env:ASPNETCORE_URLS='http://localhost:15701'; dotnet run --no-launch-profile | http://localhost:15701 | Production |
Two things in that table surprise people. Without a profile the app is in Production, not Development,
because the environment came from the profile too; that is the whole story of
the no-launch-profile
post. And --urls replaced the profile's port but kept its environment: the profile's
variables still applied, only its URL was overridden.
When they disagree
With ASPNETCORE_URLS set to 15701 in the shell for every run:
--urls http://localhost:15702with the profile: 15702 listened. The switch beat both the variable and the profile.--urlswith--no-launch-profile: 15702 again.- The profile and no switch: 5216 listened. The profile's
applicationUrlbeat the variable set in the shell, which matches the documentation's line that environment values in launchSettings.json override values set by the system environment.
Then appsettings.json got a Kestrel section with an Endpoints:Http:Url of
15703. It won in all four runs, against nothing, against the variable, against --urls and
against the profile, and whenever another source was present Kestrel said so:
warn: Microsoft.AspNetCore.Server.Kestrel[0]
Overriding address(es) 'http://localhost:15701'. Binding to endpoints defined via IConfiguration and/or UseKestrel() instead.
The quoted address changed to match whatever lost. Finally, builder.WebHost.UseUrls("http://localhost:15704")
in Program.cs beat --urls and beat the profile, and printed no warning at all; the other
value was simply not used. The pair not tested against each other is code versus the Kestrel
configuration section; the documentation says endpoints configured in code with Listen
override UseUrls, and that the configuration section replaces the top-level URLs rather
than adding to them. The practical ladder from the runs: code first, then the Kestrel section with its
warning, then --urls, then the launch profile, then ASPNETCORE_URLS, then the
5000 default.
localhost, 0.0.0.0 and *
| URL given | Sockets | 127.0.0.1 | [::1] | LAN address |
|---|---|---|---|---|
http://localhost:15707 | 127.0.0.1 and ::1 | 200 | 200 | refused |
http://0.0.0.0:15706 | 0.0.0.0 only | 200 | refused | 200 |
http://*:15708 | one IPv6 any-address socket | 200 | 200 | 200 |
The wildcard is logged as the IPv6 any-address, not as a star:
Now listening on: http://[::]:15708
localhost is the safe default for development: reachable from the machine, refused from
the network. 0.0.0.0 is IPv4 only, which is why [::1] refused it; the
documentation says anything not recognised as an address or localhost binds every IPv4 and
IPv6 address, which is what * did. Binding to the network on Windows also brings up the
"Windows Security Alert" dialog the first time; answering it creates inbound firewall rules for that
executable that stay behind after the test, so look in Windows Security, Firewall, "Allow an app"
when you are done experimenting. The LAN tests here ran from the same PC; a second device was not used.
After publish
dotnet publish -c Release -o out produced a folder without launchSettings.json.
Running dotnet out\PortLab.dll from the project folder listened on
http://localhost:5000 in Production, and one more thing worth knowing: the content root
was the current directory, not the out folder. --urls still works on the
published dll, and in production the usual choice is ASPNETCORE_URLS in the service
definition or the Kestrel section in appsettings.Production.json.
Two apps on one port
A second copy of the published app on a port the first one holds does not start:
Unhandled exception. System.IO.IOException: Failed to bind to address http://127.0.0.1:15710: address already in use.
The message names 127.0.0.1 although the URL said localhost, because Kestrel binds the loopback interfaces one by one. The process exited on its own and the first copy kept serving. The Fixes post for it shows how to find the process that owns the port.
Frequently asked
- Why does my ASP.NET Core app run on port 5000?
- Because no endpoint was configured. The template's launchSettings.json gives dotnet run a random port between 5000 and 5300, but that file is used only by dotnet run and the IDE; with --no-launch-profile, or after publish, Kestrel falls back to http://localhost:5000, and the environment falls back to Production for the same reason.
- How do I change the port of an ASP.NET Core app?
- For a one-off run, dotnet run --urls http://localhost:15700, or set ASPNETCORE_URLS before starting the app. For a project, edit applicationUrl in Properties/launchSettings.json. For a deployment, set ASPNETCORE_URLS in the service definition or add a Kestrel:Endpoints section to appsettings.json; that section overrides every other source and logs a warning when it does.
- Which wins: ASPNETCORE_URLS, --urls or launchSettings?
- Run on .NET 10: a UseUrls call in code beat everything tested, the Kestrel section in appsettings beat the rest with a warning, --urls beat both the launch profile and the variable, and the launch profile's applicationUrl beat ASPNETCORE_URLS set in the shell, because values in launchSettings.json override the system environment.
The HTTPS side of the same question, a missing development certificate, is in its own post, and the series From Prompt to Production deploys an app where these settings start to matter.