GitLab CI Error Guide: 'You are not allowed to deploy to production' Deployment Blocked by a Protected Environment
Fix a GitLab deployment blocked by a protected environment: grant deployer access, approve the gate, lift a freeze period, and resolve when:manual jobs that won't run.
- #gitlab-cicd
- #troubleshooting
- #errors
- #environments
Exact Error Message
A deployment job is held back or rejected because the target environment is protected. Depending on the gate, you will see one of these:
This job is blocked.
This deployment job does not run automatically and must be started manually.
You are not allowed to deploy to production.
This deployment is blocked by a deploy freeze.
This deployment job requires approval before it can run.
In the job log, a permission rejection looks like:
Running with gitlab-runner 16.11.0 ...
Preparing environment
ERROR: Job failed (system failure): you are not allowed to deploy to "production"
The pipeline itself is valid; GitLab is enforcing a deployment policy on the production environment before letting the job run.
What the Error Means
A protected environment restricts who and what can deploy to it. When a job declares environment: production (and production is protected), GitLab checks several gates before the job is allowed to start: is the user/role in the environment’s allowed deployers list? Is an approval required and granted? Is there an active deploy freeze? Is the job intentionally manual (when: manual) and simply waiting for a click?
If any gate is unmet, the job is blocked or rejected rather than failing in your script. The status text tells you which gate fired: a permission message means you are not an allowed deployer; “must be started manually” means the job is when: manual and waiting; “requires approval” means a deployment-approval rule is pending; “deploy freeze” means a freeze window is active. Each has a different fix, so read the exact wording first.
Common Causes
- Insufficient deploy permission. The protected environment limits deploys to a specific role (e.g., Maintainer) or named group/user, and the actor running the pipeline is not on the list.
- The job is
when: manualand was never started — “does not run automatically and must be started manually” is by design, not an error. - A deployment-approval rule is pending. The environment requires one or more approvals before the deploy runs.
- An active deploy freeze. A scheduled freeze period blocks all deployments to the environment.
- It needs an upstream manual job. The deploy
needs:a manual gate job that has not been triggered, so it stays blocked. - Approval/gate misconfiguration. Required approvers are set but unreachable, or unified approval rules disallow self-approval.
How to Reproduce the Error
Protect an environment, restrict deployers, then try to deploy as a non-allowed user:
Settings > CI/CD > Protected environments > Protect "production"
Allowed to deploy: Maintainers (and/or a specific group)
Required approvals: 1
# .gitlab-ci.yml
deploy-prod:
stage: deploy
environment:
name: production
action: start
script:
- ./deploy.sh
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manual # also requires a manual start
A Developer (not Maintainer) opening the pipeline sees the job blocked with You are not allowed to deploy to production; even a Maintainer sees it wait for both a manual start and an approval.
Diagnostic Commands
Most of the answer is in the UI, but the CLI and environment: block confirm the configuration:
Settings > CI/CD > Protected environments
-> Allowed to deploy (roles/groups/users), Required approvals
Operate > Environments > production
-> pending approvals, last deployer, blocked jobs
Settings > CI/CD > Deploy freezes
-> active freeze windows (cron + timezone)
Inspect the job/environment via the API and glab:
# Who is allowed to deploy to a protected environment
curl -s --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/protected_environments/production" | jq
# The blocked job's status (manual / blocked / waiting_for_approval)
glab ci status
glab api "projects/$CI_PROJECT_ID/jobs/$JOB_ID" | jq '.status, .name'
In .gitlab-ci.yml, confirm what the job declares:
environment:
name: production
action: start # start | prepare | stop | verify | access
# and check for: when: manual / needs: [<upstream manual job>]
Step-by-Step Resolution
-
Grant deploy access if the message is a permission rejection. Add the user, the user’s role, or their group to the environment’s allowed deployers:
Settings > CI/CD > Protected environments > production Allowed to deploy: + add group "release-engineers" (or raise to Maintainer)The actor must be an allowed deployer and meet the branch protection on the source ref.
-
Start the job if it is
when: manual. “Does not run automatically and must be started manually” is expected for gated deploys — click Play on the job (orglab ci trigger <job>). To make it automatic instead, change the rule:deploy-prod: environment: { name: production, action: start } rules: - if: '$CI_COMMIT_BRANCH == "main"' when: on_success # was: manual -
Approve the deployment if approval is pending. An allowed approver opens Operate > Environments > production and approves the pending deployment, or via API:
curl -s --request POST --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/deployments/$DEPLOYMENT_ID/approval" \ --data "status=approved" -
Lift or wait out a deploy freeze. If a freeze is active, either wait for the window to end or remove/adjust it under Settings > CI/CD > Deploy freezes (freezes are cron windows in a set timezone).
-
Trigger the upstream manual gate if the deploy
needs:one. Start the gate job first; the deploy unblocks once its dependency succeeds. -
Fix approval-rule misconfiguration. Ensure required approvers exist and are reachable, and that self-approval rules match your team’s workflow.
Re-run or replay the pipeline and confirm the environment shows a successful deployment.
Prevention and Best Practices
- Protect production-like environments and keep the allowed deployers list as a named group, so onboarding/offboarding is one change, not many.
- Make production deploys
when: manual(or approval-gated) on purpose, and document that “must be started manually” is the intended gate, not a bug. - Use deployment approvals for change control instead of ad-hoc permissions; record who approved each deploy.
- Schedule deploy freezes around release windows and announce them, so blocked jobs during a freeze are expected.
- Pair protected environments with protected branches so only the right ref and the right person can deploy.
- The free incident assistant can classify a “blocked”/“not allowed to deploy” message into the specific gate (permission vs manual vs approval vs freeze). More: GitLab CI/CD guides.
Related Errors
$DEPLOY_TOKEN: unbound variable— an empty protected CI/CD variable, a variable-availability issue rather than an environment-permission one.This pipeline is blocked and can only be unblocked by...— a manual-job/approval gate higher in the pipeline.403 Forbiddenfrom a deploy API call — token/role lacks deploy scope.- See the GitLab CI/CD guides.
Frequently Asked Questions
Is “does not run automatically and must be started manually” actually an error? No. It means the deploy job is when: manual — a deliberate gate. Click Play on the job to deploy, or change the rule to when: on_success if you want it automatic.
Why am I blocked when I have Developer access? Protected environments restrict deploys independently of project roles. If the environment’s “Allowed to deploy” list is Maintainers or a specific group, a Developer is rejected with You are not allowed to deploy to <env>. Ask an owner to add you or your group.
How do I approve a pending deployment? An allowed approver approves it under Operate > Environments > POST /deployments/:id/approval with status=approved. The deploy job stays blocked until the required number of approvals is met.
A deploy freeze is active — can I override it? Only by editing or removing the freeze window in Settings > CI/CD > Deploy freezes, which requires Maintainer/Owner rights. Otherwise the deployment waits until the freeze ends. Freezes are cron windows evaluated in their configured timezone.
My deploy job stays blocked even after I have permission — why? It likely needs: an upstream when: manual gate job that has not been run, or a required approval is still pending. Start the gate job and grant the approval; the deploy unblocks once its dependencies are satisfied.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.