diff --git a/docs/usage/self-hosted-experimental.md b/docs/usage/self-hosted-experimental.md
index b05d952b2c7180c505bad10af15cc641681a1f7a..bd95401a8cfc64b6e0a3fcc5efebc7457e3bd62f 100644
--- a/docs/usage/self-hosted-experimental.md
+++ b/docs/usage/self-hosted-experimental.md
@@ -143,8 +143,8 @@ The expected value for this environment variable is a JSON array of strings.
 
 ## `RENOVATE_X_PLATFORM_VERSION`
 
-If set, Renovate will use this string as GitLab server version instead of checking via the GitLab API.
-This can be useful when you use the GitLab `CI_JOB_TOKEN` to authenticate Renovate.
+Specify this string for Renovate to skip API checks and provide GitLab/Bitbucket server version directly.
+Particularly useful with GitLab's `CI_JOB_TOKEN` to authenticate Renovate or to reduce API calls for Bitbucket.
 
 Read [platform details](modules/platform/gitlab/index.md) to learn why we need the server version on GitLab.
 
diff --git a/lib/modules/platform/bitbucket-server/index.spec.ts b/lib/modules/platform/bitbucket-server/index.spec.ts
index feb59252d1fa8bc0200b03e98d4b89e74548a526..1130a3bb678740309524d7322278c20f51e0918e 100644
--- a/lib/modules/platform/bitbucket-server/index.spec.ts
+++ b/lib/modules/platform/bitbucket-server/index.spec.ts
@@ -1,11 +1,13 @@
 import is from '@sindresorhus/is';
 import { mockDeep } from 'jest-mock-extended';
 import * as httpMock from '../../../../test/http-mock';
+import { mocked } from '../../../../test/util';
 import {
   REPOSITORY_CHANGED,
   REPOSITORY_EMPTY,
   REPOSITORY_NOT_FOUND,
 } from '../../../constants/error-messages';
+import type { logger as _logger } from '../../../logger';
 import type * as _git from '../../../util/git';
 import type { LongCommitSha } from '../../../util/git/types';
 import type { Platform } from '../types';
@@ -185,6 +187,7 @@ describe('modules/platform/bitbucket-server/index', () => {
 
       let hostRules: jest.Mocked<HostRules>;
       let git: jest.Mocked<typeof _git>;
+      let logger: jest.Mocked<typeof _logger>;
       const username = 'abc';
       const password = '123';
 
@@ -211,6 +214,7 @@ describe('modules/platform/bitbucket-server/index', () => {
         // reset module
         jest.resetModules();
         bitbucket = await import('.');
+        logger = mocked(await import('../../../logger')).logger;
         hostRules = jest.requireMock('../../../util/host-rules');
         git = jest.requireMock('../../../util/git');
         git.branchExists.mockReturnValue(true);
@@ -226,6 +230,10 @@ describe('modules/platform/bitbucket-server/index', () => {
           username,
           password,
         });
+        httpMock
+          .scope(urlHost)
+          .get(`${urlPath}/rest/api/1.0/application-properties`)
+          .reply(200, { version: '8.0.0' });
         await bitbucket.initPlatform({
           endpoint,
           username,
@@ -234,19 +242,52 @@ describe('modules/platform/bitbucket-server/index', () => {
       });
 
       describe('initPlatform()', () => {
-        it('should throw if no endpoint', () => {
+        it('should throw if no endpoint', async () => {
           expect.assertions(1);
-          expect(() => bitbucket.initPlatform({})).toThrow();
+          await expect(bitbucket.initPlatform({})).rejects.toThrow();
         });
 
-        it('should throw if no username/password', () => {
+        it('should throw if no username/password', async () => {
           expect.assertions(1);
-          expect(() =>
+          await expect(
             bitbucket.initPlatform({ endpoint: 'endpoint' }),
-          ).toThrow();
+          ).rejects.toThrow();
+        });
+
+        it('should throw if version could not be fetched', async () => {
+          httpMock
+            .scope('https://stash.renovatebot.com')
+            .get('/rest/api/1.0/application-properties')
+            .reply(403);
+
+          await bitbucket.initPlatform({
+            endpoint: 'https://stash.renovatebot.com',
+            username: 'abc',
+            password: '123',
+          });
+          expect(logger.debug).toHaveBeenCalledWith(
+            expect.any(Object),
+            'Error authenticating with Bitbucket. Check that your token includes "api" permissions',
+          );
+        });
+
+        it('should skip api call to fetch version when platform version is set in environment', async () => {
+          process.env.RENOVATE_X_PLATFORM_VERSION = '8.0.0';
+          await expect(
+            bitbucket.initPlatform({
+              endpoint: 'https://stash.renovatebot.com',
+              username: 'abc',
+              password: '123',
+            }),
+          ).toResolve();
+          delete process.env.RENOVATE_X_PLATFORM_VERSION;
         });
 
         it('should init', async () => {
+          httpMock
+            .scope('https://stash.renovatebot.com')
+            .get('/rest/api/1.0/application-properties')
+            .reply(200, { version: '8.0.0' });
           expect(
             await bitbucket.initPlatform({
               endpoint: 'https://stash.renovatebot.com',
diff --git a/lib/modules/platform/bitbucket-server/index.ts b/lib/modules/platform/bitbucket-server/index.ts
index 3b87a94a5916fccab6adf1eaddd1027c1989016e..32600a1926c59e79741b49ca7550735edd3c84f0 100644
--- a/lib/modules/platform/bitbucket-server/index.ts
+++ b/lib/modules/platform/bitbucket-server/index.ts
@@ -1,4 +1,5 @@
 import { setTimeout } from 'timers/promises';
+import semver from 'semver';
 import type { PartialDeep } from 'type-fest';
 import {
   REPOSITORY_CHANGED,
@@ -68,8 +69,10 @@ const bitbucketServerHttp = new BitbucketServerHttp();
 const defaults: {
   endpoint?: string;
   hostType: string;
+  version: string;
 } = {
   hostType: 'bitbucket-server',
+  version: '0.0.0',
 };
 
 /* istanbul ignore next */
@@ -79,7 +82,7 @@ function updatePrVersion(pr: number, version: number): number {
   return res;
 }
 
-export function initPlatform({
+export async function initPlatform({
   endpoint,
   username,
   password,
@@ -98,7 +101,32 @@ export function initPlatform({
   const platformConfig: PlatformResult = {
     endpoint: defaults.endpoint,
   };
-  return Promise.resolve(platformConfig);
+  try {
+    let bitbucketServerVersion: string;
+    // istanbul ignore if: experimental feature
+    if (process.env.RENOVATE_X_PLATFORM_VERSION) {
+      bitbucketServerVersion = process.env.RENOVATE_X_PLATFORM_VERSION;
+    } else {
+      const { version } = (
+        await bitbucketServerHttp.getJson<{ version: string }>(
+          `./rest/api/1.0/application-properties`,
+        )
+      ).body;
+      bitbucketServerVersion = version;
+      logger.debug('Bitbucket Server version is: ' + bitbucketServerVersion);
+    }
+
+    if (semver.valid(bitbucketServerVersion)) {
+      defaults.version = bitbucketServerVersion;
+    }
+  } catch (err) {
+    logger.debug(
+      { err },
+      'Error authenticating with Bitbucket. Check that your token includes "api" permissions',
+    );
+  }
+
+  return platformConfig;
 }
 
 // Get all repositories that the user has access to
@@ -204,7 +232,7 @@ export async function initRepo({
       ...config,
       url,
       cloneSubmodules,
-      fullClone: true,
+      fullClone: semver.lte(defaults.version, '8.0.0'),
     });
 
     config.mergeMethod = 'merge';