Timer Trigger¶
Note: This guide focuses primarily on HTTP triggers. The timer trigger is covered here for future extensibility and is commonly used alongside HTTP APIs for scheduled background tasks like data cleanup, report generation, and health pings.
Overview¶
A timer trigger fires on a schedule defined by an NCRONTAB expression. Unlike HTTP triggers, timer triggers do not have an endpoint URL — they run automatically based on the schedule.
flowchart LR
SCHED[NCRONTAB Schedule] --> RUNTIME[Functions Runtime Scheduler]
RUNTIME --> TRIGGER[Timer Trigger Invocation]
TRIGGER --> TASK[Background Task Execution]
TASK --> LOGS[Application Insights Logs] Timer Trigger v2 Syntax¶
import azure.functions as func
import logging
from datetime import datetime, timezone
bp = func.Blueprint()
@bp.timer_trigger(schedule="0 */5 * * * *", arg_name="timer", run_on_startup=False)
def scheduled_task(timer: func.TimerRequest) -> None:
"""Runs every 5 minutes."""
utc_now = datetime.now(timezone.utc).isoformat()
logging.info(f"Timer trigger fired at {utc_now}")
if timer.past_due:
logging.warning("Timer is past due — this execution was delayed")
# Do your scheduled work here
logging.info("Scheduled task completed")
Register the blueprint in function_app.py:
import azure.functions as func
from blueprints.scheduled import bp as scheduled_bp
app = func.FunctionApp()
app.register_blueprint(scheduled_bp)
NCRONTAB Expression Syntax¶
Azure Functions uses a six-field NCRONTAB expression (includes seconds):
| Field | Allowed Values | Special Characters |
|---|---|---|
| Second | 0–59 | , - * / |
| Minute | 0–59 | , - * / |
| Hour | 0–23 | , - * / |
| Day | 1–31 | , - * / |
| Month | 1–12 | , - * / |
| Day of Week | 0–6 (0 = Sunday) | , - * / |
Common Schedule Examples¶
| Schedule | NCRONTAB Expression | Description |
|---|---|---|
| Every 5 minutes | 0 */5 * * * * | At second 0 of every 5th minute |
| Every hour | 0 0 * * * * | At the top of every hour |
| Every 4 hours | 0 0 */4 * * * | At minute 0 of every 4th hour |
| Daily at midnight UTC | 0 0 0 * * * | Once a day at 00:00 UTC |
| Daily at 2:30 AM UTC | 0 30 2 * * * | Once a day at 02:30 UTC |
| Weekdays at 9 AM UTC | 0 0 9 * * 1-5 | Monday through Friday at 09:00 UTC |
| Every 30 seconds | */30 * * * * * | Twice per minute |
| First of the month at midnight | 0 0 0 1 * * | Monthly on the 1st |
Important: Schedules are interpreted in UTC unless you configure
WEBSITE_TIME_ZONE. Timezone-aware schedules are supported on Windows plans and Linux Premium/Dedicated plans. Linux Consumption and Linux Flex Consumption do not supportWEBSITE_TIME_ZONEfor timer triggers.
Timer State and Past Due¶
The timer.past_due property indicates whether the current invocation is late. This can happen when:
- The function app was stopped or scaled to zero
- A previous execution took longer than the schedule interval
- The app was restarted during a scheduled window
@bp.timer_trigger(schedule="0 0 * * * *", arg_name="timer", run_on_startup=False)
def hourly_report(timer: func.TimerRequest) -> None:
"""Generate an hourly report."""
if timer.past_due:
logging.warning("Hourly report is running late")
# Generate report
report = generate_report()
logging.info(f"Report generated: {report['summary']}")
run_on_startup¶
Set run_on_startup=True to execute the timer function immediately when the function app starts, in addition to the normal schedule:
@bp.timer_trigger(schedule="0 0 0 * * *", arg_name="timer", run_on_startup=True)
def daily_cleanup(timer: func.TimerRequest) -> None:
"""Clean up expired data daily and on startup."""
logging.info("Running cleanup task")
# ... cleanup logic ...
Caution: Avoid
run_on_startup=Truein production on the Consumption plan. Every cold start triggers the function, which can lead to unexpected executions during scale-out events.
Practical Examples¶
Keep-Warm Ping¶
Reduce cold-start likelihood on the Consumption plan by pinging your own health endpoint:
import httpx
import os
@bp.timer_trigger(schedule="0 */4 * * * *", arg_name="timer", run_on_startup=False)
def keep_warm(timer: func.TimerRequest) -> None:
"""Ping the health endpoint every 4 minutes to reduce cold-start frequency."""
base_url = os.environ.get("WEBSITE_HOSTNAME", "localhost:7071")
scheme = "https" if "azurewebsites" in base_url else "http"
url = f"{scheme}://{base_url}/api/health"
try:
resp = httpx.get(url, timeout=10.0)
logging.info(f"Keep-warm ping: {resp.status_code}")
except Exception as e:
logging.error(f"Keep-warm ping failed: {e}")
Data Cleanup¶
Remove expired records on a daily schedule:
@bp.timer_trigger(schedule="0 0 2 * * *", arg_name="timer", run_on_startup=False)
def cleanup_expired(timer: func.TimerRequest) -> None:
"""Delete expired items daily at 2 AM UTC."""
from azure.cosmos import CosmosClient
from azure.identity import DefaultAzureCredential
import os
endpoint = os.environ["COSMOS_ENDPOINT"]
client = CosmosClient(endpoint, DefaultAzureCredential())
container = client.get_database_client("appdb").get_container_client("items")
now = datetime.now(timezone.utc).isoformat()
query = "SELECT * FROM c WHERE c.expires_at < @now"
expired = list(container.query_items(
query=query,
parameters=[{"name": "@now", "value": now}],
enable_cross_partition_query=True
))
for item in expired:
container.delete_item(item["id"], partition_key=item["category"])
logging.info(f"Deleted expired item: {item['id']}")
logging.info(f"Cleanup complete: {len(expired)} items removed")
Timer Trigger Requirements¶
Timer triggers require Azure Storage (AzureWebJobsStorage) to maintain schedule state. The runtime stores a lease blob to ensure only one instance processes each timer event. This means:
AzureWebJobsStoragemust be configured (not optional for timer triggers)- If using Azurite locally, it must be running