Runner Troubleshooting

A practical guide to diagnosing and fixing GitLab Runner problems. From stuck jobs and tag mismatches to DNS resolution failures, OOM kills, and cache access errors, understand the cause and apply the fix.

Runner Fixes GitLab CI/CD
Where Runner Problems Come From

GitLab Runner sits between GitLab and your job's execution environment. Problems can originate from any of the three: GitLab not assigning the job, the runner not receiving or accepting the job, or the execution environment failing to run it. The first step in troubleshooting is determining which layer is failing. A "stuck" job that never starts is a GitLab-to-runner problem. A job that starts and immediately fails is an execution environment problem .

The runner's logs are the primary diagnostic tool. On Linux, journalctl -u gitlab-runner shows the runner's activity, including job assignments, errors, and heartbeats. Enabling debug logging (log_level = "debug" in config.toml) provides more detail, but it exposes secrets in the logs, so it should be used temporarily and carefully [citation:1]. The job log itself shows what happened inside the container, which is where most execution failures are visible.

Many runner issues are configuration mismatches. The job requests tags that no runner has. The runner is registered to a different project. The runner is paused or offline. The Docker executor's DNS configuration doesn't match the host's. These are not bugs — they're configuration errors, and they're fixable once identified. This guide covers the most common ones, organized by symptom.

Core Principle: A stuck job is almost always a runner availability or tag issue. A failing job is almost always an execution environment issue. Distinguish the two before debugging.

Stuck Jobs

Job stays "pending" forever. No runner matches tags, runner is offline/paused, or capacity is exhausted.

DNS Issues

"Could not resolve host" or authentication failures. Docker executor doesn't inherit host DNS.

OOM Kills

Exit code 137. Job exceeded memory limit. Reduce memory usage or increase limit.

Cache Issues

"403 Forbidden" on S3 cache. Missing ListBucket permission or wrong credentials.

Docker Executor

Service containers fail, image pull fails, or DNS doesn't work. Configuration-specific issues.

Logs & Debug

journalctl, debug logging, correlation IDs. The primary diagnostic tools.
Stuck Jobs (Pending Forever)

A stuck job is one that stays in "pending" state and never starts. The error message usually says "This job is stuck because you don't have any active runners online or available with any of these tags assigned to them: X." The message names the tag that no runner satisfies, which is the first clue .

The most common cause is a tag mismatch. A job with tags: [docker] requires a runner that has the docker tag. If no online runner has that tag, the job waits. The fix is either to add the tag to a runner or to remove the tag from the job (if it doesn't actually need a specific runner). A related issue is untagged jobs: a job without tags only runs on a runner that has "Run untagged jobs" enabled. If all runners are tag-restricted, untagged jobs hang .

Other causes include: the runner service isn't running (registered but not started), shared runners are disabled for the project, or the runner is at capacity (concurrent limit reached). The diagnostic workflow is: check if any runner is online for the project, check its tags against the job's tags, check if it's paused or at capacity, and check if the service is actually running .

# Issue: Job stuck "pending" # Check the exact error message # "no active runners online or available with tags: docker" # -> Tag mismatch # Check runners for the project # Settings > CI/CD > Runners # Look for: online status, tags, paused state # Or via API curl -s --header "PRIVATE-TOKEN: $TOKEN" \ "https://gitlab.example.com/api/v4/projects/:id/runners" \ | jq '.[] | {description, online, paused, tag_list}' # Check runner service is running sudo gitlab-runner status sudo gitlab-runner verify # Check runner logs sudo journalctl -u gitlab-runner --no-pager | tail -30 # Fix: Add tag to runner or remove from job # .gitlab-ci.yml job: tags: - docker # Must match a runner's tag # Fix: Enable shared runners for the project # Settings > CI/CD > Runners # Toggle "Enable shared runners for this project" # Fix: Increase runner capacity # config.toml concurrent = 4 # Global concurrency [[runners]] limit = 2 # Per-runner limit # Fix: Unpause runner # Settings > CI/CD > Runners > (runner) > Pause/Unpause # Fix: Start runner service sudo gitlab-runner start # Debug: check runner version compatibility # Runner and GitLab versions should match major version
Stuck Job Checklist:
  • Does the job have tags? Do any runners have those tags?
  • Is the runner online and not paused?
  • Is the runner service running? (gitlab-runner status)
  • Are shared runners enabled for the project?
  • Is the runner at capacity? (check concurrent and limit)
DNS and Network Issues

DNS issues are common with the Docker executor because containers don't always inherit the host's DNS configuration. If the host uses a VPN with its own DNS server, or an internal DNS service, the container may use a different resolver and fail to resolve internal hostnames. The symptom is "Could not resolve host" or an authentication failure that looks like a credential problem but is actually a DNS routing problem [citation:1].

The fix is to explicitly configure DNS for the Docker executor in config.toml. The dns setting specifies which DNS servers the container should use, and dns_search specifies the search domains. For hostnames that have no DNS record (only an IP), extra_hosts maps the hostname to an IP, equivalent to adding an entry to /etc/hosts [citation:14].

Another common network issue is that service containers (like a PostgreSQL database defined in services:) aren't reachable from the job container. This can happen when Docker's legacy links functionality is deprecated or when the job and services don't share a network. Setting FF_NETWORK_PER_BUILD: "true" creates a per-build network where job and services can resolve each other by alias [citation:14].

# Issue: "Could not resolve host" or authentication failure # Cause: Docker executor doesn't use host DNS # Fix: Configure DNS in config.toml [[runners]] [runners.docker] dns = ["10.0.0.2", "1.1.1.1"] dns_search = ["internal.example.com"] # Issue: Internal hostname has no DNS record # Fix: Add extra_hosts mapping [[runners]] [runners.docker] extra_hosts = ["artifacts.corp.local:10.20.0.15"] # Issue: Service containers not reachable # Cause: Job and services don't share network # Fix: Enable per-build network variables: FF_NETWORK_PER_BUILD: "true" # Debug DNS inside job script: - cat /etc/resolv.conf - getent hosts gitlab.example.com - nslookup gitlab.example.com - ping -c 1 postgres # Service alias # Issue: Docker 29.x.x + service containers fail # Error: "No HOST or PORT found" # Cause: Legacy links deprecated # Fix: Re-enable legacy links (temporary) # /etc/systemd/system/docker.service.d/override.conf # [Service] # Environment="DOCKER_KEEP_DEPRECATED_LEGACY_LINKS_ENV_VARS=1" sudo systemctl daemon-reload sudo systemctl restart docker sudo gitlab-runner restart # Or downgrade Docker to 28.x or earlier
DNS Debugging Steps: