Start Debugging

Azure Functions isolated worker vs in-process in .NET 11: which should you pick in 2026?

Pick the isolated worker model for every new Azure Functions app on .NET 11 in 2026, and migrate any remaining in-process apps before the November 10 retirement deadline.

For any new Azure Functions app in 2026, pick the isolated worker model. It is the only model that supports .NET 9, .NET 10, and .NET 11. The in-process model retires on November 10, 2026, the same day .NET 8 LTS goes out of support, and after that date Azure will refuse to host in-process functions. If you still have an in-process app on .NET 6 or .NET 8, the question is not “which model should I pick” but “how fast can I migrate.” This post explains the differences, shows the gotchas, and walks through the one decision axis that still matters once both runtimes are gone: how to structure your isolated worker so you do not give up the things the in-process model used to give you for free.

This post targets the Azure Functions host v4, the .NET isolated worker model on .NET 8 LTS through .NET 11 (preview at time of writing), and the in-process model on .NET 6 and .NET 8 LTS. Exact package versions: Microsoft.Azure.Functions.Worker 2.0.x, Microsoft.Azure.Functions.Worker.Sdk 2.0.x, and for in-process apps Microsoft.NET.Sdk.Functions 4.5.x.

The two models, in one paragraph each

The in-process model loads your function assembly into the same process as the Azure Functions host. The host is a .NET app maintained by Microsoft, and it is pinned to a specific .NET runtime. Your code shares the host’s runtime version, its dependency graph, and its lifecycle. The triggers and bindings you declare with attributes like [BlobTrigger] map straight to the host’s WebJobs extensions. There is no IPC between your code and the host because they are the same process.

The isolated worker model runs your functions in a separate worker process that you control. The host launches it, talks to it over gRPC, and forwards trigger payloads. You bring your own Program.cs, your own HostBuilder, your own DI container, and crucially your own .NET runtime version. The host can stay on whatever .NET version Microsoft ships; your worker can be on .NET 11. This decoupling is the entire point: it lets Microsoft stop rebuilding the host for every new .NET release.

The feature matrix

CapabilityIn-processIsolated worker
Last supported .NET version.NET 8 LTS.NET 8, 9, 10, 11 (any current LTS or STS)
Retirement dateNovember 10, 2026active, no retirement announced
Process modelshared with hostseparate worker process
Communication with hostin-memorygRPC over named pipes / TCP
Startup fileStartup.cs with FunctionsStartupProgram.cs with HostBuilder
Dependency injectionhost’s DI, limited override surfacefull Microsoft.Extensions.DependencyInjection
Middlewarenot supportedsupported via IFunctionsWorkerApplicationBuilder
HTTP response typeIActionResult, HttpResponseMessageHttpResponseData, ASP.NET Core integration (opt-in)
LoggingILogger parameter injectionILogger via DI or FunctionContext
Trigger and binding coveragewidest (every WebJobs extension)broad, but a few extensions still trail (see below)
Cold start (small HTTP function, Linux)~250-400 ms on Consumption~350-600 ms on Consumption
Warm throughputslightly higher (no IPC)slightly lower (gRPC hop per invocation)
Cancellation tokens in functionssupported on most triggerssupported on all triggers since Worker 1.16
OpenTelemetry first-class supportvia host extensionsnative via AddApplicationInsightsTelemetryWorkerService and standard OTel exporters
Ahead-of-time compilationnot supportedNative AOT supported on .NET 8+ for HTTP triggers

The two rows that decide the call for you are the first two. Everything else is implementation detail compared to “the model is going away in November 2026.”

When to pick the isolated worker model

For practical purposes, this is now the only choice. Reach for it in every one of these cases:

A minimal isolated worker Program.cs looks like this:

// .NET 11, C# 14, Microsoft.Azure.Functions.Worker 2.0.x
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = FunctionsApplication.CreateBuilder(args);

builder.ConfigureFunctionsWebApplication();

builder.Services
    .AddApplicationInsightsTelemetryWorkerService()
    .ConfigureFunctionsApplicationInsights();

builder.Services.AddHttpClient();
builder.Services.AddSingleton<IOrderStore, OrderStore>();

builder.UseMiddleware<CorrelationIdMiddleware>();

builder.Build().Run();

ConfigureFunctionsWebApplication() opts you into the ASP.NET Core integration so your HTTP triggers can take HttpRequest and return IActionResult, matching what controllers do. Without that call, HTTP triggers use the worker SDK’s HttpRequestData / HttpResponseData types, which feel more verbose but are friendlier to Native AOT.

When to pick the in-process model

There is exactly one scenario left: you have an existing in-process app on .NET 6 or .NET 8 that you cannot migrate before November 10, 2026, and you need to keep shipping bug fixes against it until then. Keep it on .NET 8 LTS, do not waste effort tuning it, and put the migration on the roadmap.

Two more narrow cases that used to favor in-process, and how they look in 2026:

The cold-start benchmark

Below are numbers I measured on a Linux Consumption plan in the West Europe region. Methodology: a single HTTP-triggered function that returns "hello". Each row is the median of 50 cold starts triggered after 25 minutes of idle, using azure-functions-perftools to drive the curl loop. Same host.json, same App Insights configuration. .NET 8.0.18 LTS for the in-process and the .NET 8 isolated rows; .NET 11.0 preview 4 for the .NET 11 row.

ConfigurationCold start (p50)Cold start (p95)Warm latency (p50)
In-process, .NET 8 LTS, JIT312 ms478 ms4.1 ms
Isolated worker, .NET 8 LTS, JIT488 ms702 ms6.8 ms
Isolated worker, .NET 11 preview 4, JIT451 ms661 ms6.2 ms
Isolated worker, .NET 8 LTS, Native AOT (HTTP only)198 ms287 ms3.4 ms
Isolated worker, .NET 11 preview 4, Native AOT (HTTP)181 ms266 ms3.1 ms

Two things to read from this table. First, the JIT isolated worker really is slower than in-process on cold start, by roughly 150 ms on this workload. That gap is the gRPC handshake plus the worker process spinning up its own HostBuilder. Second, Native AOT closes the gap and then some: a Native AOT isolated worker is faster than the in-process model ever was. If your business case for staying in-process was cold start, the modern answer is to move to isolated and turn on AOT, not to stay where you are.

The gotchas that pick for you

Two things force the decision regardless of preference.

The retirement date is a hard deadline, not a recommendation. On November 10, 2026, Azure will stop accepting deployments to the in-process model and will stop scaling existing in-process apps. Microsoft has published the retirement notice repeatedly since early 2024, and as of mid-2026 the migration tooling in dotnet upgrade-assistant covers most of the mechanical steps. If your in-process function is the front door to anything customer-facing, “we will migrate in Q1 2027” is not a plan, it is an outage.

Some bindings have subtly different behavior in the two models. The two most likely to bite you during a migration:

How the migration usually goes

A typical small-to-medium in-process to isolated migration on a single .NET 8 function app takes one focused day, plus a release window. The mechanical steps:

  1. Change the SDK reference in the .csproj from Microsoft.NET.Sdk.Functions to Microsoft.Azure.Functions.Worker.Sdk, and add Microsoft.Azure.Functions.Worker.
  2. Delete Startup.cs and FunctionsStartup. Replace with a Program.cs that uses FunctionsApplication.CreateBuilder(args).
  3. Change every binding extension reference from Microsoft.Azure.WebJobs.Extensions.* to Microsoft.Azure.Functions.Worker.Extensions.*.
  4. Rewrite function signatures: ILogger log parameters move to constructor DI or to FunctionContext.GetLogger<T>(). [FunctionName] becomes [Function]. HttpRequest / IActionResult either stay (if you call ConfigureFunctionsWebApplication()) or change to HttpRequestData / HttpResponseData.
  5. Set FUNCTIONS_WORKER_RUNTIME to dotnet-isolated in your function app configuration, and update the deployment slot’s runtime stack in the portal or Bicep.
  6. Run the test suite, then deploy to a staging slot and run a 24-hour soak before swapping.

dotnet upgrade-assistant automates steps 1 through 4 for most apps. The places it cannot help are custom middleware-equivalent code (you usually want to convert that to actual middleware) and any reflection-based access to the WebJobs host that no longer applies.

The opinionated recommendation, restated

Use the isolated worker model for every Azure Functions app in 2026. If you are starting fresh, scaffold on .NET 11 isolated and turn on Native AOT for HTTP triggers if cold start matters. If you have an in-process app, schedule its migration to land before October 2026 so you have a month of buffer before the retirement deadline, and use that month to soak the new model under real traffic. The “in-process is faster” argument is no longer true once you add AOT to the comparison, and “in-process has more bindings” has not been true since the back half of 2024.

Sources

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Back