Scheduling recurring tasks in App Engine using cron.yaml — configuration, schedule syntax, retry behavior, and best practices.


What are App Engine Cron Jobs?

The App Engine Cron Service lets you configure regularly scheduled tasks that run at defined times or intervals. Cron jobs make HTTP GET requests to URLs within your application on a schedule you define.

You specify the schedule in a cron.yaml file, deploy it alongside your app, and App Engine handles the rest.

Key Insight: App Engine cron jobs are HTTP requests, not shell commands. Your handler must be an HTTP endpoint that executes the task when called. This is different from traditional Linux cron.


Limits

LimitFree AppsPaid Apps
Maximum scheduled tasks20250
Request timeout60 minutes60 minutes
Minimum interval1 second1 second

cron.yaml Reference

Place cron.yaml alongside your app.yaml file.

cron:
  - description: "Daily summary report"
    url: /tasks/summary
    schedule: every 24 hours
 
  - description: "Weekly Monday email"
    url: /mail/weekly
    schedule: every monday 09:00
    timezone: America/New_York
 
  - description: "Cache warmup during business hours"
    url: /tasks/warmup
    schedule: every 10 mins from 08:00 to 18:00
    timezone: America/Chicago
 
  - description: "Quarterly report"
    url: /tasks/quarterly
    schedule: 1 of january,april,july,october 00:00
 
  - description: "Retry example"
    url: /tasks/retry
    schedule: every 10 mins
    retry_parameters:
      job_retry_limit: 5
      min_backoff_seconds: 2.5
      max_doublings: 5
    target: worker-service

Fields

FieldRequiredDescription
urlYesURL path to call (e.g., /tasks/summary). Must be within your app.
scheduleYesSchedule definition (see syntax below).
descriptionNoHuman-readable description. Shown in the Console.
timezoneNoTimezone name (default: UTC). Use IANA names like America/New_York.
targetNoTarget service name. Routes the cron request to a specific service.
retry_parametersNoRetry configuration for failed attempts.

Note: Cron jobs can only call endpoints within the same App Engine application. They cannot call external URLs. For external scheduling, use Cloud Scheduler instead.


Schedule Syntax

Sub-daily intervals (end-time)

The job runs at the specified interval after the previous run completes:

schedule: every 5 minutes
schedule: every 30 mins
schedule: every 2 hours
schedule: every 12 hours

Sub-daily intervals (start-time range)

The job runs at the specified interval within a time window:

schedule: every 5 minutes from 10:00 to 14:00
schedule: every 1 hours from 08:00 to 16:00
schedule: every 2 hours synchronized    # Must divide 24 evenly

Custom intervals

# Daily at a specific time
schedule: every day 00:00
 
# Specific day of the week
schedule: every monday 09:00
schedule: every friday 17:00
 
# Specific day of the month
schedule: 1st,8th,15th,22nd of month 09:00
 
# Specific day and month
schedule: 1st monday of september,october,november 09:00
schedule: 1 of january,april,july,october 00:00
 
# Ordinal day of week in a month
schedule: 2nd wednesday of march 17:00
schedule: 1st,2nd monday,wednesday,friday of may 10:00

Schedule keywords

KeywordExampleMeaning
every N minutesevery 5 minutesEvery N minutes
every N hoursevery 2 hoursEvery N hours
every dayevery day 00:00Once daily
every DAYevery monday 09:00Once per week on that day
Nth of MONTH1 of january 00:00Specific date
from X to Yfrom 08:00 to 18:00Only during time range
synchronizedevery 6 hours synchronizedAligned to midnight (must divide 24)

Retry Parameters

When a cron job fails (returns a non-200 HTTP status), App Engine can retry it automatically:

retry_parameters:
  job_retry_limit: 5           # Max retry attempts (0-5, default: 0)
  job_age_limit: 5d            # Give up after 5 days
  min_backoff_seconds: 2.5     # Wait at least 2.5s before first retry
  max_backoff_seconds: 60      # Wait at most 60s between retries
  max_doublings: 5             # Max times to double the interval

Retry behavior

ParameterDefaultDescription
job_retry_limit0 (no retry)Maximum number of retry attempts
job_age_limitNoneTime limit for retries (e.g., 5d, 12h)
min_backoff_seconds0.1Minimum wait before first retry
max_backoff_seconds300Maximum wait between retries
max_doublings5Max times to double the backoff interval

The retry uses exponential backoff: starting at min_backoff_seconds, doubling each time up to max_doublings, capped at max_backoff_seconds.


Validating Cron Requests

Cron requests are regular HTTP requests to your app. To prevent unauthorized access, validate that requests come from App Engine’s cron service:

Headers

App Engine cron requests include:

# Python (Flask)
from flask import request, abort
 
@app.route("/tasks/summary")
def daily_summary():
    if request.headers.get("X-Appengine-Cron") != "true":
        abort(403)
    # Task logic here
    return "OK"

Origin IP

Cron requests originate from the IP address 0.1.0.2 (or 0.1.0.1 for older gcloud versions).

Tip: Always validate cron requests. Without validation, anyone who knows your /tasks/* URLs could trigger them by visiting the URL directly.


Deployment

# Deploy cron jobs
gcloud app deploy cron.yaml
 
# Delete all cron jobs (set cron.yaml to just "cron:")
gcloud app deploy cron.yaml

You can also manage cron jobs via the Google Cloud Console under App Engine > Cron Jobs.


Gotchas

GotchaDetail
No traffic splittingCron jobs always route to a single version, ignoring traffic splits
dispatch.yaml precedenceIf both dispatch.yaml and target are set, dispatch.yaml wins
At-least-once deliveryCron may call your handler more than once. Make handlers idempotent
Only GET requestsCron jobs send HTTP GET requests, not POST
Internal onlyCannot call external URLs, only endpoints within your app
Target routingThe target field routes to a service’s traffic-serving version
No guarantee of exact timeCron may fire slightly after the scheduled time during high load

Warning: Cron jobs deliver at-least-once. Your handler might be called multiple times for the same scheduled execution. Design your tasks to be idempotent — running them twice should produce the same result as running them once.


Cron vs Cloud Scheduler

FeatureApp Engine CronCloud Scheduler
ScopeApp Engine endpoints onlyAny HTTP endpoint, Pub/Sub, etc.
Configurationcron.yaml in your projectSeparate GCP resource
AuthAutomatic (X-Appengine-Cron header)Configurable (OIDC, OAuth)
Limits20 (free) / 250 (paid)3 (free) / higher quotas
TargetApp Engine services onlyAny URL, Pub/Sub topic, App Engine
HTTP methodGET onlyGET, POST, PUT, DELETE, etc.

Use App Engine Cron for simple scheduled tasks within your app. Use Cloud Scheduler for cross-product or cross-project scheduling, or when you need POST requests.


TL;DR

  • App Engine cron jobs send HTTP GET requests to your app on a schedule.
  • Configure jobs in cron.yaml with url, schedule, and optional timezone, target, and retry_parameters.
  • Supports intervals, specific times, day-of-week, day-of-month, and time-range schedules.
  • Always validate cron requests using the X-Appengine-Cron header.
  • Handlers must be idempotent — at-least-once delivery means possible duplicates.
  • Cron jobs ignore traffic splitting and always route to a single version.
  • Limits: 20 tasks (free), 250 tasks (paid).

Resources

Scheduling Jobs with cron.yaml Official cron.yaml reference and schedule syntax.

Cloud Scheduler Documentation Alternative scheduling service for cross-product and HTTP targets.

Cron Jobs in the Console Manage cron jobs from the Google Cloud Console.

app.yaml Configuration Service configuration file reference.

Creating a Service How services relate to cron job targeting.