diff --git a/docs/usage/self-hosted-experimental.md b/docs/usage/self-hosted-experimental.md
index d7d7348927361d1ea937bac20602ba38692f89f7..65120ead158cba0fa47a936372d7af8cf85d7693 100644
--- a/docs/usage/self-hosted-experimental.md
+++ b/docs/usage/self-hosted-experimental.md
@@ -93,6 +93,10 @@ If set to any value, Renovate will use the Docker Hub API (`https://hub.docker.c
 If set to an integer, Renovate will use this as max page number for docker tags lookup on docker registries, instead of the default 20 pages.
 This is useful for registries which ignores the `n` parameter in the query string and only return 50 tags per page.
 
+## `RENOVATE_X_EAGER_GLOBAL_EXTENDS`
+
+Resolve and merge `globalExtends` presets before other global config, instead of after.
+
 ## `RENOVATE_X_EXEC_GPID_HANDLE`
 
 If set, Renovate will terminate the whole process group of a terminated child process spawned by Renovate.
diff --git a/lib/workers/global/index.spec.ts b/lib/workers/global/index.spec.ts
index 6ee3c4a84e185949254023ae2c85298b10e51df3..a75815c0252165158e67c92d2d3bccca812c1516 100644
--- a/lib/workers/global/index.spec.ts
+++ b/lib/workers/global/index.spec.ts
@@ -45,6 +45,7 @@ describe('workers/global/index', () => {
     initPlatform.mockImplementation((input) => Promise.resolve(input));
     delete process.env.AWS_SECRET_ACCESS_KEY;
     delete process.env.AWS_SESSION_TOKEN;
+    delete process.env.RENOVATE_X_EAGER_GLOBAL_EXTENDS;
   });
 
   describe('getRepositoryConfig', () => {
@@ -87,6 +88,21 @@ describe('workers/global/index', () => {
     expect(addSecretForSanitizing).toHaveBeenCalledTimes(2);
   });
 
+  it('resolves global presets first', async () => {
+    process.env.RENOVATE_X_EAGER_GLOBAL_EXTENDS = 'true';
+    parseConfigs.mockResolvedValueOnce({
+      repositories: [],
+      globalExtends: [':pinVersions'],
+      hostRules: [{ matchHost: 'github.com', token: 'abc123' }],
+    });
+    presets.resolveConfigPresets.mockResolvedValueOnce({});
+    await expect(globalWorker.start()).resolves.toBe(0);
+    expect(presets.resolveConfigPresets).toHaveBeenCalledWith({
+      extends: [':pinVersions'],
+    });
+    expect(parseConfigs).toHaveBeenCalledTimes(1);
+  });
+
   it('resolves global presets immediately', async () => {
     parseConfigs.mockResolvedValueOnce({
       repositories: [],
diff --git a/lib/workers/global/index.ts b/lib/workers/global/index.ts
index 1046164ce7165055896e259c08006206e7bbece1..bc691ccd344436e5ff7ed7e73d373f269fb35996 100644
--- a/lib/workers/global/index.ts
+++ b/lib/workers/global/index.ts
@@ -144,10 +144,17 @@ export async function start(): Promise<number> {
       config = await getGlobalConfig();
       if (config?.globalExtends) {
         // resolve global presets immediately
-        config = mergeChildConfig(
-          config,
-          await resolveGlobalExtends(config.globalExtends),
-        );
+        if (process.env.RENOVATE_X_EAGER_GLOBAL_EXTENDS) {
+          config = mergeChildConfig(
+            await resolveGlobalExtends(config.globalExtends),
+            config,
+          );
+        } else {
+          config = mergeChildConfig(
+            config,
+            await resolveGlobalExtends(config.globalExtends),
+          );
+        }
       }
       // initialize all submodules
       config = await globalInitialize(config);