diff --git a/docs/usage/self-hosted-configuration.md b/docs/usage/self-hosted-configuration.md
index 2e19b6b341a0dddb2f65de8dd834c6598b091b5c..66b65a93204db02e8ec31c41a6edd017d713a648 100644
--- a/docs/usage/self-hosted-configuration.md
+++ b/docs/usage/self-hosted-configuration.md
@@ -353,6 +353,13 @@ Possible values:
 - `ssh`: use SSH URLs provided by the platform for Git
 - `endpoint`: ignore URLs provided by the platform and use the configured endpoint directly
 
+## globalExtends
+
+Unlike the `extends` field, which is passed through unresolved to be part of repository config, any presets in `globalExtends` are resolved immediately as part of global config.
+Therefore you need to use this field if your preset contains any global-only configuration options, such as the list of repositories to run against.
+
+Use the `extends` field instead of this if, for example, you need the ability for a repository config (e.g. `renovate.json`) to be able to use `ignorePresets` for any preset defined in global config.
+
 ## logContext
 
 `logContext` is included with each log entry only if `logFormat="json"` - it is not included in the pretty log output.
diff --git a/lib/config/options/index.ts b/lib/config/options/index.ts
index 1816a3e154255efe0fca0ee2291655fe31892a53..438453110a0792e56d5772285f141c7ab64f2e0e 100644
--- a/lib/config/options/index.ts
+++ b/lib/config/options/index.ts
@@ -181,6 +181,14 @@ const options: RenovateOptions[] = [
       type: 'string',
     },
   },
+  {
+    name: 'globalExtends',
+    description:
+      'Configuration presets to use/extend for a self-hosted config.',
+    type: 'array',
+    subType: 'string',
+    globalOnly: true,
+  },
   {
     name: 'description',
     description: 'Plain text description for a config or preset.',
diff --git a/lib/config/types.ts b/lib/config/types.ts
index e792be3aa0939d45798c59ef448ed5ee3db11fc9..6fe7a9452a69b7233b5f8dfa15c6f3f8afd146cf 100644
--- a/lib/config/types.ts
+++ b/lib/config/types.ts
@@ -81,6 +81,7 @@ export interface GlobalOnlyConfig {
   forceCli?: boolean;
   gitNoVerify?: GitNoVerifyOption[];
   gitPrivateKey?: string;
+  globalExtends?: string[];
   logFile?: string;
   logFileLevel?: LogLevel;
   prCommitsPerRunLimit?: number;
diff --git a/lib/workers/global/index.spec.ts b/lib/workers/global/index.spec.ts
index 237bcd76690309d258e4f0002368dc046a0a23c4..b1d7df99b4bd5a2a26c9d659ed05b99cad249271 100644
--- a/lib/workers/global/index.spec.ts
+++ b/lib/workers/global/index.spec.ts
@@ -1,7 +1,9 @@
 import { expect } from '@jest/globals';
 import { ERROR, WARN } from 'bunyan';
-import { fs, logger } from '../../../test/util';
+import { fs, logger, mocked } from '../../../test/util';
+import * as _presets from '../../config/presets';
 import { PlatformId } from '../../constants';
+import { CONFIG_PRESETS_INVALID } from '../../constants/error-messages';
 import * as datasourceDocker from '../../datasource/docker';
 import * as _platform from '../../platform';
 import * as _repositoryWorker from '../repository';
@@ -11,11 +13,13 @@ import * as globalWorker from '.';
 
 jest.mock('../repository');
 jest.mock('../../util/fs');
+jest.mock('../../config/presets');
 
 // imports are readonly
 const repositoryWorker = _repositoryWorker;
 const configParser: jest.Mocked<typeof _configParser> = _configParser as never;
 const platform: jest.Mocked<typeof _platform> = _platform as never;
+const presets = mocked(_presets);
 const limits = _limits;
 
 describe('workers/global/index', () => {
@@ -25,6 +29,7 @@ describe('workers/global/index', () => {
     configParser.parseConfigs = jest.fn();
     platform.initPlatform.mockImplementation((input) => Promise.resolve(input));
   });
+
   it('handles config warnings and errors', async () => {
     configParser.parseConfigs.mockResolvedValueOnce({
       repositories: [],
@@ -33,6 +38,33 @@ describe('workers/global/index', () => {
     });
     await expect(globalWorker.start()).resolves.toBe(0);
   });
+
+  it('resolves global presets immediately', async () => {
+    configParser.parseConfigs.mockResolvedValueOnce({
+      repositories: [],
+      globalExtends: [':pinVersions'],
+    });
+    presets.resolveConfigPresets.mockResolvedValueOnce({});
+    await expect(globalWorker.start()).resolves.toBe(0);
+    expect(presets.resolveConfigPresets).toHaveBeenCalledWith({
+      extends: [':pinVersions'],
+    });
+  });
+
+  it('throws if global presets could not be resolved', async () => {
+    configParser.parseConfigs.mockResolvedValueOnce({
+      repositories: [],
+      globalExtends: [':pinVersions'],
+    });
+    presets.resolveConfigPresets.mockImplementation(() => {
+      throw new Error('some-error');
+    });
+    await expect(
+      globalWorker.resolveGlobalExtends(['some-preset'])
+    ).rejects.toThrow(CONFIG_PRESETS_INVALID);
+    expect(presets.resolveConfigPresets).toHaveBeenCalled();
+  });
+
   it('handles zero repos', async () => {
     configParser.parseConfigs.mockResolvedValueOnce({
       baseDir: '/tmp/base',
@@ -41,6 +73,7 @@ describe('workers/global/index', () => {
     });
     await expect(globalWorker.start()).resolves.toBe(0);
   });
+
   it('processes repositories', async () => {
     configParser.parseConfigs.mockResolvedValueOnce({
       gitAuthor: 'a@b.com',
diff --git a/lib/workers/global/index.ts b/lib/workers/global/index.ts
index 36222ffd01a10cde50823c2aef4827c4231fbafd..1470d4efe49d6f1d9f9828c69f07d248a9aa5162 100644
--- a/lib/workers/global/index.ts
+++ b/lib/workers/global/index.ts
@@ -4,6 +4,7 @@ import fs from 'fs-extra';
 import semver from 'semver';
 import upath from 'upath';
 import * as configParser from '../../config';
+import { mergeChildConfig } from '../../config';
 import { resolveConfigPresets } from '../../config/presets';
 import { validateConfigSecrets } from '../../config/secrets';
 import type {
@@ -85,11 +86,32 @@ export async function validatePresets(config: AllConfig): Promise<void> {
   }
 }
 
+export async function resolveGlobalExtends(
+  globalExtends: string[]
+): Promise<AllConfig> {
+  try {
+    // Make a "fake" config to pass to resolveConfigPresets and resolve globalPresets
+    const config = { extends: globalExtends };
+    const resolvedConfig = await resolveConfigPresets(config);
+    return resolvedConfig;
+  } catch (err) {
+    logger.error({ err }, 'Error resolving config preset');
+    throw new Error(CONFIG_PRESETS_INVALID);
+  }
+}
+
 export async function start(): Promise<number> {
   let config: AllConfig;
   try {
     // read global config from file, env and cli args
     config = await getGlobalConfig();
+    if (config?.globalExtends) {
+      // resolve global presets immediately
+      config = mergeChildConfig(
+        config,
+        await resolveGlobalExtends(config.globalExtends)
+      );
+    }
     // initialize all submodules
     config = await globalInitialize(config);