.NET 8 and .NET 9 both reach end of support on November 10, 2026. That is about five weeks from today.
Many of us still run services on .NET 8. It was the safe LTS choice, and it worked well. So the real question now is not "should we move?" but "how fast, and to which version?"
In this post we will learn about:
- The dates that matter, from Microsoft's own support policy
- What "end of support" really means for your apps on Azure
- A simple decision guide: migrate now, migrate soon, or wait
The migration steps we follow, and the pitfalls to watch for
.NET 10 is LTS and is supported until November 14, 2028.
.NET 8 (LTS) and .NET 9 (STS) end on the same day: November 10, 2026.
For most production services, move to .NET 10 now. Do not wait for .NET 11.
The dates that matter
Here are the dates from the official .NET support policy (last updated September 8, 2026):
| Version | Type | Released | End of support |
|---|---|---|---|
| .NET 8 | LTS | Nov 14, 2023 | Nov 10, 2026 |
| .NET 9 | STS | Nov 12, 2024 | Nov 10, 2026 |
| .NET 10 | LTS | Nov 11, 2025 | Nov 14, 2028 |
| .NET 11 | STS | Nov 2026 (RC1 out now) |
Note: .NET 9 was first planned to end in May 2026. During its life, Microsoft extended STS support from 18 to 24 months. That is why .NET 8 and .NET 9 now end together.
LTS means Long Term Support (three years). STS means Standard Term Support (now two years). The quality of both is the same. Only the support length is different.
What "end of support" really means
Your app will not stop on November 11. It keeps running.
But after that date:
- No more security patches for that runtime
- No Microsoft support if something breaks
- Security scanners and audits start to flag it
On Azure App Service, apps on an old runtime keep running, but App Service can't give security patches or support for that runtime after its end-of-support date.
On Azure Functions, check two things. .NET 8 and .NET 9 workers end on the same date. And the in-process model for .NET Functions also ends support on November 10, 2026. If you still use in-process Functions, plan the move to the isolated worker model together with the version upgrade.
The decision guide
flowchart TD
A[Service on .NET 8 or 9] --> B{Is it in production<br/>and still maintained?}
B -- No, retiring soon --> W[Fine to wait.<br/>Retire it, or isolate it]
B -- Yes --> C{Do all key packages<br/>support .NET 10?}
C -- Yes --> N[Migrate now to .NET 10]
C -- Not yet --> S[Migrate soon.<br/>Track the blocker, upgrade the rest]
N --> T[Target .NET 10 LTS,<br/>not .NET 11 STS]
S --> T
Migrate now
- The service is in production and you will keep changing it.
- It faces the internet, or it handles payments or personal data.
- Your main packages (EF Core, your message bus client, your auth library) already support .NET 10.
Migrate soon (but start this week)
- One important third-party package is not ready yet. Upgrade everything else, and keep a short list of blockers.
- You have many microservices. Start with one small, low-risk service and learn from it.
It's fine to wait
- The app will be retired soon, and it is not exposed to the internet.
- It is a .NET Framework app. That is a different support track and a different (bigger) migration.
Be honest with this last group. "We will retire it next year" often becomes three more years.
Why .NET 10 and not .NET 11?
.NET 11 ships in November 2026, at .NET Conf (November 10 to 13). It is an STS release.
So why not wait and jump straight to .NET 11?
- .NET 11 GA arrives on the same day .NET 8 support ends. Waiting leaves no buffer for testing.
- Your packages need time to support a new version. .NET 10 has had almost a year.
If your team likes the newest features and upgrades every year, .NET 11 is a fine next step after you are on .NET 10. Going 8 to 10 now, then 10 to 11 later, is a much smaller jump each time.
Step-by-step migration
These are the steps we follow for one service. The sample config is in my repo (link at the end).
1. Install the SDK and pin it with global.json
Install the latest .NET 10 SDK. Then pin it, so every developer and the CI pipeline use the same SDK.
// global.json
{
"sdk": {
"version": "10.0.100",
"rollForward": "latestFeature"
}
}
Note:
rollForward: latestFeaturelets the build use a newer 10.0 SDK feature band if one is installed,
2. Change the target framework in one place
If you have many projects, put the target framework in Directory.Build.props at the solution root. Then one line upgrades every project.
<!-- Directory.Build.props -->
<Project>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<CheckSdkVulnerabilities>true</CheckSdkVulnerabilities>
</PropertyGroup>
</Project>
Note: remove
<TargetFramework>from each.csproj, or it will override this value.
CheckSdkVulnerabilities makes the build warn when the SDK you build with has known vulnerabilities (NETSDK1238) or is end of life (NETSDK1239). The check arrives with the .NET 11 SDK (Preview 5 and later); the .NET 10 SDK ignores the property, so today it does nothing. It is a cheap safety net for the next migration.
3. Update the Microsoft packages
Update every Microsoft.* package that follows the runtime version (ASP.NET Core, EF Core, Extensions) to 10.0.x. Then check what else is outdated or vulnerable:
# from the solution folder
dotnet list package --outdated
dotnet list package --vulnerable --include-transitive
4. Read the breaking changes (really)
Microsoft keeps a full list of breaking changes in .NET 10. For microservices, these are the ones I would check first:
-
Default .NET container images use Ubuntu (not Debian). If your Dockerfile runs
apt-getor depends on OS packages, test it. -
System.Linq.AsyncEnumerableis now in the core libraries. If you use theSystem.Linq.Asyncpackage, you can get conflicts. -
BackgroundServiceruns all ofExecuteAsyncas a Task. Check workers that did blocking work before their firstawait. - Null values are preserved in configuration. Check options classes that relied on nulls being dropped.
-
dotnet restoreaudits transitive packages. Your build may show new vulnerability warnings, which is a good thing.
Also read ASP.NET Core 10 breaking changes and EF Core 10 breaking changes if you use them.
5. Update the Docker base images
# Dockerfile
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish ./src/OrderService/OrderService.csproj -c Release -o /app
FROM mcr.microsoft.com/dotnet/aspnet:10.0
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["dotnet", "OrderService.dll"]
6. Update the CI pipeline
# .github/workflows/build.yml (only the setup step)
- uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
Reading the version from global.json means you change the SDK in one place only.
7. Update the Azure runtime setting
For App Service on Linux, change the stack to .NET 10:
az webapp config set --name <app-name> --resource-group <rg-name> --linux-fx-version "DOTNETCORE|10.0"
For Azure Functions, move to the isolated worker model first if you are still in-process, then target net10.0.
8. Run the tests, then deploy one service first
Run all tests. Then deploy one small service to a test environment and watch logs and metrics for a day before moving the rest.
Should a tool do this for me?
Microsoft now recommends GitHub Copilot app modernization for upgrades. It analyzes your solution, writes an upgrade plan, and commits each change so you can review or roll it back.
The older .NET Upgrade Assistant is officially deprecated. Use it only if you can't use the new agent.
My honest view: a tool can do the boring edits. But you still need to read the breaking changes and test your own service. The tool doesn't know your production traffic.
Key takeaways
- .NET 8 and .NET 9 both end on November 10, 2026.
- .NET 10 (LTS, until November 14, 2028) is the safe target for most services.
- Waiting for .NET 11 leaves you no testing buffer.
- Pin the SDK with
global.json, set the target framework in one place, and read the breaking changes. - Start with one small service, then move the rest.
This is a practical guide, not a full list of every change. Always check the official docs for your own stack.
The sample config is on GitHub:
https://github.com/ahmedshahjr/Articles/tree/main/should-you-migrate-to-dotnet-10
Direct link to the sample folder:
In the next post we will look at the new C# 14 field keyword and how it removes a lot of property boilerplate.
Which .NET version are your services running today, and what is the one thing blocking your move to .NET 10?
Top comments (0)