Node.js v4 Azure Function POST returns empty 404 while GET works on the same Flex Consumption app

Ade Jabar 0 Reputation points
2026-10-12T00:11:24.7633333+00:00

Environment:

  • JavaScript, Node.js 24
  • Azure Functions programming model v4
  • @azure/functions 4.16.5
  • Linux Flex Consumption, UK South
  • Easy Auth requiring authenticated application tokens

CREATE registration:

app.http("guardCreateObject", {

methods: ["POST"],

authLevel: "anonymous",

route: "guard/objects",

handler: async (request, context) => { /* handler */ }

});

host.json has routePrefix: "api".

Observed during a controlled test on 11 October 2026:

  1. Management-plane checks reported the CREATE disable setting as false and the function's isDisabled property as false.
  2. The registered invoke URL matched the requested URL exactly.
  3. An authenticated GET to another route on the same hostname returned the application's expected JSON response.
  4. Immediately afterwards, POST /api/guard/objects returned: HTTP 404 Server: Kestrel Content-Length: 0 Empty response body.

The POST used application/json with:

{"correlation_id":"cfos-body-evidence"}

The handler should return JSON 428 for the deliberately omitted

If-None-Match header, or JSON 400 if body parsing fails. Both checks

precede storage access. The reviewed CREATE handler has no 404 branch.

The function is listed in Azure with POST and route guard/objects.

All write functions were restored disabled after the test.

What could explain the discrepancy between management-plane enablement

and the runtime POST response? What read-only host evidence would

distinguish routing, stale disable state, or runtime metadata issues?

If trigger synchronisation is appropriate, please identify the

supported operation specifically for Flex Consumption.

Azure Functions
Azure Functions

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

0 comments No comments

2 answers

Sort by: Oldest
  1. Rukshan edirisinghe 1,560 Reputation points
    2026-10-12T08:23:02.3266667+00:00

    Hi @Ade Jabar

    A detail in your post stands out: the write functions were disabled before the test and switched back to disabled right after. Enable and Disable in Azure Functions are just the AzureWebJobs.<FUNCTION_NAME>.Disabled app setting, and Microsoft's docs say changing an app setting restarts the function app by default. If your POST landed on a host that was still running with the old "disabled" setting, an empty Kestrel 404 is what you'd expect, since the function simply isn't loaded there. That fits your result even though the management plane already showed it as enabled.

    To confirm it from the host side, without changing anything:

    1. In Application Insights, look at the traces around the test. Find the most recent host startup before your POST and check whether guardCreateObject is in the list of functions that host loaded, or whether it was logged as disabled.
    2. Compare the timestamp of that host startup with the time of the app setting change. If the POST came first, it's a timing issue and not a routing or metadata problem.
    3. For the next test, enable the function, wait until you see a new host startup in the traces with the function loaded, and only then send the POST.

    If the host clearly loaded the function and the POST still gets an empty 404, that points to something deeper, and a support case with the invocation timestamps would be the next step.

    Roughly how many seconds passed between enabling the function and sending the POST?

    If this resolved your issue, please consider accepting it as the answer. If the traces show something different, let me know and I'll be happy to keep helping.

    Reference:

    Was this answer helpful?

    0 comments No comments

  2. Fabian Zankl 345 Reputation points
    2026-10-12T14:52:31.62+00:00

    Hi @Ade Jabar ,

    You re-enabled the CREATE function through its app setting, the management plane reports it as enabled, a GET to another route on the same app answers normally, and yet the POST to api/guard/objects returns an empty 404 from Kestrel without reaching the handler. The successful GET confirms that the app can serve requests, but it does not establish that the CREATE function is registered or enabled on the instance that handled the POST. @Rukshan edirisinghe 's answer covers the restart timing: the POST most likely reached a host that still ran with the previous disable setting. On Flex Consumption, three further points apply.

    Why the management plane and the runtime disagree on Flex Consumption

    The values you checked (the AzureWebJobs.guardCreateObject.Disabled app setting and the management-plane isDisabled value) do not establish which configuration a particular running host instance has loaded. On Flex Consumption, changing an app setting is a site update, and the site update strategies article describes both strategies as asynchronous. With the default Recreate strategy, new instances are provisioned while original instances might still be deprovisioning in the background. With RollingUpdate, instances are drained and replaced in batches at regular intervals. For both, the article states that there is no built-in completion signal, and it provides a best-effort KQL query that compares the instances active before the update with the instances active now. Running that query around the time of your test can show whether instances from before the update were still active. It does not, by itself, identify which instance handled the failed POST.

    A test to narrow down disable state versus routing

    The disable function documentation states that a disabled function can still be called with the master key, and that a request with any other key receives HTTP 404. You can use this as a controlled diagnostic without enabling anything: while the function is disabled and no settings change is in progress, send the same POST twice, once without a key and once with the master key in the x-functions-key header (both with the Easy Auth token). As you describe it, your handler returns 428 for the missing If-None-Match header before any storage access, so neither call writes anything. The master key grants elevated access to the whole app, so use it only for this test and keep it out of logs, screenshots, and posts.

    • Empty 404 without the key, 428 with it: the function is registered and executable, and the disable setting alone produces exactly the response signature you observed. This does not prove that the original 404 had the same cause, but it makes a stale disable state the explanation that matches the evidence.
    • 404 with the master key as well: the request did not reach the handler despite the master key. Assuming the key was accepted, this points toward registration, routing, or the deployed package content rather than the disable gate alone. For the v4 model, one concrete check is whether the main entry in package.json includes the file that calls app.http.

    Trigger synchronisation

    A manual trigger sync is unlikely to help here. The trigger syncing section describes it as updating trigger metadata after a deployment adds, removes, or changes functions; it is not documented as a mechanism for reloading app settings on running host instances, which is where a stale disable state would sit. The scenarios that require a manual sync (external package URL, local Git, and FTPS) do not apply to Flex Consumption, which uses its own package deployment. The documented manual operation, the syncfunctiontriggers POST against the Microsoft.Web/sites resource, is described for function apps in general; the article does not name a separate operation for Flex Consumption.

    References


    Drafted with help from Claude, disclosed per the Q&A AI usage policy. Documented: Flex Consumption site update behavior, master-key access to disabled functions, and trigger sync requirements (Learn). Inference from the symptoms: that the POST was served by an instance with the previous configuration; the master-key test is proposed to narrow this down, not to prove it.

    Was this answer helpful?

    0 comments No comments

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.