Problema

En entornos de producción es frecuente que una Azure Function expuesta vía HTTP reciba archivos desde un cliente web. Cuando el tamaño del payload supera los 30 MB, los desarrolladores observan respuestas intermitentes: a veces la carga finaliza con éxito, otras veces la petición termina con error 500 o 408. El comportamiento es aleatorio, no depende del cliente ni del tipo de archivo, y ocurre solo en producción; en local la función acepta archivos de 100 MB sin inconvenientes. Este patrón indica que algo en la cadena de ejecución (plano de Azure Functions, red, autenticación o Blob Storage) está imponiendo restricciones que no se reflejan en el entorno de desarrollo.

Causa

1. Límite de tamaño de petición HTTP en Azure Functions

En el plan Consumption y Premium, la plataforma impone un límite de 100 MB para la carga completa del cuerpo HTTP. Sin embargo, el gateway de Azure Front Door / Application Gateway que suele estar delante de la función puede tener un límite menor (por ejemplo 30 MB). Si la arquitectura incluye un Azure API Management o App Service Front Door, su política de tamaño máximo sobrescribe el límite de la función y genera errores cuando se supera.

2. Timeout de ejecución

Una función que procesa el stream del archivo y lo escribe en Blob Storage puede tardar varios segundos. En Consumption el tiempo máximo es 5 min (configurable a 10 min) y en Premium el límite es 60 min. Si la función está configurada con el valor por defecto (5 min) y la red o el cliente son lentos, la ejecución se corta antes de completar la escritura, provocando fallos intermitentes.

3. Configuración de host.json para http y maxConcurrentRequests

El archivo host.json controla el comportamiento del runtime. Si maxRequestBodySize está definido (por ejemplo 30 MB) o si maxConcurrentRequests está bajo, la función rechaza o retrasa peticiones grandes bajo carga.

4. Credenciales y acceso a Blob Storage

Cuando la función usa DefaultAzureCredential, el token se renueva automáticamente. En entornos con Managed Identity restringido, un refresco fallido del token durante una carga larga puede producir un 401 o 403 inesperado. En local, la credencial de desarrollador siempre está disponible, lo que explica la diferencia de comportamiento.

5. Configuración de red del Storage Account

Si el Storage Account tiene firewall o private endpoints y la función está en una VNet sin acceso directo, el intento de escritura puede fallar después de varios segundos, generando errores que aparecen solo en producción.

Solución

Paso 1: Verificar y ampliar el límite de tamaño en el gateway

  • Si usas Azure Front Door, revisa la política frontendEndpointcustomHttpsConfigurationmaxRequestBodySize. Aumenta el valor a 100 MB o más.
  • En API Management, revisa la política <set-body> o el atributo max-request-body-size y ajústalo.
  • En App Service (cuando la Function se ejecuta bajo un App Service Plan), modifica la configuración WEBSITES_CONTAINER_START_TIME_LIMIT y WEBSITES_MAX_HTTP_CONTENT_LENGTH mediante Azure CLI.
az functionapp config appsettings set \
  --name MyFunctionApp \
  --resource-group MyRG \
  --settings WEBSITES_MAX_HTTP_CONTENT_LENGTH=104857600

Paso 2: Ajustar timeout de la función

En el plan Consumption, habilita el timeout extendido (máximo 10 min). En Premium, define un timeout mayor si la carga es lenta.

az functionapp config set \
  --name MyFunctionApp \
  --resource-group MyRG \
  --timeout 600   # 600 segundos = 10 minutos

Paso 3: Configurar host.json para aceptar cuerpos grandes

Añade o modifica la sección http:

{
  "version": "2.0",
  "extensions": {
    "http": {
      "maxRequestBodySize": 104857600,
      "routePrefix": "api"
    }
  }
}

Esto eleva el límite interno del runtime a 100 MB.

Paso 4: Implementar streaming directo a Blob Storage

En lugar de cargar el archivo completo en memoria, usa BlobClient.UploadAsync(stream, cancellationToken) con TransferOptions que habilitan multipart streaming. De este modo la función solo mantiene un buffer pequeño y reduce la probabilidad de timeout.

var blobClient = new BlobContainerClient(storageConnection, "uploads")
    .GetBlobClient(fileName);

var options = new BlobUploadOptions
{
    TransferOptions = new StorageTransferOptions
    {
        MaximumConcurrency = 4,
        MaximumTransferSize = 4 * 1024 * 1024 // 4 MiB
    }
};

await blobClient.UploadAsync(request.Body, options, cancellationToken);

Paso 5: Asegurar que la Managed Identity tenga permisos persistentes

Verifica que la identidad asignada al Function App tenga el rol Storage Blob Data Contributor en el scope del storage account. Además, habilita el token cache para evitar refrescos durante la operación.

az role assignment create \
  --assignee <principalId> \
  --role "Storage Blob Data Contributor" \
  --scope /subscriptions/<subId>/resourceGroups/<rg>/providers/Microsoft.Storage/storageAccounts/<account>

Paso 6: Revisar la red del Storage Account

Si el storage está detrás de un private endpoint, confirma que la VNet de la Function App tiene la ruta correcta y que los NSG permiten tráfico saliente a *.blob.core.windows.net en puerto 443.

Cuándo aplicar esta solución

  • Síntomas: errores 500/408 al subir archivos >30 MB, comportamiento aleatorio, logs de Function indican “Request body too large” o “Function timeout”.
  • Entorno: Azure Functions en HTTP trigger, con Front Door, API Management o App Service como front‑end, y Blob Storage como destino.
  • No aplica: cuando el problema se origina en el cliente (por ejemplo, navegadores que limitan FormData) o cuando el archivo nunca supera el límite de 30 MB.

Código

# 1. Aumentar límite de cuerpo HTTP en la Function App
az functionapp config appsettings set \
  --name MyFunctionApp \
  --resource-group MyRG \
  --settings WEBSITES_MAX_HTTP_CONTENT_LENGTH=104857600

# 2. Extender timeout (solo Consumption)
az functionapp config set \
  --name MyFunctionApp \
  --resource-group MyRG \
  --timeout 600

# 3. Asignar rol de acceso al storage
az role assignment create \
  --assignee $(az functionapp identity show -g MyRG -n MyFunctionApp --query principalId -o tsv) \
  --role "Storage Blob Data Contributor" \
  --scope /subscriptions/<subId>/resourceGroups/MyRG/providers/Microsoft.Storage/storageAccounts/myaccount

Verificación

  1. Prueba de carga: usa curl con --data-binary @bigfile.bin y verifica que la respuesta sea 200 en menos del timeout configurado.
  2. Logs de Application Insights: busca eventos FunctionExecutionFailed y revisa la propiedad exceptionMessage. Debe desaparecer la referencia a “Request body too large” o “Operation timed out”.
  3. Métricas de Storage: confirma que el número de PutBlob aumenta y que no aparecen errores de autorización.
  4. Gateway: revisa en el portal que la política de tamaño de cuerpo muestra el nuevo valor.

Notas adicionales

  • En planes Premium o Dedicated, el límite de 100 MB sigue vigente; si necesitas superar ese umbral, considera usar Azure Blob Storage SAS URL y que el cliente suba directamente, evitando pasar por la Function.
  • El uso de Azure Front Door con WAF puede bloquear peticiones grandes si hay una regla personalizada; revisa las reglas activas.
  • Cuando la Function está bajo alta concurrencia, el parámetro maxConcurrentRequests en host.json puede provocar throttling. Ajusta según la capacidad de tu plan.
  • Mantén la versión del runtime en al menos 4.x para que maxRequestBodySize sea respetado; versiones anteriores ignoran la configuración.