Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for GitLab CI/CD By James Joyner IV · · 9 min read

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: manual and 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

  1. 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.

  2. 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 (or glab 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
  3. 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"
  4. 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).

  5. Trigger the upstream manual gate if the deploy needs: one. Start the gate job first; the deploy unblocks once its dependency succeeds.

  6. 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.
  • $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 Forbidden from 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 > , or via 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.

Free download · 368-page PDF

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.