Azure Functions Linux Consumption: OAuth-uploaded package blob causes 503, identical Shared Key overwrite starts

Cloud Infrastructure Lab 0 Reputation points
2026-10-12T10:10:57.06+00:00

I am troubleshooting a .NET 9 isolated Azure Functions v4 app on Linux Consumption (North Europe). The app runs a few Service Bus/timer functions and uses an external, immutable ZIP package in a private Azure Blob container. Its system-assigned managed identity has Storage Blob Data Reader on that container. WEBSITE_RUN_FROM_PACKAGE points to the blob HTTPS URL, and WEBSITE_RUN_FROM_PACKAGE_BLOB_MI_RESOURCE_ID is SystemAssigned.

For CI/CD, an Azure Pipelines service connection uses workload identity federation (no client secret). Its service principal has Storage Blob Data Contributor only on the package container and Website Contributor only on this Function App. The pipeline performs a clean build, uploads the ZIP with Azure CLI using az storage blob upload --auth-mode login, changes WEBSITE_RUN_FROM_PACKAGE to the new blob URL, and does a full stop/start. The ZIP has host.json at its root and metadata for the expected three functions.

Observed behavior:

  1. With the Entra/OAuth-uploaded blob, the Functions host returns HTTP 503 ('Function host is not running'). Calling syncfunctiontriggers returns HTTP 400 with ServiceUnavailable from the host runtime. No new startup exception appears in Application Insights.
  2. Pointing the app back to an older known-good blob and stopping/starting restores HTTP 200 and processes queued messages.
  3. Uploading the exact same new ZIP bytes to the same container using a storage account key, then pointing the app to that blob and stopping/starting, immediately returns HTTP 200.
  4. Even overwriting the original OAuth-uploaded blob at the SAME blob name with the SAME bytes using the account-key upload makes that URL start successfully. The two versions had identical MD5, length, content type (application/zip), access tier, server-side encryption status, and visible metadata.

The source code and app settings did not change between steps 3 and 4. The storage container is private; we do not want to retain an account key in CI/CD. Is there an Azure Functions/package-mount or Blob Storage authorization difference between an Entra-authenticated upload and a Shared Key upload that could explain this? Which diagnostics should we collect, and what is the recommended secretless deployment route for this hosting plan?

Azure Functions
Azure Functions

An Azure service that provides an event-driven serverless compute platform.

0 comments No comments

2 answers

Sort by: Most helpful
  1. Cloud Infrastructure Lab 0 Reputation points
    2026-10-13T04:52:51.01+00:00

    Resolved for our deployment. Application Insights showed System.UnauthorizedAccessException: Access to the path '/home/site/wwwroot/host.json' is denied during host startup. The ZIP entry for host.json had Unix mode 0600, so the runtime could not read it. We now normalize package file permissions before creating the ZIP (host.json is 0644) and verify the archive in the pipeline. Two consecutive releases using the same workload-identity-federated Azure Pipelines connection and Entra-authenticated blob upload started successfully (HTTP 200, three triggers). We also tested rollback to the previous package and then restored the latest package. No storage account key is used in CI/CD.

    This fixes the operational issue, but it does not prove that OAuth and Shared Key uploads are treated differently by Azure Functions. The earlier same-hash overwrite observation remains unexplained, so we are not claiming an Azure upload-authentication defect.

    Was this answer helpful?

    0 comments No comments

  2. AI answer

    2026-10-12T10:11:24.33+00:00

    For Linux Consumption, the supported secretless pattern is to run from an external package URL in private Blob Storage and let the function app read that package by managed identity. The documented configuration is:

    1. Upload a ready-to-run .zip package to Blob Storage.
    2. Grant the function app identity Storage Blob Data Reader on the package blob or container.
    3. Set WEBSITE_RUN_FROM_PACKAGE to the blob URL.
    4. Optionally set WEBSITE_RUN_FROM_PACKAGE_BLOB_MI_RESOURCE_ID to the user-assigned identity resource ID, or use SystemAssigned/omit it for system-assigned identity.
    5. Restart the function app.
    6. Manually sync triggers after publishing an updated package. If the package contents change without changing the URL, restart before syncing triggers.

    For this hosting plan, that is the recommended deployment route when avoiding SAS-based deployment. Managed identity is recommended over SAS because SAS tokens expire and require maintenance.

    The context does not document any Azure Functions or Blob Storage runtime difference where identical package bytes behave differently solely because the blob was uploaded with Entra/OAuth versus Shared Key. It also does not document a package-mount behavior that depends on the upload authentication method.

    Supported conclusions from the documented behavior:

    • Linux Consumption does not support WEBSITE_RUN_FROM_PACKAGE=1; it must use an external package URL.
    • External package URL is the only supported deployment method for Linux Consumption when a remote build is not wanted.
    • The package must be a ready-to-run .zip with host.json at the root.
    • wwwroot is mounted read-only from the package.
    • After each updated package publication, trigger sync is required.
    • If using ZIP push deployment tooling such as AzureFunctionApp@1, that path sets WEBSITE_RUN_FROM_PACKAGE to a SAS-based URL and there is currently no option in that task to suppress the SAS token. To use managed identity, the package upload and app-setting update must be managed directly.

    Based on the documented guidance, the correct secretless CI/CD pattern for Linux Consumption is:

    1. Build the ready-to-run ZIP.
    2. Upload the ZIP to a private Blob container.
    3. Keep WEBSITE_RUN_FROM_PACKAGE set to the blob URL without SAS.
    4. Ensure the function app managed identity has Storage Blob Data Reader.
    5. Set WEBSITE_RUN_FROM_PACKAGE_BLOB_MI_RESOURCE_ID=SystemAssigned only if needed for clarity.
    6. Restart the app.
    7. Manually sync triggers.

    A practical diagnostic checklist supported by the documentation is:

    1. Verify the app is still using an external package URL, not WEBSITE_RUN_FROM_PACKAGE=1.
    2. Verify the blob URL in WEBSITE_RUN_FROM_PACKAGE points to the intended package.
    3. Verify the function app identity has Storage Blob Data Reader on the blob or container.
    4. If a user-assigned identity is intended, verify WEBSITE_RUN_FROM_PACKAGE_BLOB_MI_RESOURCE_ID is the exact resource ID; otherwise use SystemAssigned or omit it.
    5. Restart the function app after changing app settings or replacing package content.
    6. Manually call trigger sync after each package update.
    7. Confirm the package is a .zip and remains a ready-to-run app package.

    One important limitation is also documented: an existing app already using WEBSITE_RUN_FROM_PACKAGE=1 cannot be changed to external package URL mode; a new function app is required for that transition. That does not apply if the app is already using the blob URL pattern.

    If the app still fails only for OAuth-uploaded blobs after the documented managed-identity configuration, restart, and manual trigger sync, the context provides no documented product behavior explaining that upload-method-specific difference.


    References:

    AI-generated content may be incorrect. Read our transparency notes for more information.

    Was this answer helpful?

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.