A protected runner runs jobs only for protected branches or protected tags. If your deploy runner holds production network access or credentials, marking it protected means a job from an unreviewed feature branch cannot land on it. Combine it with runner tags and protected variables for a clear deploy path.
What the setting does
GitLab's runner configuration page says protected runners can be restricted to jobs on protected branches or jobs with protected tags, which helps stop runners from revealing sensitive information. A protected runner can optionally run jobs in merge request pipelines too, which you should only allow once you trust the merge request flow. See when merge request pipelines get protected access.
How tags fit in
Tags are labels on runners. A job must carry every tag a runner has before the runner picks it up. A runner set to take only tagged jobs will leave jobs with no matching tag waiting, and a runner with no tags that does not accept untagged jobs can leave tagged jobs stuck. GitLab notes that CI/CD tags are different from Git tags: runner tags route jobs, Git tags mark commits.
So a deploy path looks like this:
| Layer | Setting | Effect |
|---|---|---|
| Runner | Protected | Only protected refs reach it |
| Runner | Tag deploy-prod | Only jobs naming that tag reach it |
| Runner | Run untagged jobs off | Random jobs do not land there |
| Variables | Protected | Secrets only on protected refs |
| Environment | Protected environment | Deploy needs approval |
Steps to set it up
- Create a dedicated runner for production deploys.
- In its settings, enable Protected and disable Run untagged jobs.
- Give it a clear tag such as
deploy-prod. - In the deploy job, add that tag under
tagsand restrict it withrulesto the default branch. - Mark the deploy credentials as protected variables. See masked versus protected variables.
- Protect the environment too. See protected environments.
deploy_prod:
tags: [deploy-prod]
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
script:
- ./deploy.sh
Risks that remain
Instance runners are the riskiest option, per GitLab, since they are available by default to all groups and projects. Forked projects inherit job settings, so instance runners set up for the original project also serve the fork. Anyone with access to the runner host can inspect code the runner executed and its authentication details. With concurrency above 1 and a shared build directory, jobs may work in the same directory and the runner does not stop that. Set a runner maximum job timeout too. GitLab's minimum is 600 seconds, and a runner limit shorter than the project timeout wins.
Common mistakes
The common one is marking a runner protected but leaving Run untagged jobs on, so unrelated jobs still land on it when their branches are protected. Another is allowing merge request pipelines on the protected runner before the review process is solid. A third is forgetting that anyone with host access can inspect past jobs. Revisit these settings whenever a team joins or a repository is forked, because new projects can reach shared runners by default.
Safeguard's asset discovery can help you see which repositories you have, which makes it easier to find deploy jobs that still use shared runners. Run a free scan at https://gold.safeguard.sh/scan.
Quick check
Does Protected replace tags?
No. Protected limits which refs can use the runner. Tags limit which jobs ask for it. Use both.
Who can see what the runner ran?
Anyone with access to the runner host, so treat the host as sensitive.
Sources
- GitLab Docs: Configuring runners, fetched 19 October 2026.
Last reviewed: 19 October 2026.