diff --git a/docs/usage/self-hosted-configuration.md b/docs/usage/self-hosted-configuration.md
index 998f6d9a2b18193c95f88619a54b4a74756cc05e..8247faa8f5c9ca5d371e480091502071c3d83630 100644
--- a/docs/usage/self-hosted-configuration.md
+++ b/docs/usage/self-hosted-configuration.md
@@ -383,7 +383,7 @@ To handle the case where the underlying Git processes appear to hang, configure
 ## gitUrl
 
 Override the default resolution for Git remote, e.g. to switch GitLab from HTTPS to SSH-based.
-Currently works for GitLab only.
+Currently works for Bitbucket Server and GitLab only.
 
 Possible values:
 
diff --git a/lib/config/options/index.ts b/lib/config/options/index.ts
index 37bb30e434b1448bbea632063b42e21097c6ceff..bcc167f8d0ae6e9df1f93201b4fe483d6db44089 100644
--- a/lib/config/options/index.ts
+++ b/lib/config/options/index.ts
@@ -2295,6 +2295,7 @@ const options: RenovateOptions[] = [
     description:
       'Overrides the default resolution for Git remote, e.g. to switch GitLab from HTTPS to SSH-based.',
     type: 'string',
+    supportedPlatforms: ['gitlab', 'bitbucket-server'],
     allowedValues: ['default', 'ssh', 'endpoint'],
     default: 'default',
     stage: 'repository',
diff --git a/lib/modules/platform/bitbucket-server/index.spec.ts b/lib/modules/platform/bitbucket-server/index.spec.ts
index 4883352ff9648c32dfa506f9b3efcbe79fa351be..8faf6f06a6185e951d213710cf9690e984a40e90 100644
--- a/lib/modules/platform/bitbucket-server/index.spec.ts
+++ b/lib/modules/platform/bitbucket-server/index.spec.ts
@@ -292,6 +292,113 @@ describe('modules/platform/bitbucket-server/index', () => {
           ).toMatchSnapshot();
         });
 
+        it('no git url', async () => {
+          expect.assertions(1);
+          httpMock
+            .scope(urlHost)
+            .get(`${urlPath}/rest/api/1.0/projects/SOME/repos/repo`)
+            .reply(200, repoMock(url, 'SOME', 'repo'))
+            .get(
+              `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/branches/default`
+            )
+            .reply(200, {
+              displayId: 'master',
+            });
+          expect(
+            await bitbucket.initRepo({
+              endpoint: 'https://stash.renovatebot.com/vcs/',
+              repository: 'SOME/repo',
+            })
+          ).toEqual({ defaultBranch: 'master', isFork: false });
+        });
+
+        it('gitUrl ssh returns ssh url', async () => {
+          expect.assertions(2);
+          const responseMock = repoMock(url, 'SOME', 'repo', {
+            cloneUrl: { https: false, ssh: true },
+          });
+          httpMock
+            .scope(urlHost)
+            .get(`${urlPath}/rest/api/1.0/projects/SOME/repos/repo`)
+            .reply(200, responseMock)
+            .get(
+              `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/branches/default`
+            )
+            .reply(200, {
+              displayId: 'master',
+            });
+          const res = await bitbucket.initRepo({
+            endpoint: 'https://stash.renovatebot.com/vcs/',
+            repository: 'SOME/repo',
+            gitUrl: 'ssh',
+          });
+          expect(git.initRepo).toHaveBeenCalledWith(
+            expect.objectContaining({ url: sshLink('SOME', 'repo') })
+          );
+          expect(res).toEqual({ defaultBranch: 'master', isFork: false });
+        });
+
+        it('gitURL endpoint returns generates endpoint URL', async () => {
+          expect.assertions(2);
+          const link = httpLink(url.toString(), 'SOME', 'repo');
+          const responseMock = repoMock(url, 'SOME', 'repo', {
+            cloneUrl: { https: false, ssh: false },
+          });
+          httpMock
+            .scope(urlHost)
+            .get(`${urlPath}/rest/api/1.0/projects/SOME/repos/repo`)
+            .reply(200, responseMock)
+            .get(
+              `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/branches/default`
+            )
+            .reply(200, {
+              displayId: 'master',
+            });
+          git.getUrl.mockReturnValueOnce(link);
+          const res = await bitbucket.initRepo({
+            endpoint: 'https://stash.renovatebot.com/vcs/',
+            repository: 'SOME/repo',
+            gitUrl: 'endpoint',
+          });
+          expect(git.initRepo).toHaveBeenCalledWith(
+            expect.objectContaining({
+              url: link,
+            })
+          );
+          expect(res).toEqual({ defaultBranch: 'master', isFork: false });
+        });
+
+        it('gitUrl default returns http from API with injected auth', async () => {
+          expect.assertions(2);
+          const responseMock = repoMock(url, 'SOME', 'repo', {
+            cloneUrl: { https: true, ssh: true },
+          });
+          httpMock
+            .scope(urlHost)
+            .get(`${urlPath}/rest/api/1.0/projects/SOME/repos/repo`)
+            .reply(200, responseMock)
+            .get(
+              `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/branches/default`
+            )
+            .reply(200, {
+              displayId: 'master',
+            });
+          const res = await bitbucket.initRepo({
+            endpoint: 'https://stash.renovatebot.com/vcs/',
+            repository: 'SOME/repo',
+            gitUrl: 'default',
+          });
+          expect(git.initRepo).toHaveBeenCalledWith(
+            expect.objectContaining({
+              url: httpLink(url.toString(), 'SOME', 'repo').replace(
+                'https://',
+                `https://${username}:${password}@`
+              ),
+            })
+          );
+          expect(res).toEqual({ defaultBranch: 'master', isFork: false });
+        });
+
         it('uses ssh url from API if http not in API response', async () => {
           expect.assertions(2);
           const responseMock = repoMock(url, 'SOME', 'repo', {
diff --git a/lib/modules/platform/bitbucket-server/index.ts b/lib/modules/platform/bitbucket-server/index.ts
index 212f01a481bcb11fe032724c25708e1fe7a32203..289bb36418e33b42bb37c492d87646df3235e8cf 100644
--- a/lib/modules/platform/bitbucket-server/index.ts
+++ b/lib/modules/platform/bitbucket-server/index.ts
@@ -154,6 +154,7 @@ export async function initRepo({
   repository,
   cloneSubmodules,
   ignorePrAuthor,
+  gitUrl,
 }: RepoParams): Promise<RepoResult> {
   logger.debug(`initRepo("${JSON.stringify({ repository }, null, 2)}")`);
   const opts = hostRules.find({
@@ -189,17 +190,18 @@ export async function initRepo({
       throw new Error(REPOSITORY_EMPTY);
     }
 
-    const gitUrl = utils.getRepoGitUrl(
+    const url = utils.getRepoGitUrl(
       config.repositorySlug,
       // TODO #7154
       defaults.endpoint!,
+      gitUrl,
       info,
       opts
     );
 
     await git.initRepo({
       ...config,
-      url: gitUrl,
+      url,
       cloneSubmodules,
       fullClone: true,
     });
diff --git a/lib/modules/platform/bitbucket-server/types.ts b/lib/modules/platform/bitbucket-server/types.ts
index fe5d4f2f74515a9247ebea4b3614d3845ffd0ac3..a856ffea5980d69f076f9917daa1a90fc2c6ee22 100644
--- a/lib/modules/platform/bitbucket-server/types.ts
+++ b/lib/modules/platform/bitbucket-server/types.ts
@@ -51,7 +51,7 @@ export interface BbsRestRepo {
   project: { key: string };
   origin: { name: string; slug: string };
   links: {
-    clone: { href: string; name: string }[];
+    clone?: { href: string; name: string }[];
   };
 }
 
diff --git a/lib/modules/platform/bitbucket-server/utils.spec.ts b/lib/modules/platform/bitbucket-server/utils.spec.ts
index c8598c21079d2a8b0635fdd6cf1f279b54789fad..460a94a8277246fc830af38c9581860de87acf75 100644
--- a/lib/modules/platform/bitbucket-server/utils.spec.ts
+++ b/lib/modules/platform/bitbucket-server/utils.spec.ts
@@ -1,11 +1,80 @@
 import type { Response } from 'got';
 import { partial } from '../../../../test/util';
-import type { BitbucketError, BitbucketErrorResponse } from './types';
+import { CONFIG_GIT_URL_UNAVAILABLE } from '../../../constants/error-messages';
+import type {
+  BbsRestRepo,
+  BitbucketError,
+  BitbucketErrorResponse,
+} from './types';
 import {
   BITBUCKET_INVALID_REVIEWERS_EXCEPTION,
   getInvalidReviewers,
+  getRepoGitUrl,
 } from './utils';
 
+function sshLink(projectKey: string, repositorySlug: string): string {
+  return `ssh://git@stash.renovatebot.com:7999/${projectKey.toLowerCase()}/${repositorySlug}.git`;
+}
+
+function httpLink(
+  endpointStr: string,
+  projectKey: string,
+  repositorySlug: string
+): string {
+  return `${endpointStr}scm/${projectKey}/${repositorySlug}.git`;
+}
+
+function infoMock(
+  endpoint: URL | string,
+  projectKey: string,
+  repositorySlug: string,
+  options: { cloneUrl: { https: boolean; ssh: boolean } } = {
+    cloneUrl: { https: true, ssh: true },
+  }
+): BbsRestRepo {
+  const endpointStr = endpoint.toString();
+  const links: {
+    self: { href: string }[];
+    clone?: { href: string; name: string }[];
+  } = {
+    self: [
+      {
+        href: `${endpointStr}projects/${projectKey}/repos/${repositorySlug}/browse`,
+      },
+    ],
+  };
+
+  if (options.cloneUrl.https || options.cloneUrl.ssh) {
+    links.clone = [];
+    if (options.cloneUrl.https) {
+      links.clone.push({
+        href: httpLink(endpointStr, projectKey, repositorySlug),
+        name: 'http',
+      });
+    }
+
+    if (options.cloneUrl.ssh) {
+      links.clone.push({
+        href: sshLink(projectKey, repositorySlug),
+        name: 'ssh',
+      });
+    }
+    return {
+      project: { key: projectKey },
+      origin: { name: repositorySlug, slug: repositorySlug },
+      links,
+    } as BbsRestRepo;
+  } else {
+    // This mimics the behavior of bb-server which does not include the clone property at all
+    // if ssh and https are both turned off
+    return {
+      project: { key: projectKey },
+      origin: { name: repositorySlug, slug: repositorySlug },
+      links: { clone: undefined },
+    } as BbsRestRepo;
+  }
+}
+
 describe('modules/platform/bitbucket-server/utils', () => {
   function createError(
     body: Partial<BitbucketErrorResponse> | undefined = undefined
@@ -37,4 +106,167 @@ describe('modules/platform/bitbucket-server/utils', () => {
       )
     ).toStrictEqual([]);
   });
+
+  const scenarios = {
+    'endpoint with no path': new URL('https://stash.renovatebot.com'),
+    'endpoint with path': new URL('https://stash.renovatebot.com/vcs/'),
+  };
+
+  describe('getRepoGitUrl', () => {
+    Object.entries(scenarios).forEach(([scenarioName, url]) => {
+      describe(scenarioName, () => {
+        const username = 'abc';
+        const password = '123';
+        const opts = {
+          username: username,
+          password: password,
+        };
+
+        it('works gitUrl:undefined generate endpoint', () => {
+          expect(
+            getRepoGitUrl(
+              'SOME/repo',
+              url.toString(),
+              undefined,
+              infoMock(url, 'SOME', 'repo', {
+                cloneUrl: { https: false, ssh: false },
+              }),
+              opts
+            )
+          ).toBe(
+            httpLink(url.toString(), 'SOME', 'repo').replace(
+              'https://',
+              `https://${username}:${password}@`
+            )
+          );
+        });
+
+        it('works gitUrl:undefined use endpoint with injected auth', () => {
+          expect(
+            getRepoGitUrl(
+              'SOME/repo',
+              url.toString(),
+              undefined,
+              infoMock(url, 'SOME', 'repo', {
+                cloneUrl: { https: true, ssh: false },
+              }),
+              opts
+            )
+          ).toBe(
+            httpLink(url.toString(), 'SOME', 'repo').replace(
+              'https://',
+              `https://${username}:${password}@`
+            )
+          );
+        });
+
+        it('works gitUrl:undefined use ssh', () => {
+          expect(
+            getRepoGitUrl(
+              'SOME/repo',
+              url.toString(),
+              undefined,
+              infoMock(url, 'SOME', 'repo', {
+                cloneUrl: { https: false, ssh: true },
+              }),
+              opts
+            )
+          ).toBe(sshLink('SOME', 'repo'));
+        });
+
+        it('works gitUrl:default', () => {
+          expect(
+            getRepoGitUrl(
+              'SOME/repo',
+              url.toString(),
+              'default',
+              infoMock(url, 'SOME', 'repo'),
+              opts
+            )
+          ).toBe(
+            httpLink(url.toString(), 'SOME', 'repo').replace(
+              'https://',
+              `https://${username}:${password}@`
+            )
+          );
+        });
+
+        it('gitUrl:default invalid http url throws CONFIG_GIT_URL_UNAVAILABLE', () => {
+          expect(() =>
+            getRepoGitUrl(
+              'SOME/repo',
+              url.toString(),
+              'default',
+              infoMock('invalidUrl', 'SOME', 'repo', {
+                cloneUrl: { https: true, ssh: false },
+              }),
+              opts
+            )
+          ).toThrow(Error(CONFIG_GIT_URL_UNAVAILABLE));
+        });
+
+        it('gitUrl:default no http url returns generated url', () => {
+          expect(
+            getRepoGitUrl(
+              'SOME/repo',
+              url.toString(),
+              'default',
+              infoMock(url, 'SOME', 'repo', {
+                cloneUrl: { https: false, ssh: false },
+              }),
+              opts
+            )
+          ).toBe(
+            httpLink(url.toString(), 'SOME', 'repo').replace(
+              'https://',
+              `https://${username}:${password}@`
+            )
+          );
+        });
+
+        it('gitUrl:ssh no ssh url throws CONFIG_GIT_URL_UNAVAILABLE', () => {
+          expect(() =>
+            getRepoGitUrl(
+              'SOME/repo',
+              url.toString(),
+              'ssh',
+              infoMock(url, 'SOME', 'repo', {
+                cloneUrl: { https: false, ssh: false },
+              }),
+              opts
+            )
+          ).toThrow(Error(CONFIG_GIT_URL_UNAVAILABLE));
+        });
+
+        it('works gitUrl:ssh', () => {
+          expect(
+            getRepoGitUrl(
+              'SOME/repo',
+              url.toString(),
+              'ssh',
+              infoMock(url, 'SOME', 'repo'),
+              opts
+            )
+          ).toBe(sshLink('SOME', 'repo'));
+        });
+
+        it('works gitUrl:endpoint', () => {
+          expect(
+            getRepoGitUrl(
+              'SOME/repo',
+              url.toString(),
+              'endpoint',
+              infoMock(url, 'SOME', 'repo'),
+              opts
+            )
+          ).toBe(
+            httpLink(url.toString(), 'SOME', 'repo').replace(
+              'https://',
+              `https://${username}:${password}@`
+            )
+          );
+        });
+      });
+    });
+  });
 });
diff --git a/lib/modules/platform/bitbucket-server/utils.ts b/lib/modules/platform/bitbucket-server/utils.ts
index 4031166e97b38ae210a515624a430085ebb5f73e..a3c1c8ab3b26b5b18c497eed88852295b6bf5a43 100644
--- a/lib/modules/platform/bitbucket-server/utils.ts
+++ b/lib/modules/platform/bitbucket-server/utils.ts
@@ -1,6 +1,8 @@
 // SEE for the reference https://github.com/renovatebot/renovate/blob/c3e9e572b225085448d94aa121c7ec81c14d3955/lib/platform/bitbucket/utils.js
 import url from 'url';
 import is from '@sindresorhus/is';
+import { CONFIG_GIT_URL_UNAVAILABLE } from '../../../constants/error-messages';
+import { logger } from '../../../logger';
 import { HostRule, PrState } from '../../../types';
 import type { GitProtocol } from '../../../types/git';
 import * as git from '../../../util/git';
@@ -10,7 +12,9 @@ import type {
   HttpPostOptions,
   HttpResponse,
 } from '../../../util/http/types';
+import { parseUrl } from '../../../util/url';
 import { getPrBodyStruct } from '../pr-body';
+import type { GitUrlOption } from '../types';
 import type { BbsPr, BbsRestPr, BbsRestRepo, BitbucketError } from './types';
 
 export const BITBUCKET_INVALID_REVIEWERS_EXCEPTION =
@@ -156,39 +160,62 @@ export function getInvalidReviewers(err: BitbucketError): string[] {
   return invalidReviewers;
 }
 
+function generateUrlFromEndpoint(
+  defaultEndpoint: string,
+  opts: HostRule,
+  repository: string
+): string {
+  const url = new URL(defaultEndpoint);
+  const generatedUrl = git.getUrl({
+    protocol: url.protocol as GitProtocol,
+    auth: `${opts.username}:${opts.password}`,
+    host: `${url.host}${url.pathname}${
+      // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
+      url.pathname!.endsWith('/') ? '' : /* istanbul ignore next */ '/'
+    }scm`,
+    repository,
+  });
+  logger.debug({ url: generatedUrl }, `using generated endpoint URL`);
+  return generatedUrl;
+}
+
+function injectAuth(url: string, opts: HostRule): string {
+  const repoUrl = parseUrl(url)!;
+  if (!repoUrl) {
+    logger.debug(`Invalid url: ${url}`);
+    throw new Error(CONFIG_GIT_URL_UNAVAILABLE);
+  }
+  // TODO: null checks (#7154)
+  repoUrl.username = opts.username!;
+  repoUrl.password = opts.password!;
+  return repoUrl.toString();
+}
+
 export function getRepoGitUrl(
   repository: string,
   defaultEndpoint: string,
+  gitUrl: GitUrlOption | undefined,
   info: BbsRestRepo,
   opts: HostRule
 ): string {
-  let cloneUrl = info.links.clone?.find(({ name }) => name === 'http');
-  if (!cloneUrl) {
-    // Http access might be disabled, try to find ssh url in this case
-    cloneUrl = info.links.clone?.find(({ name }) => name === 'ssh');
+  if (gitUrl === 'ssh') {
+    const sshUrl = info.links.clone?.find(({ name }) => name === 'ssh');
+    if (sshUrl === undefined) {
+      throw new Error(CONFIG_GIT_URL_UNAVAILABLE);
+    }
+    logger.debug({ url: sshUrl.href }, `using ssh URL`);
+    return sshUrl.href;
   }
-
-  let gitUrl: string;
-  if (!cloneUrl) {
-    // Fallback to generating the url if the API didn't give us an URL
-    const { host, pathname } = url.parse(defaultEndpoint);
-    // TODO #7154
-    gitUrl = git.getUrl({
-      protocol: defaultEndpoint.split(':')[0] as GitProtocol,
-      auth: `${opts.username}:${opts.password}`,
-      host: `${host}${pathname}${
-        pathname!.endsWith('/') ? '' : /* istanbul ignore next */ '/'
-      }scm`,
-      repository,
-    });
-  } else if (cloneUrl.name === 'http') {
+  let cloneUrl = info.links.clone?.find(({ name }) => name === 'http');
+  if (cloneUrl) {
     // Inject auth into the API provided URL
-    const repoUrl = url.parse(cloneUrl.href);
-    repoUrl.auth = `${opts.username}:${opts.password}`;
-    gitUrl = url.format(repoUrl);
-  } else {
-    // SSH urls can be used directly
-    gitUrl = cloneUrl.href;
+    return injectAuth(cloneUrl.href, opts);
+  }
+  // Http access might be disabled, try to find ssh url in this case
+  cloneUrl = info.links.clone?.find(({ name }) => name === 'ssh');
+  if (gitUrl === 'endpoint' || !cloneUrl) {
+    return generateUrlFromEndpoint(defaultEndpoint, opts, repository);
   }
-  return gitUrl;
+  // SSH urls can be used directly
+  return cloneUrl.href;
 }