diff --git a/docs/usage/self-hosted-configuration.md b/docs/usage/self-hosted-configuration.md
index 66685a10e25e68ae1b141663f70c4029a45ea8e4..a4a02f2e354a6a39e4d346a42e969900f1ba59e7 100644
--- a/docs/usage/self-hosted-configuration.md
+++ b/docs/usage/self-hosted-configuration.md
@@ -238,6 +238,16 @@ Before the first commit in a repository, Renovate will:
 The `git` commands are run locally in the cloned repo instead of globally.
 This reduces the chance of unintended consequences with global Git configs on shared systems.
 
+## gitUrl
+
+Override the default resolution for git remote, e.g. to switch GitLab from HTTPS to SSH-based. Currently works for GitLab only.
+
+Possible values:
+
+- `default`: use HTTP URLs provided by the platform for Git
+- `ssh`: use SSH URLs provided by the platform for Git
+- `endpoint`: ignore URLs provided by the platform and use the configured endpoint directly
+
 ## logContext
 
 `logContext` is included with each log entry only if `logFormat="json"` - it is not included in the pretty log output.
diff --git a/docs/usage/self-hosted-experimental.md b/docs/usage/self-hosted-experimental.md
index 2f00716900c3b6269365ef2dc5a4ef8e863ac827..556152ca162a59a287b6d374c2a424d17306230f 100644
--- a/docs/usage/self-hosted-experimental.md
+++ b/docs/usage/self-hosted-experimental.md
@@ -14,10 +14,6 @@ We do not follow Semantic Versioning for any experimental variables.
 These variables may be removed or have their behavior changed in **any** version.
 We will try to keep breakage to a minimum, but make no guarantees that a experimental variable will keep working.
 
-## GITLAB_IGNORE_REPO_URL
-
-If set to any value, Renovate will ignore the Project's `http_url_to_repo` value and instead construct the Git URL manually.
-
 ## RENOVATE_CACHE_NPM_MINUTES
 
 If set to any integer, Renovate will use this integer instead of the default npm cache time (15 minutes) for the npm datasource.
diff --git a/lib/config/options/index.ts b/lib/config/options/index.ts
index 4017df1c3613ceae91f3fae0c3934ad8f79df786..b796b0990ddd569b8401dc8914cc3f83b404cccf 100644
--- a/lib/config/options/index.ts
+++ b/lib/config/options/index.ts
@@ -2030,6 +2030,16 @@ const options: RenovateOptions[] = [
     type: 'boolean',
     default: true,
   },
+  {
+    name: 'gitUrl',
+    description:
+      'Overrides the default resolution for git remote, e.g. to switch GitLab from HTTPS to SSH-based.',
+    type: 'string',
+    allowedValues: ['default', 'ssh', 'endpoint'],
+    default: 'default',
+    stage: 'repository',
+    globalOnly: true,
+  },
 ];
 
 export function getOptions(): RenovateOptions[] {
diff --git a/lib/constants/error-messages.ts b/lib/constants/error-messages.ts
index eec1bbff1ecdff253c46dd0b3b7a63c47dc4b43f..a71b6ae9fa39fbf28a49b8411ddc97f858d279a0 100644
--- a/lib/constants/error-messages.ts
+++ b/lib/constants/error-messages.ts
@@ -15,6 +15,7 @@ export const CONFIG_VALIDATION = 'config-validation';
 export const CONFIG_PRESETS_INVALID = 'config-presets-invalid';
 export const CONFIG_SECRETS_EXPOSED = 'config-secrets-exposed';
 export const CONFIG_SECRETS_INVALID = 'config-secrets-invalid';
+export const CONFIG_GIT_URL_UNAVAILABLE = 'config-git-url-unavailable';
 
 // Repository Errors - causes repo to be considered as disabled
 export const REPOSITORY_ACCESS_FORBIDDEN = 'forbidden';
diff --git a/lib/platform/gitlab/__snapshots__/index.spec.ts.snap b/lib/platform/gitlab/__snapshots__/index.spec.ts.snap
index 8bd6f87c7dd2f1fe696765f76267850cedbe2df8..5d4c684615dd188981291825b4ddcd9a02be1266 100644
--- a/lib/platform/gitlab/__snapshots__/index.spec.ts.snap
+++ b/lib/platform/gitlab/__snapshots__/index.spec.ts.snap
@@ -2653,6 +2653,39 @@ Array [
 ]
 `;
 
+exports[`platform/gitlab/index initRepo should use ssh_url_to_repo if gitUrl is set to ssh 1`] = `
+Array [
+  Object {
+    "headers": Object {
+      "accept": "application/json",
+      "accept-encoding": "gzip, deflate, br",
+      "authorization": "Bearer abc123",
+      "host": "gitlab.com",
+      "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)",
+    },
+    "method": "GET",
+    "url": "https://gitlab.com/api/v4/projects/some%2Frepo%2Fproject",
+  },
+]
+`;
+
+exports[`platform/gitlab/index initRepo should use ssh_url_to_repo if gitUrl is set to ssh 2`] = `
+Array [
+  Array [
+    Object {
+      "cloneSubmodules": undefined,
+      "defaultBranch": "master",
+      "gitAuthorEmail": undefined,
+      "gitAuthorName": undefined,
+      "ignorePrAuthor": undefined,
+      "mergeMethod": "merge",
+      "repository": "some%2Frepo%2Fproject",
+      "url": "ssh://git@gitlab.com/some%2Frepo%2Fproject.git",
+    },
+  ],
+]
+`;
+
 exports[`platform/gitlab/index initRepo should fall back respecting when GITLAB_IGNORE_REPO_URL is set 1`] = `
 Array [
   Array [
diff --git a/lib/platform/gitlab/index.spec.ts b/lib/platform/gitlab/index.spec.ts
index 66fc051eca391a96c067ee740e613e140b332297..9a27a2399a49b158e897f99d795b7ae0e46a5f44 100644
--- a/lib/platform/gitlab/index.spec.ts
+++ b/lib/platform/gitlab/index.spec.ts
@@ -285,6 +285,23 @@ describe('platform/gitlab/index', () => {
       expect(httpMock.getTrace()).toMatchSnapshot();
     });
 
+    it('should use ssh_url_to_repo if gitUrl is set to ssh', async () => {
+      httpMock
+        .scope(gitlabApiHost)
+        .get('/api/v4/projects/some%2Frepo%2Fproject')
+        .reply(200, {
+          default_branch: 'master',
+          http_url_to_repo: `https://gitlab.com/some%2Frepo%2Fproject.git`,
+          ssh_url_to_repo: `ssh://git@gitlab.com/some%2Frepo%2Fproject.git`,
+        });
+      await gitlab.initRepo({
+        repository: 'some/repo/project',
+        gitUrl: 'ssh',
+      });
+      expect(httpMock.getTrace()).toMatchSnapshot();
+      expect(git.initRepo.mock.calls).toMatchSnapshot();
+    });
+
     it('should fall back respecting when GITLAB_IGNORE_REPO_URL is set', async () => {
       process.env.GITLAB_IGNORE_REPO_URL = 'true';
       const selfHostedUrl = 'http://mycompany.com/gitlab';
diff --git a/lib/platform/gitlab/index.ts b/lib/platform/gitlab/index.ts
index 33ab186ef322621ac9f4920acf3ea55ba12d891b..bd2e7afd6d2250b63bbe7d4bbfdf47b3ef1e50d2 100644
--- a/lib/platform/gitlab/index.ts
+++ b/lib/platform/gitlab/index.ts
@@ -4,6 +4,7 @@ import delay from 'delay';
 import pAll from 'p-all';
 import { lt } from 'semver';
 import {
+  CONFIG_GIT_URL_UNAVAILABLE,
   PLATFORM_AUTHENTICATION_ERROR,
   REPOSITORY_ACCESS_FORBIDDEN,
   REPOSITORY_ARCHIVED,
@@ -30,6 +31,7 @@ import type {
   EnsureCommentRemovalConfig,
   EnsureIssueConfig,
   FindPRConfig,
+  GitUrlOption,
   Issue,
   MergePRConfig,
   PlatformParams,
@@ -162,11 +164,62 @@ export async function getJsonFile(
   return JSON.parse(raw);
 }
 
+function getRepoUrl(
+  repository: string,
+  gitUrl: GitUrlOption | undefined,
+  res: HttpResponse<RepoResponse>
+): string {
+  if (gitUrl === 'ssh') {
+    if (!res.body.ssh_url_to_repo) {
+      throw new Error(CONFIG_GIT_URL_UNAVAILABLE);
+    }
+    logger.debug({ url: res.body.ssh_url_to_repo }, `using ssh URL`);
+    return res.body.ssh_url_to_repo;
+  }
+
+  const opts = hostRules.find({
+    hostType: defaults.hostType,
+    url: defaults.endpoint,
+  });
+
+  if (
+    gitUrl === 'endpoint' ||
+    process.env.GITLAB_IGNORE_REPO_URL ||
+    res.body.http_url_to_repo === null
+  ) {
+    if (res.body.http_url_to_repo === null) {
+      logger.debug('no http_url_to_repo found. Falling back to old behaviour.');
+    }
+    if (process.env.GITLAB_IGNORE_REPO_URL) {
+      logger.warn(
+        'GITLAB_IGNORE_REPO_URL environment variable is deprecated. Please use "gitUrl" option.'
+      );
+    }
+
+    const { protocol, host, pathname } = parseUrl(defaults.endpoint);
+    const newPathname = pathname.slice(0, pathname.indexOf('/api'));
+    const url = URL.format({
+      protocol: protocol.slice(0, -1) || 'https',
+      auth: 'oauth2:' + opts.token,
+      host,
+      pathname: newPathname + '/' + repository + '.git',
+    });
+    logger.debug({ url }, 'using URL based on configured endpoint');
+    return url;
+  }
+
+  logger.debug({ url: res.body.http_url_to_repo }, `using http URL`);
+  const repoUrl = URL.parse(`${res.body.http_url_to_repo}`);
+  repoUrl.auth = 'oauth2:' + opts.token;
+  return URL.format(repoUrl);
+}
+
 // Initialize GitLab by getting base branch
 export async function initRepo({
   repository,
   cloneSubmodules,
   ignorePrAuthor,
+  gitUrl,
 }: RepoParams): Promise<RepoResult> {
   config = {} as any;
   config.repository = urlEscape(repository);
@@ -220,31 +273,7 @@ export async function initRepo({
     logger.debug(`${repository} default branch = ${config.defaultBranch}`);
     delete config.prList;
     logger.debug('Enabling Git FS');
-    const opts = hostRules.find({
-      hostType: defaults.hostType,
-      url: defaults.endpoint,
-    });
-    let url: string;
-    if (
-      process.env.GITLAB_IGNORE_REPO_URL ||
-      res.body.http_url_to_repo === null
-    ) {
-      logger.debug('no http_url_to_repo found. Falling back to old behaviour.');
-      const { protocol, host, pathname } = parseUrl(defaults.endpoint);
-      const newPathname = pathname.slice(0, pathname.indexOf('/api'));
-      url = URL.format({
-        protocol: protocol.slice(0, -1) || 'https',
-        auth: 'oauth2:' + opts.token,
-        host,
-        pathname: newPathname + '/' + repository + '.git',
-      });
-      logger.debug({ url }, 'using URL based on configured endpoint');
-    } else {
-      logger.debug(`${repository} http URL = ${res.body.http_url_to_repo}`);
-      const repoUrl = URL.parse(`${res.body.http_url_to_repo}`);
-      repoUrl.auth = 'oauth2:' + opts.token;
-      url = URL.format(repoUrl);
-    }
+    const url = getRepoUrl(repository, gitUrl, res);
     await git.initRepo({
       ...config,
       url,
diff --git a/lib/platform/gitlab/types.ts b/lib/platform/gitlab/types.ts
index 4a59858488afc6770e94c2f8dd356a6e70bba237..aaffe7386ec5715bcee1c16ba937bf477d35dbc5 100644
--- a/lib/platform/gitlab/types.ts
+++ b/lib/platform/gitlab/types.ts
@@ -47,6 +47,7 @@ export interface RepoResponse {
   mirror: boolean;
   default_branch: string;
   empty_repo: boolean;
+  ssh_url_to_repo: string;
   http_url_to_repo: string;
   forked_from_project: boolean;
   repository_access_level: 'disabled' | 'private' | 'enabled';
diff --git a/lib/platform/types.ts b/lib/platform/types.ts
index 020247e95576e8059837304c09b370d5d3df99e3..04981cff72e02a117779b5edf2cede8c9258a96e 100644
--- a/lib/platform/types.ts
+++ b/lib/platform/types.ts
@@ -27,9 +27,12 @@ export interface RepoResult {
   isFork: boolean;
 }
 
+export type GitUrlOption = 'default' | 'ssh' | 'endpoint';
+
 export interface RepoParams {
   repository: string;
   endpoint?: string;
+  gitUrl?: GitUrlOption;
   forkMode?: string;
   forkToken?: string;
   includeForks?: boolean;