Azure Function does not recognise or trigger from a once-daily TimerTrigger, but a trigger every 2 or 3 minutes works

Adam 0 Reputation points
2026-08-26T16:31:09.2566667+00:00

I'm attempting to set my .NET 10 function app to run every day at midnight using the following TimerTrigger NCRONTAB expression:

[Function("Reconciliation")]
public async Task Run(
        // Trigger the function every 24 hours at midnight
        [TimerTrigger("0 0 0 * * *")] TimerInfo timer)

When I use the above expression, the Function app appears to deploy, and I can see the two Functions listed in the Overview page, but the app never triggers on the schedule.

The traces Application Insights logs indicate that the Function "Reconciliation" is found, but the trigger schedule is never registered (there is no log message like: "The next X occurrences of the 'Reconciliation' schedule..."). The status file of the TimerTrigger is present, but not updated with the expected lastrun, nextrun, lastupdated values.

azure-webjobs-hosts
└── timers
    └── <appId>
        └── host.Functions.Reconciliation
            └── status

The strange thing is that if I set the TimerTrigger NCRONTAB expression to the following (every 2 minutes), it works and will trigger every two minutes as expected. The status file of the TimerTrigger is present and is updated with the expected lastrun, nextrun, lastupdated values each time the Function triggers:

[Function("Reconciliation")]
public async Task Run(
        // Trigger the function every 24 hours at midnight
        [TimerTrigger("0 */2 * * * *")] TimerInfo timer)

To reiterate, the only difference in the code between a successful and unsuccessful deployment is the TimerTrigger schedule.

There are differences in the traces logs in Application Insights between the functioning and non-functioning deployments. When I set the timer to every two minutes, the logs read like this (abridged and timestamps removed):

- Generating 2 job function(s)
- Worker process started and initialized.
- Found the following functions:
Host.Functions.Webhook
Host.Functions.Reconciliation
- Function group target is function:reconciliation
- Mapped function route 'api/webhooks/' [post] to 'Webhook'
- Initializing function HTTP routes
- Host initialized (13ms)
- The next 5 occurrences of the 'Reconciliation' schedule (Cron: '0 2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58 * * * * *') will be:
08/25/2026 13:54:00Z
08/25/2026 13:56:00Z
08/25/2026 13:58:00Z
08/25/2026 14:00:00Z
08/25/2026 14:02:00Z
- Executing 'Functions.Reconciliation' (Reason='Timer fired at 2026-08-25T13:53:20.4445575+00:00', Id=aa057fd8-7467-4c59-9e08-98578fcdfbfc)
- Trigger Details: UnscheduledInvocationReason: IsPastDue, OriginalSchedule: 2026-08-25T11:12:00.0000000+00:00, ScheduleStatus: {"Last":"2026-08-25T11:10:00+00:00","Next":"2026-08-25T11:12:00+00:00","LastUpdated":"2026-08-25T11:10:00+00:00"}
- Host started (315ms)
- Job host started

In comparison, the deployment of the app using a 24 hour timer has the following lines missing:

- Function group target is function:reconciliation (instead, it says "Function group target is http")
- The next 5 occurrences of the 'Reconciliation' schedule...
- Executing 'Functions.Reconciliation'
- Trigger Details: 

A short time after deployment, the app shows these logs, indicating the host has stopped:

- DrainMode mode enabled
- Calling StopAsync on the registered listeners
- Stopping the listener 'Microsoft.Azure.WebJobs.Script.Description.FunctionGroupListenerDecorator+NoOpListener' for function 'Reconciliation'
- Call to StopAsync complete, registered listeners are now stopped
- Stopping JobHost
- Job host stopped

After this, neither the HTTP trigger or the Timer trigger can be invoked. I have waited over 24 hours and can confirm that the Timer Trigger does not fire. As soon as I deploy the app build with the 2 minute timer, the Function App works again.

I have tried multiple troubleshooting steps, including:

  • Restarting the Function App, before and after deployments
  • Refreshing the Function App, before and after deployments
  • Shutting down the Function App and starting it again, before and after deployments
  • Syncing the Function App triggers via the API endpoint

Function App details

Deployment method: Manual from the Azure portal

Functions: "Reconciliation" (Timer trigger) and "Webhook" (HTTP trigger)

Stack: .NET 10 Isolated

AzureWebStorage authentication: Managed identity with the correct permissions

Successful invocation based on a Timer trigger every 2 minutes

UTC execution time: 2026-08-25T13:53:20.4445575+00:00

Execution Id: aa057fd8-7467-4c59-9e08-98578fcdfbfc

Region: UK South

Thanks!

Azure Functions
Azure Functions

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

0 comments No comments

3 answers

Sort by: Most helpful
  1. Sina Salam 31,456 Reputation points Volunteer Moderator
    2026-08-26T18:00:32.9033333+00:00

    Hello Adam,

    Welcome to the Microsoft Q&A and thank you for posting your questions here.

    I understand that your Azure Function does not recognize or trigger from a once-daily TimerTrigger, but a trigger every 2 or 3 minutes works.

    This is daily reconciliation job and is not firing and that the HTTP webhook also becomes unavailable after the Function App host stops. This affects both scheduled reconciliation and webhook processing, so the important issue is not the NCRONTAB expression alone. It is whether the app can start a new instance when the daily timer or an HTTP request arrives.

    The following expression is valid: [TimerTrigger("0 0 0 * * *")] Azure Functions timer schedules use six-field NCRONTAB syntax, including the seconds field. This expression runs daily at 00:00:00, normally in UTC. The working expression 0 */2 * * * * runs every two minutes. Therefore, the daily expression does not need to be changed to a 24-hour interval. .NET isolated TimerTrigger documentation Also, .NET 10 is supported by Azure Functions runtime 4.x when the application uses the isolated-worker model, so downgrading the application is not indicated by the available evidence..NET isolated-worker guidance

    The log shows that abnormal behavior is that the daily timer does not produce an invocation and, according to the question, the HTTP endpoint cannot reactivate the application after the host stops. That points to a broader cold-start or scale-from-zero problem rather than a malformed timer expression.

    The customer-visible logs are not sufficient to distinguish between failed specialization, inaccessible deployment or host storage, worker-startup failure, stale trigger metadata, and an Azure platform allocation issue. The following procedure isolates those possibilities without repeating the restart and trigger-synchronization actions already completed.

    What you need to do is to

    1. Keep the daily schedule in configuration by using an application setting for the schedule. Do not use RunOnStartup = true as a workaround. TimerTrigger samples use an application setting for the schedule. - https://learn.microsoft.com/en-us/samples/azure-samples/functions-quickstart-dotnet-azd-timer/starter-timer-trigger-csharp/ Also check that this setting is not present with a value of true: AzureWebJobs.Reconciliation.Disabled
    2. Validate the application as an isolated-worker project and do not mix those packages with the in-process package families such as Microsoft.NET.Sdk.Functions and Microsoft.Azure.WebJobs.Extensions. - https://learn.microsoft.com/en-us/azure/azure-functions/dotnet-isolated-process-guide, https://learn.microsoft.com/en-us/azure/azure-functions/dotnet-isolated-in-process-differences then create a clean release build.
    3. Because the current deployment is described only as “manual from the Azure portal,” publish the validated build once using Core Tools: func azure functionapp publish <FUNCTION_APP_NAME> Azure Functions deployment technologies and do not repeat restarts or manual trigger synchronization after this deployment unless a specific diagnostic result requires it.
    4. After the app has stopped or scaled down, invoke the timer function through the administrative endpoint. The master Manually run a non-HTTP function and also send a direct request to the Webhook endpoint after scale-down and record the HTTP status, response body, and UTC timestamp.
    5. Test a near-future wall-clock schedule by temporarily set the schedule to a specific UTC time five to ten minutes ahead. For example, to test at 18:35 UTC: ReconciliationSchedule = 0 35 18 * * * do not use the every-two-minute expression for this test. A specific future occurrence more closely tests the same wake-up behavior required by the midnight schedule.
    6. If the clean deployment still fails, deploy the exact same published package to a new test Function App with equivalent: Hosting plan. Region. Runtime. Application settings. Managed identity. Storage access. and Network configuration. Do not rebuild the package between the two deployments.

    I hope this is helpful. Please! Do not hesitate to let me know if you have any other questions, steps or clarifications.


    Please do not close the thread by upvoting and accepting the answer if any part of it is helpful.

    Was this answer helpful?

    0 comments No comments

  2. Fabian 80 Reputation points
    2026-08-26T17:57:44.6366667+00:00

    Hi @Adam ,

    Your logs point at the hosting plan rather than the CRON expression. The lines "Function group target is ..." and "FunctionGroupListenerDecorator+NoOpListener" only appear on the Flex Consumption plan, so I assume that is where the app runs. Please correct me if not.

    What the log lines mean

    Flex Consumption uses per-function scaling. HTTP-triggered functions scale together as one group called http. Apart from the Blob (Event Grid) and Durable groups, every other trigger, including your timer, gets its own group named function:<FunctionName>. This is why the working deployment logs Function group target is function:reconciliation. See Per-function scaling.

    Each instance is started for exactly one group. Inside the host, FunctionGroupListenerDecorator compares the instance's target group with each function's group and wraps every function that does not belong to that group in a NoOpListener, whose StartAsync does nothing. The code is in FunctionGroupListenerDecorator.cs in the host repository.

    So on the failing deployment the instance you are looking at is an http instance. On that instance the timer listener is intentionally a no-op. That explains three of your observations at once:

    • no "The next 5 occurrences of the 'Reconciliation' schedule" line, because the real timer listener never starts there
    • the status blob exists but is never updated, because nothing runs the schedule monitor
    • DrainMode mode enabled followed by Job host stopped shortly after deployment, because an idle http instance is scaled back to zero. That part is normal.

    The actual failure is one level up. The timer can only fire on a function:reconciliation instance, and creating instances is the job of the scale controller. It reads the timer's CRON schedule for that purpose (Event-driven scaling, AZFD0015). In your daily-schedule deployment that instance never appears. With the two-minute schedule the platform keeps allocating it, so the timer appears to work. The host registers the schedule correctly in both builds. What differs is whether the platform allocates an instance for the timer group at the scheduled time.

    A side note on time zones: WEBSITE_TIME_ZONE and TZ aren't supported on Flex Consumption, so 0 0 0 * * * is midnight UTC. That doesn't explain a 24-hour gap, but when you check for the run, look at 00:00 UTC, which is 01:00 in London during British Summer Time.

    What to check

    1. Confirm the plan is Flex Consumption.
    2. Check which schedule the platform has registered. The scale decision is made from the trigger metadata synced after deployment, which can lag behind or differ from what is in your package:
         az functionapp function show --resource-group <rg> --name <app> --function-name Reconciliation
      
      Look at config.bindings. The schedule value there must be 0 0 0 * * *. If it still shows the two-minute expression, or is missing, the sync didn't take.
    3. Look for failed sync operations. In the portal, open the app's Activity log and filter for the sync triggers operation around the failing deployment. If the activity log is exported to Log Analytics:
         AzureActivity
         | where ResourceId contains "<app>"
         | where OperationNameValue contains "sync"
         | project TimeGenerated, OperationNameValue, ActivityStatusValue, Properties
         | order by TimeGenerated desc
      
      Also open the app in the portal, choose Diagnose and solve problems, and run the "Flex Consumption Deployment" detector for the failing deployment.
    4. Run the decisive test. Pin one always-ready instance to the timer's group and wait for the next midnight:
         az functionapp scale config always-ready set --resource-group <rg> --name <app> --settings function:Reconciliation=1
      
      The same setting is available in the portal under Scale and concurrency, where the function is selected by name from a list. Either way the platform stores the name in lowercase (azure-rest-api-specs #33095), which is why your logs show function:reconciliation. If the timer fires with the pin, your code and schedule are fine and the scale-from-zero path for that group is what fails. Remove the pin afterwards with az functionapp scale config always-ready delete --resource-group <rg> --name <app> --setting-names function:Reconciliation, because always-ready instances are billed continuously. Documentation: Set always ready instance counts. A scheduled HTTP warmup call shortly before midnight doesn't work as a substitute. It starts an http instance, and on that instance the timer listener is the no-op described above, so it can't bring up the function:Reconciliation group.
    5. A faster variant of the same test, which @Sina Salam also suggests in another answer on this thread. Set the schedule to a fixed time about ten minutes ahead, for example 0 40 18 * * * if it is 18:30 UTC now, deploy without a pin, and watch the traces for a Function group target is function:reconciliation line at that time. If it appears, scale-from-zero allocation works and the daily failure needs a different explanation. If it doesn't, the pin test above confirms the diagnosis the same evening.
    6. If step 4 fires, open a support case and include the execution ID and timestamp from the working deployment plus the deployment time of the failing one. There is a March 2025 thread with the same signature (Flex Consumption function timer trigger and Orchestration queue trigger stopped firing): group target had flipped to http, the timer stopped, adding an always-ready instance got it running again temporarily, and the case was then resolved through support without a published root cause. The execution ID and the timestamps give support what they need to look at the scale controller for your app.

    What doesn't fit

    You write that after the drain neither the HTTP trigger nor the timer can be invoked. Scaling an idle http instance to zero is expected, and the next HTTP request should start a new instance. If HTTP requests really fail on the daily-schedule deployment, please post the HTTP status code and the requests table entry, because that would mean the deployment as a whole is unhealthy rather than the timer group alone. Deploying the daily build once with az functionapp deployment source config-zip would also rule out the portal upload path.


    Drafted with help from Claude, disclosed per the Q&A AI usage policy. The mechanism was verified against the linked host source and documentation before posting, and the always-ready and CLI commands against the current Flex Consumption pages.

    Was this answer helpful?

    0 comments No comments

  3. Jerald Felix 18,680 Reputation points Volunteer Moderator
    2026-08-26T16:49:51.38+00:00

    Hello Adam,

    Greetings! Thanks for raising this question in the Q&A forum.

    The log line Function group target is http is the key clue here, and it confirms this is a known scaling behavior on the Flex Consumption plan rather than an issue with your NCRONTAB expression or code. Flex Consumption uses per-function scaling, where the platform groups certain triggers together for scaling decisions. Timer triggers are scaled individually under the convention function:<NAMED_FUNCTION>, which is exactly what you see in your working two-minute deployment (Function group target is function:reconciliation). When your daily timer deployment instead shows Function group target is http, it means the instance that started up was allocated to the HTTP scale group, not to your Reconciliation function's own scale group, so the timer listener for Reconciliation never registers or starts on that instance. That is why there is no "next occurrences" log line and no invocation, and it is also why the host later shows DrainMode and stops, since with no HTTP traffic keeping that instance alive, it scales back down.

    This happens because with a once-a-day schedule, there is a very long gap between invocations. On Flex Consumption, non-HTTP triggers rely on the platform's scale controller to know an instance needs to be running at the right moment, and for infrequent or sparse trigger schedules the platform can fail to allocate or keep a warm instance in the correct function-specific scale group in time for the next scheduled run. Multiple similar reports describe the identical symptom with Flex Consumption on queue, Service Bus, and timer triggers, where an instance is simply never brought up in the correct group unless it is deliberately pinned there.

    Here is how to resolve it:

    Confirm you are on the Flex Consumption plan. Go to your Function App in the Azure portal, then Overview, and check the App Service Plan or Hosting plan value. If it reads Flex Consumption, the behavior above applies directly.

    Configure an Always Ready instance for the Reconciliation function specifically. In the portal, go to your Function App, then Scale and Concurrency, and add an Always Ready instance targeted at function:Reconciliation (not the generic HTTP group). This keeps a dedicated warm instance assigned to that function's own scale group, so the TimerListener is registered and reliably fires on schedule.

    Equivalently through the CLI:

    az functionapp scale config always-ready set \
      --name <your-function-app-name> \
      --resource-group <your-resource-group> \
      --settings "function:Reconciliation=1"
    
    1. Redeploy after making the change and monitor the next scheduled run. After the always-ready instance is applied, check Application Insights traces again for the The next 5 occurrences of the 'Reconciliation' schedule log line at startup, this confirms the TimerListener registered correctly this time.
    2. If cost is a concern for keeping an instance always warm just for a once-daily job, consider moving this specific workload to a Premium or Dedicated (App Service) plan with Always On enabled, since those plans do not depend on the same event-driven scale controller behavior for sparse triggers, or alternatively pair the timer function with a lightweight HTTP-triggered warmup call on a schedule slightly before the timer is due, though pinning always-ready on the function group is the more reliable fix.

    If this answer helps you kindly accept the answer which will help others who have similar questions.

    Best Regards,

    Jerald Felix.

    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.