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.

This site uses Walter, an AI agent, for research, writing, and maintenance. Editorial and verification process

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:

  • Any new Azure Functions project in 2026. There is no business reason to ship a new app on a model that retires this November. Even if you are on .NET 8 today, scaffold with func init --worker-runtime dotnet-isolated, not --worker-runtime dotnet.
  • You need .NET 9 or .NET 11 language features. Collection expressions, the new System.Threading.Lock type, primary constructors, field keyword, and Native AOT for HTTP triggers are all unavailable on the in-process model because the host is pinned to .NET 8. The moment you want one of those, you are isolated.
  • You want middleware. Telemetry, auth bypass for warmup probes, correlation ID propagation, request validation, anything you would normally write as ASP.NET Core middleware. The isolated worker exposes a real middleware pipeline through IFunctionsWorkerApplicationBuilder.UseMiddleware<T>(). The in-process model has nothing equivalent; you simulate it by sprinkling code at the top of each function.
  • You are running on Native AOT. Functions on Native AOT requires the isolated worker model. The published binary boots in tens of milliseconds rather than hundreds, which closes most of the cold-start gap I called out in the matrix.

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:

  • A binding extension that has not been ported. For most of 2023 and 2024, certain Durable Functions features, the SignalR Service binding, and a few preview extensions trailed the isolated worker by 6 to 12 months. As of mid-2026 the binding gap has effectively closed: Durable Functions, SignalR Service, Event Grid, Cosmos DB, Service Bus, Event Hubs, Blob, Queue, and Table bindings all ship first-class isolated worker SDKs. Before assuming the gap still exists, check the binding’s NuGet page for a Microsoft.Azure.Functions.Worker.Extensions.* package. If it exists, the gap is gone.
  • Sub-300 ms cold start matters more than language version. This was a real argument in 2023. It is much weaker now. Native AOT on the isolated worker is faster on cold start than the in-process model ever was, because the worker boots without JIT and without loading the WebJobs host’s full dependency graph. If cold start is your problem, the answer is Native AOT, not in-process.

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:

  • HTTP triggers in the isolated worker default to returning HttpResponseData, which serializes via the WorkerOptions.Serializer you configure (System.Text.Json by default). In-process HTTP triggers returning IActionResult use the MVC formatters from the host. If your in-process function relied on a Newtonsoft contract resolver, the migration needs an explicit serializer registration on the worker side.
  • Durable Functions orchestrator code in the isolated worker runs in your worker process with full access to your DI container, but the orchestration replay rules still apply. Code that called into an instance-scoped service inside an orchestrator and worked accidentally on in-process (because the host’s DI is more forgiving) can deadlock or non-deterministically replay on isolated. The fix is the standard Durable rule: only call activity functions from orchestrators, do not inject services.

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.