How to Set Up Renovate With Safe Auto-Merge: Step-by-Step (2026)

Nov 10, 2026 · 14 min · Ajay Kumar

This guide is for anyone who has installed Renovate, watched it open twenty pull requests, and now wants it to merge the boring ones by itself without merging something dangerous. At the end you will have a validated renovate.json that auto-merges minor, patch and digest updates once your required checks pass, waits three days before proposing any new release, groups the noisy updates, holds majors behind a Dependency Dashboard approval, and runs either on the free Mend app or as a self-hosted GitHub Action. I tested the configuration with Renovate 44.225.1 (released 10 November 2026) and its renovate-config-validator, and checked every option against the current docs. You need admin access to a GitHub repository with a CI workflow that runs on pull requests. If you are still choosing a bot, read Renovate vs Dependabot in 2026 first.

One Renovate update, from publish to merge New versionpublished minimumReleaseAge3 days, no branch yetstability-days Branch and PR(grouped if set) Required checksvia ruleset Minor, patch or digest, and package at 1.0+?packageRules decide yes GitHub native auto-mergemerges when checks are green no Human review (majors, pre-1.0)majors need a dashboard tick first Security fixes skip minimumReleaseAge and the PR limits, so they arrive as soon as Renovate sees the advisory.
The path one update takes with the configuration in this guide. Behaviour per the Renovate 44 docs for minimumReleaseAge, internalChecksFilter (default strict), platformAutomerge (default true) and vulnerabilityAlerts.

Step 1: Choose the Mend app or self-hosted Renovate

There are two realistic ways to run Renovate on GitHub. The Mend Renovate Community Cloud is free for unlimited public and private repositories, keeps the token for you, and runs each active repository every four hours with one concurrent job per organisation and a 30-minute timeout. Self-hosting with the Renovate GitHub Action gives you any cadence, private-network registries and full control of the credentials, and costs you Actions minutes plus a GitHub App to maintain.

Start with the app unless you already know you need one of those things. Everything from Step 3 to Step 7 is identical for both, because the repository config is the same file. Step 8 covers the self-hosted workflow.

Step 2: Install the Mend Renovate app and merge the onboarding PR

Install the Renovate app from GitHub and choose Only select repositories for the first rollout. That choice matters. If an organisation installs with All repositories, the Mend-hosted app defaults to silent mode (dryRun=lookup): it scans but opens no onboarding PR and no issues. With selected repositories you get an onboarding PR titled "Configure Renovate" in each one.

Do not merge the onboarding PR as it stands. Check out its branch, replace the generated renovate.json with the one from Step 4, push, and merge once CI passes. Renovate re-reads the config from the onboarding branch, so the PR description updates to show what it will do.

You can confirm the app's identity before granting it anything:

# Read-only lookups; no write scope needed
gh api /apps/renovate --jq '.id, .slug'
# 2740
# renovate

gh api '/users/renovate%5Bbot%5D' --jq '.login, .id'
# renovate[bot]
# 29139614

The app ID (2740) is what you would put in a ruleset bypass list; the bot login is what appears as the PR author.

Step 3: Turn on auto-merge and required checks in GitHub

Renovate's default platformAutomerge: true hands the merge to GitHub's native auto-merge. The Renovate docs are blunt about the risk: without "require status checks before merging", GitHub may merge Renovate PRs before tests have started, while they are running, or even after they fail. So the repository settings come before the config.

Enable auto-merge and branch cleanup on the repository:

# gh 2.x: both flags exist on `gh repo edit`
gh repo edit OWNER/REPO --enable-auto-merge --delete-branch-on-merge

# Verify (read-only): both values should print true
gh api repos/OWNER/REPO --jq '.allow_auto_merge, .delete_branch_on_merge'

Then require your CI job with a repository ruleset. The context must be the exact check name GitHub shows on a PR, which for GitHub Actions is the job name (test below):

{
  "name": "default-branch-protection",
  "target": "branch",
  "enforcement": "active",
  "conditions": {
    "ref_name": { "include": ["~DEFAULT_BRANCH"], "exclude": [] }
  },
  "rules": [
    { "type": "deletion" },
    { "type": "non_fast_forward" },
    {
      "type": "pull_request",
      "parameters": {
        "required_approving_review_count": 0,
        "dismiss_stale_reviews_on_push": true,
        "require_code_owner_review": false,
        "require_last_push_approval": false,
        "required_review_thread_resolution": false
      }
    },
    {
      "type": "required_status_checks",
      "parameters": {
        "strict_required_status_checks_policy": true,
        "required_status_checks": [{ "context": "test" }]
      }
    }
  ]
}
# Save the JSON above as ruleset.json, then:
gh api --method POST repos/OWNER/REPO/rulesets --input ruleset.json

The field names follow the rulesets REST API. strict_required_status_checks_policy: true means a PR must be tested against the latest base before it merges; Renovate rebases its branches when they fall behind, so this costs CI minutes but closes the gap where two individually green updates break together. If you require approvals (required_approving_review_count above 0) or CODEOWNERS review, Renovate's PRs will wait for a human; see the troubleshooting table for the two documented ways around that.

Step 4: Write renovate.json with packageRules for safe auto-merge

This is the full configuration. Put it at the repository root as renovate.json:

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    "config:best-practices",
    "security:minimumReleaseAgePypi",
    ":timezone(Europe/London)"
  ],
  "schedule": ["* 0-8 * * 1-5"],
  "prHourlyLimit": 4,
  "prConcurrentLimit": 10,
  "minimumReleaseAge": "3 days",
  "osvVulnerabilityAlerts": true,
  "vulnerabilityAlerts": {
    "labels": ["security"]
  },
  "packageRules": [
    {
      "description": "Automerge minor and patch updates of stable (>=1.0) packages once required checks pass",
      "matchUpdateTypes": ["minor", "patch"],
      "matchCurrentVersion": "!/^0/",
      "automerge": true
    },
    {
      "description": "Automerge digest pins and digest bumps (GitHub Actions SHAs, Docker digests)",
      "matchUpdateTypes": ["digest", "pinDigest"],
      "automerge": true
    },
    {
      "description": "One weekly PR for all GitHub Actions updates",
      "matchManagers": ["github-actions"],
      "groupName": "github actions",
      "schedule": ["* 0-8 * * 1"]
    },
    {
      "description": "Group non-major devDependencies into one PR",
      "matchDepTypes": ["devDependencies"],
      "matchUpdateTypes": ["minor", "patch"],
      "groupName": "dev dependencies (non-major)"
    },
    {
      "description": "Majors wait 14 days and need a tick on the Dependency Dashboard",
      "matchUpdateTypes": ["major"],
      "minimumReleaseAge": "14 days",
      "dependencyDashboardApproval": true,
      "automerge": false,
      "labels": ["major-update"]
    },
    {
      "description": "Never automerge these, whatever the update type",
      "matchPackageNames": ["react", "react-dom", "@aws-sdk/**"],
      "automerge": false
    }
  ]
}

What each part does, and why:

  • config:best-practices extends config:recommended (Dependency Dashboard, monorepo grouping, replacement and workaround presets) and adds Docker digest pinning, GitHub Actions SHA pinning, config migration PRs, pinned devDependencies, weekly lock file maintenance, abandonment detection and security:minimumReleaseAgeNpm. You can read the exact list in the config presets docs.
  • security:minimumReleaseAgePypi adds the same 3-day wait for PyPI. There is a security:minimumReleaseAgeCrate for Rust. The top-level "minimumReleaseAge": "3 days" then covers every other datasource that publishes release timestamps.
  • schedule uses cron syntax; Renovate requires * in the minutes field. Weekdays before 09:00 London time keeps rebases and CI runs out of working hours.
  • prHourlyLimit and prConcurrentLimit cap the flood. Renovate's defaults are 2 PRs created per hour and 10 open; I raise the hourly limit slightly because auto-merged PRs close fast.
  • packageRules are applied in order, and later matches override earlier ones. That is why the "never automerge" rule is last: it wins over the minor and patch rule above it. matchCurrentVersion: "!/^0/" excludes 0.x packages, which may break on any release under SemVer.
  • Majors get a longer 14-day wait and dependencyDashboardApproval, so no branch exists until someone ticks the box on the dashboard issue.

The vulnerabilityAlerts block only adds a label. Security fixes already bypass minimumReleaseAge, schedule and the hourly limit, and their open-PR budget is separate from the regular one, so they are never stuck behind routine bumps. Renovate reads GitHub's alerts only if Dependabot alerts are enabled on the repository and the app has Dependabot alerts: read; osvVulnerabilityAlerts adds OSV as a second source for direct dependencies.

If you have no tests at all, Renovate will not auto-merge, and you should not override that with ignoreTests until you do.

Step 5: Validate the config locally and in CI

Renovate ships a validator in every distribution. Run it before you push:

# Node 24.11+ is required by Renovate 44 (package.json engines)
npx --yes --package renovate@44.225.1 -- renovate-config-validator --strict

On the config above, this is the real output from my run:

 INFO: Validating renovate.json
 INFO: Config validated successfully against 1 file(s)

And this is what a stale or broken config looks like. I fed it config:base, the old stabilityDays option, and a string where a boolean belongs:

 WARN: Config migration necessary
       "newConfig": {
         "extends": ["config:recommended"],
         "minimumReleaseAge": "3 days",
         ...
ERROR: Found errors in configuration
       "message": "Configuration option `packageRules[0].automerge` should be boolean. Found: \"yes\" (string)"

--strict makes migration warnings fail the run, which is what you want in CI. Add a small workflow so nobody merges a broken config:

# .github/workflows/renovate-config.yml
name: renovate-config
on:
  pull_request:
    paths: ["renovate.json"]
permissions:
  contents: read
jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7.0.1   # pin to a SHA in production
      - uses: actions/setup-node@v7.1.0
        with:
          node-version: "24"
      - run: npx --yes --package renovate@44.225.1 -- renovate-config-validator --strict

Renovate will keep that renovate@44.225.1 pin up to date for you, which is a nice bit of recursion. The config validation docs cover pre-commit and custom file names.

Step 6: Understand minimumReleaseAge and the stability-days check

minimumReleaseAge counts from each version's own release timestamp. It does not wait for a package to go quiet; a package that releases daily will still get a PR for each version once that version is three days old. The minimum release age docs explain the mechanics, and three defaults matter:

  • internalChecksFilter defaults to strict, so Renovate creates no branch until a version passes the age check. You see the waiting updates on the Dependency Dashboard, not as pending PRs.
  • minimumReleaseAgeBehaviour defaults to timestamp-required since Renovate 42. A release with no timestamp counts as too new. That is safe, but registries without timestamps (the docs name GHCR, Quay and ECR for some digest cases) are held indefinitely unless you set timestamp-optional for them.
  • minimumReleaseAgeBuffer defaults to 30 minutes on top of the age, so Renovate doesn't propose a package whose platform binaries were published slightly later and still sit inside pnpm's own minimumReleaseAge.

When a branch does exist and is still too young (for example with internalChecksFilter: "flexible"), Renovate sets a pending renovate/stability-days status check on it, which stops auto-merge until it turns green. Don't add that check to your ruleset; it only appears on Renovate branches and would block every human PR.

Why bother? Because auto-merge is a fast lane for whatever got published. The npm worms of 2025 spread through freshly published malicious versions, which I wrote about in the npm supply-chain attacks post. A three-day wait is a cheap bet that someone else's scanner sees it first.

Step 7: Use the Dependency Dashboard to control noise

config:recommended turns on the Dependency Dashboard, a single issue titled "Dependency Dashboard" that lists every pending, rate-limited, awaiting-schedule, awaiting-approval and errored update. It is the control panel for this setup:

  • Tick a box under Pending Approval to create a major-update branch.
  • Tick a box under Rate-Limited or Awaiting Schedule to create that PR now.
  • Check Pending Status Checks when updates seem to be missing; that is usually the release-age gate working as intended.

If the repository gets too many PRs even with grouping, the noise reduction docs cover the remaining levers, which I would try in this order: a schedule, grouping, automergeType: "branch" so passing updates commit without a PR (only if your rules don't require PRs), and minimumGroupSize to hold a group until several updates are ready.

Step 8 (optional): Self-host Renovate with GitHub Actions and a GitHub App

Create a GitHub App with the permissions the Renovate GitHub platform docs list: repository Administration read, Checks, Commit statuses, Contents, Issues, Merge queues, Pull requests and Workflows read and write, Dependabot alerts and Metadata read, and organisation Members read. Install it on the target repositories, store its client ID as a variable and its private key as a secret, then add this workflow to an admin repository:

# .github/workflows/renovate.yml
name: renovate
on:
  schedule:
    - cron: "17 * * * *"      # hourly, off the top of the hour
  workflow_dispatch:
permissions:
  contents: read
jobs:
  renovate:
    runs-on: ubuntu-latest
    steps:
      - id: app-token
        uses: actions/create-github-app-token@v3.3.0
        with:
          client-id: ${{ vars.RENOVATE_APP_CLIENT_ID }}
          private-key: ${{ secrets.RENOVATE_APP_PRIVATE_KEY }}
          owner: ${{ github.repository_owner }}
      - uses: actions/checkout@v7.0.1
      - uses: renovatebot/github-action@v46.3.13
        with:
          token: ${{ steps.app-token.outputs.token }}
          renovate-version: 44.225.1
        env:
          RENOVATE_AUTODISCOVER: "true"
          RENOVATE_AUTODISCOVER_FILTER: "my-org/*"
          RENOVATE_PLATFORM_COMMIT: "enabled"
          LOG_LEVEL: info

create-github-app-token v3 takes client-id; the older app-id input is deprecated. Installation tokens expire after an hour, which suits a job that runs for minutes. RENOVATE_PLATFORM_COMMIT makes Renovate commit through the API so commits are signed as the app. The same renovate.json from Step 4 applies unchanged. If you also run self-hosted runners, keep this job on GitHub-hosted ones: it holds a write token to every repository it touches.

Troubleshooting

Symptom Likely cause Fix
No onboarding PR after installing the app Installed on All repositories, so the app is in silent mode Switch the repo to Interactive in the Mend Developer Portal, or reinstall with selected repositories
PR body says "Automerge: Disabled by config" No packageRules entry matched, or a later rule set automerge: false Check rule order; the last matching rule wins
PR merged before CI finished platformAutomerge with no required status checks Add a ruleset with required_status_checks (Step 3)
Auto-merge never happens, checks green Required approvals or CODEOWNERS review Add the bot to the ruleset bypass list, or install the documented renovate-approve helper apps
No checks ever appear on Renovate PRs CI only runs on push to main, or path filters exclude lock files Run CI on pull_request; with merge queues also on merge_group
Updates listed under Pending Status Checks for days minimumReleaseAge working, or a registry with no release timestamps Wait, or set minimumReleaseAgeBehaviour: "timestamp-optional" for that datasource
PRs trickle in one or two at a time prHourlyLimit (default 2) or the Mend 4-hour cadence Raise prHourlyLimit, widen schedule, or self-host for hourly runs
Branch automerge opens a PR instead on a merge-queue branch Branch automerge needs Renovate on the queue bypass list Use automergeType: "pr" (the default)
Validator warns "Config migration necessary" Old option names such as stabilityDays or config:base Apply the printed newConfig, or merge Renovate's config migration PR
Security fix PR not grouped or scheduled By design: vulnerability PRs skip schedule and hourly limits Leave it; label it via vulnerabilityAlerts.labels

The automerge troubleshooting page has more cases, and its first piece of advice is worth repeating: give Renovate about two hours before deciding auto-merge is broken.

FAQ

Why is Renovate not auto-merging my PR?

The common causes are no passing status checks on the branch, a required review or CODEOWNERS rule, a later packageRules entry that turns automerge off, and the release-age check still pending. The PR body states whether automerge is enabled, and the Dependency Dashboard shows pending checks. Renovate also merges only one branch per base branch per run when it merges by itself.

Is it safe to auto-merge dependency updates?

It is reasonably safe for minor, patch and digest updates of stable packages when your tests are good, required checks are enforced by the platform, and a release-age delay is in place. It is not safe for majors, 0.x packages, or repositories with thin tests. Keep a revert path and watch the first few weeks.

What is the difference between config:recommended and config:best-practices?

config:recommended is the base preset: the Dependency Dashboard, monorepo grouping, replacements and workarounds. config:best-practices extends it with digest pinning for Docker and GitHub Actions, config migration PRs, pinned devDependencies, weekly lock file maintenance, abandonment detection and a 3-day minimum release age for npm.

Does Renovate work with GitHub merge queues?

Yes. With the default platformAutomerge, GitHub's auto-merge adds the PR to the queue once checks pass, provided "Allow auto-merge" is on and your workflows also run on the merge_group event. Use the default automergeType: "pr"; branch automerge only works if Renovate is on the queue's bypass list.

In production, the first thing I check on a new Renovate repo is the ruleset, not the config: if required checks aren't enforced, platform auto-merge is a liability. Next I read the Dependency Dashboard after the first week to see what the release-age gate held back and whether grouping is too coarse. Then I watch the auto-merged commits for a month before I widen the rules to anything beyond minor and patch. The official references are the configuration options, automerge, minimum release age and GitHub platform pages, plus GitHub's rulesets documentation. For CI basics behind those required checks, see How to Build a CI/CD Pipeline With GitHub Actions.

Related: Renovate vs Dependabot in 2026, GitHub Actions Security in 2026: 76 Hijacked Tags and the Fixes That Work and How to Build a CI/CD Pipeline With GitHub Actions: Step-by-Step (2026).

Spot an error or something out of date? Email me and I will fix it and log it on the corrections page.

I'm Ajay Kumar — I build and operate PandaStack, an open-source Firecracker microVM cloud for AI agents, and write about the infrastructure and tooling around it.

Need this kind of infrastructure work? See what I do or email hello@ajayk.sh.


Related