GitLab CICI/CDSchedules8 min read

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.

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.

AspectDetail
Configuration locationGitLab UI → Project → CI/CD → Schedules (or API)
Pipeline sourceCI_PIPELINE_SOURCE == 'schedule'
Cron formatStandard 5-field (minute hour day month weekday)
Minimum intervalEvery minute (no enforced minimum, but reasonable use applies)
TimezoneUTC 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:

GitLab API — create pipeline schedule
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.

FieldRangeSpecial chars
Minute0–59* , - /
Hour0–23* , - /
Day of month1–31* , - /
Month1–12* , - /
Day of week0–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:

.gitlab-ci.yml — run nightly job only on schedule
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"
Legacy only/except syntax (still supported)
nightly-job:
  script: ./nightly.sh
  only:
    - schedules          # only on scheduled pipelines

regular-build:
  script: make build
  except:
    - schedules          # skip on scheduled pipelines
Recommendation: Prefer rules: 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:

.gitlab-ci.yml — branching on schedule variable
# 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.

API — set timezone when creating schedule
# 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"
Best practice: Use UTC for all scheduled pipelines when possible. This avoids DST surprises and makes schedules predictable across team members in different timezones.

7. Common Schedule Patterns

DescriptionExpressionNotes
Nightly at midnight UTC0 0 * * *All branches
Every hour0 * * * *Monitoring/health check
Every weekday at 6 AM0 6 * * 1-5MON–FRI pre-standup
Every 15 minutes*/15 * * * *Integration polling
First of month at 2 AM0 2 1 * *Monthly report/cleanup
Every Sunday at 3 AM0 3 * * 0Weekly full test suite
Twice daily (8 AM and 8 PM)0 8,20 * * *Comma list
Every 6 hours0 */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

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.