Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Important
Support will end for the in-process model on November 10, 2026. We highly recommend that you migrate your apps to the isolated worker model for full support.
The Durable Functions runtime automatically persists function parameters, return values, and other state to the task hub to provide reliable execution. However, the amount and frequency of data persisted to durable storage can impact application performance and storage transaction costs. Depending on the type of data your application stores, data retention and privacy policies may also need to be considered.
This article explains what data gets persisted, how to handle large payloads and sensitive data, and how to customize serialization for each supported language.
In this article:
- Task hub contents - What data is stored and how
- Keep inputs and outputs small - Strategies for managing payload size
- Work with sensitive data - Protect secrets and personally identifiable information
- Secure your task hub storage - Protect your storage backend from unauthorized access
- Customize serialization and deserialization - Language-specific serialization options
Task hub contents
Task hubs store the current state of instances, and any pending messages:
- Instance states store the current status and history of an instance. For orchestration instances, this state includes the runtime state, the orchestration history, inputs, outputs, and custom status. For entity instances, it includes the entity state.
- Messages store function inputs or outputs, event payloads, and metadata that is used for internal purposes, like routing and end-to-end correlation.
Messages are deleted after being processed, but instance states persist unless they're explicitly deleted by the application or an operator. In particular, an orchestration history remains in storage even after the orchestration completes.
For an example of how states and messages represent the progress of an orchestration, see the task hub execution example.
Where and how states and messages are represented in storage depends on the storage provider. Use Durable Task Scheduler because it provides a managed backend for task hubs and handles the underlying state store for you. However, Azure Storage remains a solid option for existing workloads and for apps that want to manage their own storage resources.
| Storage provider | How state is stored | Recommended use |
|---|---|---|
| Durable Task Scheduler | Orchestration and entity state are stored in the managed scheduler backend behind a task hub resource. | Preferred option for new Durable Functions apps and managed deployments. |
| Azure Storage | State and messages are represented in queues, tables, and blobs in an Azure Storage account. | Good fit for existing apps or deployments that already rely on Azure Storage. |
Types of data that are serialized and persisted
The following list shows the different types of data that will be serialized and persisted when using features of Durable Functions:
- All inputs and outputs of orchestrator, activity, and entity functions, including any IDs and unhandled exceptions
- Orchestrator, activity, and entity function names
- External event names and payloads
- Custom orchestration status payloads
- Orchestration termination messages
- Durable timer payloads
- Durable HTTP request and response URLs, headers, and payloads
- Entity call and signal payloads
- Entity state payloads
For guidance on managing payload size and protecting sensitive items in this list, see the following sections.
Keep Durable Functions inputs and outputs small
You can run into memory issues if you provide large inputs and outputs to and from Durable Functions APIs. Inputs and outputs are serialized into the orchestration history, which means that large payloads can, over time, greatly contribute to unbounded history growth. This growth risks causing memory exceptions during replay.
To mitigate the impact of large inputs and outputs, you can:
- Delegate work to sub-orchestrators to load balance the history memory burden across multiple orchestrators, keeping the memory footprint of individual histories small.
- Store large data in external storage (such as Azure Blob Storage) and pass lightweight identifiers that allow you to retrieve that data inside activity functions when needed.
For the Durable Task Scheduler, use large payload support to offload larger payloads to Azure Blob Storage. For new apps, this pattern is recommended when an orchestration must pass large payloads between durable operations. If you use the Azure Storage provider, you can still apply the claim-check pattern shown in the following section and pass lightweight references between operations.
Tip
The best practice for dealing with large data is to keep it in external storage and materialize that data only inside activities, when needed.
Pass references to large payloads
Choose the pattern that fits your storage provider.
Durable Task Scheduler
If you use Durable Task Scheduler, enable large payload support so the runtime writes larger payloads to Azure Blob Storage and sends a small reference through the scheduler. A typical configuration is shown in the scheduler docs:
{
"version": "2.0",
"extensions": {
"durableTask": {
"storageProvider": {
"type": "azureManaged",
"connectionStringName": "DTS_CONNECTION_STRING",
"payloadStorageEnabled": true,
"payloadStorageThresholdBytes": 262144
},
"hubName": "%TASKHUB_NAME%"
}
}
}
Azure Storage
With the Azure Storage provider, you can use the Claim Check pattern to keep orchestration history small while still processing large payloads. The orchestrator passes a lightweight reference that contains the blob container and blob name, and the activity reads or writes the payload from Azure Blob Storage as needed.
The following examples assume that you already have a blob in your storage account. Start the orchestration with a reference such as {"container":"large-payloads","blobName":"input/job-123.json"}. The activity creates the processed-payloads output container if necessary. The sample processing step copies the input bytes unchanged; replace it with your application logic.
Important
Never include storage credentials or a shared access signature (SAS) in the reference. The system persists the reference in orchestration history. These examples use an app setting named PAYLOAD_STORAGE_CONNECTION_STRING to keep the storage code concise. For production workloads, use Microsoft Entra ID to authorize access to blob data.
This example requires the Azure.Storage.Blobs NuGet package.
using System;
using System.Threading.Tasks;
using Azure.Storage.Blobs;
using Microsoft.Azure.Functions.Worker;
using Microsoft.DurableTask;
public record BlobReference(string Container, string BlobName);
public static class LargePayloadFunctions
{
[Function("ProcessLargePayload")]
public static async Task<BlobReference> RunOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
BlobReference inputReference = context.GetInput<BlobReference>()
?? throw new InvalidOperationException("A blob reference is required.");
return await context.CallActivityAsync<BlobReference>(
nameof(ProcessLargePayloadActivity), inputReference);
}
[Function(nameof(ProcessLargePayloadActivity))]
public static async Task<BlobReference> ProcessLargePayloadActivity(
[ActivityTrigger] BlobReference inputReference)
{
string connectionString =
Environment.GetEnvironmentVariable("PAYLOAD_STORAGE_CONNECTION_STRING")
?? throw new InvalidOperationException("Payload storage is not configured.");
BlobServiceClient service = new BlobServiceClient(connectionString);
BlobClient inputBlob = service
.GetBlobContainerClient(inputReference.Container)
.GetBlobClient(inputReference.BlobName);
BinaryData inputData = (await inputBlob.DownloadContentAsync()).Value.Content;
BlobContainerClient outputContainer =
service.GetBlobContainerClient("processed-payloads");
await outputContainer.CreateIfNotExistsAsync();
string outputName = $"processed/{Guid.NewGuid():N}.json";
await outputContainer.GetBlobClient(outputName)
.UploadAsync(inputData, overwrite: true);
return new BlobReference(outputContainer.Name, outputName);
}
}
If parallel activities produce multiple large results, return a list of references and pass that list to a final aggregation activity. The aggregation activity should load and combine the payloads and then write one final output blob. Don't load or concatenate the large results in the orchestrator.
Work with sensitive data
Inputs and outputs (including exceptions) to and from Durable Functions APIs are durably persisted in your storage provider of choice. If those inputs, outputs, or exceptions contain sensitive data (such as secrets, connection strings, or personally identifiable information), anyone with read access to your storage provider's resources could obtain them.
To safely handle sensitive data, fetch that data within activity functions from either Azure Key Vault or environment variables, and never communicate that data directly to or from orchestrators or entities. This approach helps prevent sensitive data from leaking into your storage resources.
Similarly, write access to storage resources must be tightly controlled, as tampered data in storage could alter orchestration behavior. For more information about securing task hub storage, see Secure your task hub storage.
Tip
This guidance also applies to the CallHttp orchestrator API, which persists its request and response payloads in storage. If your target HTTP endpoints require authentication, implement the HTTP call inside an activity, or use the built-in managed identity support offered by CallHttp, which doesn't persist credentials to storage.
Note
Avoid logging data containing secrets as anyone with read access to your logs (for example in Application Insights) could obtain those secrets.
Encryption at rest
When using the Azure Storage provider, all data is automatically encrypted at rest. However, anyone with access to the storage account can read the data in its unencrypted form. If you need stronger protection for sensitive data, consider first encrypting the data using your own encryption keys so that the data is persisted in its pre-encrypted form.
Alternatively, .NET users have the option of implementing custom serialization providers that provide automatic encryption. An example of custom serialization with encryption can be found in this GitHub sample.
Note
If you decide to implement application-level encryption, be aware that orchestrations and entities can exist for indefinite amounts of time. This matters when it comes time to rotate your encryption keys because an orchestration or entities may run longer than your key rotation policy. If a key rotation happens, the key used to encrypt your data may no longer be available to decrypt it the next time your orchestration or entity executes. Custom encryption is therefore recommended only when orchestrations and entities are expected to run for relatively short periods of time.
Secure your task hub storage
The storage backend that hosts your task hub is a critical trust boundary. The Durable Task Framework trusts data it reads from storage during orchestration replay and message processing. Anyone with write access to the task hub storage can tamper with orchestration state, pending messages, or stored payloads. This may alter application behavior, trigger unintended actions, or achieve remote code execution within the context of your function app.
Important
Don't expose your task hub storage credentials or grant write access to untrusted parties. Write access to task hub storage can be used to alter application behavior, including triggering arbitrary code execution.
Shared responsibility
Securing the storage backend is your responsibility, the same as securing any database that stores application state or code. The Durable Task Framework doesn't perform integrity verification on stored data, so it relies on the storage layer's access controls to prevent unauthorized modifications.
| Backend | Security responsibility | Guidance |
|---|---|---|
| Durable Task Scheduler | Microsoft manages the underlying storage backend. You manage identities, task hub access, and app-level security. | Preferred default for new Durable Functions apps. |
| Azure Storage and other BYO providers | You manage the storage account or database and its security controls. | Good fit for existing workloads or deployments that already rely on Azure Storage. |
Note
Don't share a single task hub between untrusted tenants. A task hub doesn't enforce access boundaries between its users, so any tenant that can read or write to the task hub can affect all orchestrations and entities within it. Similarly, don't rely on separate task hubs within the same backend as a security boundary. While Durable Task Scheduler supports RBAC scoped to individual task hubs, network controls such as IP allow lists and private endpoints apply only at the scheduler level, so task hubs within a scheduler aren't a security isolation boundary. The same is true for BYO storage providers—any tenant with access to the storage account or database can reach all task hubs on that backend. When you need security isolation between tenants, provision separate infrastructure for each tenant: separate storage accounts or databases for BYO providers, or separate Durable Task Scheduler instances.
Storage hardening checklist
Apply the following best practices to protect your task hub storage:
Use identity-based connections for the backend you choose.
- With Durable Task Scheduler, rely on managed identities and RBAC for the scheduler and task hubs.
- With Azure Storage and other BYO providers, prefer managed identity over connection strings where possible.
Apply least-privilege RBAC roles. Grant only the minimum permissions required. Avoid granting broad storage access to users or services that don't need it.
Restrict network access to your storage account or scheduler deployment by using private endpoints or service endpoints. This restriction helps prevent unauthorized network-level access to task hub data.
Monitor storage access by enabling Azure Monitor resource logs for your storage account, especially the
StorageWritelog category. Route these logs to a destination outside of the monitored storage account, such as Log Analytics, so they can't be tampered with. See Storage logs.Rotate credentials regularly if you use connection strings. Treat storage account keys with the same care as any other high-privilege credential.
Consider a managed storage backend. Durable Task Scheduler handles storage security automatically, including authentication, RBAC, and network isolation, while Azure Storage offers explicit storage control.
Customize serialization and deserialization
Serialization customization options vary by language. Select your language tab to see the available options.
.NET Isolated and System.Text.Json
Durable Functions running in the .NET Isolated worker process use the same object serializer configured globally for your Azure Functions app (see WorkerOptions). This serializer is System.Text.Json by default rather than Newtonsoft.Json. Any changes to WorkerOptions.Serializer transitively apply to Durable Functions.
For more information on the built-in support for JSON serialization in .NET, see the JSON serialization and deserialization in .NET overview documentation.