| title | GitLab CI β 429 Too Many Requests (Rate Limited) | |||||
|---|---|---|---|---|---|---|
| slug | gitlab-429-too-many-requests | |||||
| technologies |
|
|||||
| severity | medium | |||||
| tags |
|
|||||
| related |
|
|||||
| last_reviewed | 2026-06-27 |
$ docker pull node:20
Error response from daemon: toomanyrequests: You have reached your pull rate limit.
$ git fetch origin
error: RPC failed; HTTP 429 curl 22 The requested URL returned error: 429
fatal: expected flush after ref listing
WARNING: Retrying (attempt 1/3) after 429 Too Many Requests
Retry-After: 60
A 429 Too Many Requests response means a server is throttling the job for
exceeding a rate limit. In GitLab CI this comes from one of several places: the
GitLab API/Git endpoints (instance rate limits), the GitLab Container Registry, or
an upstream like Docker Hub's anonymous pull limit. The job typically fails or
retries with a Retry-After hint. The fix depends on which endpoint is limiting
you β authenticate, cache/mirror, or reduce request volume.
- gitlab (API/registry rate limiting), upstream registries
medium β intermittent failures and pipeline retries slow delivery; it becomes high during peak load when many pipelines are throttled simultaneously.
- Docker Hub anonymous pull limit β many CI jobs pulling public images from the same egress IP exhaust the unauthenticated quota.
- GitLab.com or self-managed application rate limits (raw endpoints, unauthenticated API, Git over HTTPS) tripped by tight loops or fan-out jobs.
- GitLab Container Registry request limits hit by parallel pulls/pushes.
- A polling script (
while curl ... /pipelines) hammering the API. - Shared NAT IP across many runners aggregating into one limited identity.
Rate limiting is per-identity and/or per-IP over a sliding window. When the count
exceeds the threshold, the server short-circuits the request and returns 429,
often with a Retry-After header telling clients how long to back off.
Authenticated requests usually get a higher (or unlimited) quota than anonymous
ones, which is why pulling node:20 anonymously from Docker Hub fails while an
authenticated or GitLab-mirrored pull succeeds. Identifying the which-host
returning 429 is the key diagnostic step.
# Which host/endpoint returned 429? Inspect the job log line just above the error.
grep -nE 'image:|docker pull|FROM |registry|curl ' .gitlab-ci.yml
# Check Docker Hub remaining pull quota for the current IP (read-only)
TOKEN=$(curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:ratelimitpreview/test:pull" | grep -o '"token":"[^"]*' | cut -d'"' -f4)
curl -sI -H "Authorization: Bearer $TOKEN" https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest | grep -i ratelimit
# See the Retry-After / RateLimit headers GitLab returns
curl -sI --header "PRIVATE-TOKEN: $GL_TOKEN" "https://gitlab.com/api/v4/projects/<id>/pipelines" | grep -iE 'ratelimit|retry-after'
# Egress IP that all these runners share
curl -s https://ifconfig.me# Docker Hub near/over limit:
ratelimit-limit: 100;w=21600
ratelimit-remaining: 0;w=21600 <- exhausted
# GitLab throttling:
RateLimit-Remaining: 0
Retry-After: 60
-
Authenticate registry pulls so you get the higher quota (and use GitLab's Dependency Proxy to cache upstream images):
default: image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/node:20 before_script: - docker login -u "$CI_DEPENDENCY_PROXY_USER" -p "$CI_DEPENDENCY_PROXY_PASSWORD" "$CI_DEPENDENCY_PROXY_SERVER"
-
Add retry/back-off for transient 429s:
build: retry: max: 2 when: [runner_system_failure, api_failure]
-
Reduce request volume: cache dependencies, avoid tight API polling, and respect
Retry-Afterin custom scripts. -
For self-managed GitLab, raise the relevant Admin > Settings > Network rate limits if they are too tight for your runner fleet.
# After enabling the Dependency Proxy / auth, re-pull:
docker pull "${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/node:20" # succeeds, no 429- Route all upstream image pulls through GitLab's Dependency Proxy or a private mirror.
- Always authenticate to registries in CI to avoid anonymous limits.
- Replace polling loops with webhooks or pipeline
needs:; honorRetry-After.
gitlab Β· ci Β· rate-limit Β· registry Β· production