GitLab CI Scheduled Pipelines Guide
GitLab CI pipelines can be triggered on a cron schedule through the GitLab UI or API. This guide covers how to create pipeline schedules, filter jobs to run only on schedule, pass custom variables, and handle timezones.
Contents
1. How GitLab Pipeline Schedules Work
GitLab's pipeline scheduler is a GitLab-side feature, not defined in .gitlab-ci.yml. The schedule lives in GitLab → CI/CD → Schedulesand triggers a pipeline on the specified branch at the cron interval.
| Aspect | Detail |
|---|---|
| Configuration location | GitLab UI → Project → CI/CD → Schedules (or API) |
| Pipeline source | CI_PIPELINE_SOURCE == 'schedule' |
| Cron format | Standard 5-field (minute hour day month weekday) |
| Minimum interval | Every minute (no enforced minimum, but reasonable use applies) |
| Timezone | UTC by default; configurable per-schedule in UI |
2. Creating a Schedule (UI + API)
Via UI: Navigate to your project → Build → Pipeline schedules → New schedule. Fill in the description, cron expression, timezone, target branch, and any variables.
Via API:
curl --request POST \ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ --form description="Nightly build" \ --form ref="main" \ --form cron="0 0 * * *" \ --form cron_timezone="UTC" \ --form active="true" \ "https://gitlab.com/api/v4/projects/$PROJECT_ID/pipeline_schedules"
3. Cron Syntax in GitLab
GitLab uses standard 5-field cron syntax (the same as Linux crontab). There is no Jenkins-style H hash symbol.
| Field | Range | Special chars |
|---|---|---|
| Minute | 0–59 | * , - / |
| Hour | 0–23 | * , - / |
| Day of month | 1–31 | * , - / |
| Month | 1–12 | * , - / |
| Day of week | 0–6 (Sun=0) | * , - / |
4. Running Only Specific Jobs on Schedule
By default, a scheduled pipeline runs all jobs. Use rules: to include or exclude jobs based on the pipeline source:
nightly-integration-tests:
stage: test
script:
- ./run_full_test_suite.sh
rules:
# Only run when triggered by a schedule
- if: $CI_PIPELINE_SOURCE == "schedule"
build:
stage: build
script:
- make build
rules:
# Run on push, merge request, but NOT on schedule
- if: $CI_PIPELINE_SOURCE == "push"
- if: $CI_PIPELINE_SOURCE == "merge_request_event"nightly-job:
script: ./nightly.sh
only:
- schedules # only on scheduled pipelines
regular-build:
script: make build
except:
- schedules # skip on scheduled pipelinesrules: over only/except. The rules syntax is more expressive and will eventually replace the legacy keywords.5. Schedule-Specific Variables
Each pipeline schedule can define custom CI/CD variables. This lets you run the same pipeline differently depending on which schedule triggered it:
# Schedule A: SCHEDULE_TYPE=nightly
# Schedule B: SCHEDULE_TYPE=weekly-release
deploy:
stage: deploy
script:
- |
if [ "$SCHEDULE_TYPE" = "weekly-release" ]; then
./deploy-production.sh
else
./deploy-staging.sh
fi
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"GitLab also provides the built-in CI_PIPELINE_SCHEDULE and CI_PIPELINE_SCHEDULE_DESCRIPTION variables (GitLab 16+) to inspect which schedule fired.
6. Timezone Configuration
Each pipeline schedule has its own timezone setting, configured when you create or edit the schedule in the UI or API. The timezone dropdown accepts standard IANA timezone identifiers.
# Run at 9 AM EST (New York) curl --request POST \ --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ --form description="Morning build (EST)" \ --form ref="main" \ --form cron="0 9 * * 1-5" \ --form cron_timezone="America/New_York" \ "https://gitlab.com/api/v4/projects/$PROJECT_ID/pipeline_schedules"
7. Common Schedule Patterns
| Description | Expression | Notes |
|---|---|---|
| Nightly at midnight UTC | 0 0 * * * | All branches |
| Every hour | 0 * * * * | Monitoring/health check |
| Every weekday at 6 AM | 0 6 * * 1-5 | MON–FRI pre-standup |
| Every 15 minutes | */15 * * * * | Integration polling |
| First of month at 2 AM | 0 2 1 * * | Monthly report/cleanup |
| Every Sunday at 3 AM | 0 3 * * 0 | Weekly full test suite |
| Twice daily (8 AM and 8 PM) | 0 8,20 * * * | Comma list |
| Every 6 hours | 0 */6 * * * | Canary deployment |
8. Common Gotchas
Schedules are per-project, not per-branch
A pipeline schedule targets one specific branch/tag. If you need schedules for multiple branches, create separate schedules for each.
Inactive schedules don't fire
Each schedule has an Active toggle. If a schedule was manually deactivated or auto-deactivated due to repo archiving, it silently stops running. Check the UI if a scheduled pipeline suddenly stops.
Auto-deactivation for missed schedules
GitLab can auto-deactivate schedules that fail repeatedly (e.g., branch deleted). GitLab.com sends an email warning before deactivating.
Schedule granularity is 1 minute, but don't abuse it
GitLab.com enforces a minimum interval for shared runners. Very frequent schedules (every minute) on large jobs can hit concurrency limits and get queued. Use webhooks for sub-minute event triggers instead.
rules: vs only: interaction with schedules
If a job has no rules/only/except at all, it runs on every pipeline including scheduled ones. Add `rules: - if: $CI_PIPELINE_SOURCE == "schedule" when: never` to any job you want to explicitly exclude from scheduled runs.
Related Guides
Jenkins Cron Schedule Guide
H hash syntax, pollSCM, and Declarative Pipeline triggers.
GitHub Actions Cron Guide
on.schedule trigger, UTC requirement, and debug patterns.
Cron Job Best Practices
12 production rules: idempotency, locking, alerting, and more.
Cron Jobs and Timezones
UTC default, IANA timezone IDs, and platform-specific config.
Validate your GitLab cron expression
GitLab uses standard 5-field cron syntax. Test your expression with our free tools to preview run times and get a plain-English description.