diff --git a/lib/modules/datasource/terraform-module/index.ts b/lib/modules/datasource/terraform-module/index.ts
index 7e412935d5ca658fe7f833cda2f9e6e9f46a0f1e..eda82254ae89b7acedf5656e62d07d31d135623b 100644
--- a/lib/modules/datasource/terraform-module/index.ts
+++ b/lib/modules/datasource/terraform-module/index.ts
@@ -10,6 +10,7 @@ import type {
   TerraformModuleVersions,
   TerraformRelease,
 } from './types';
+import { createSDBackendURL } from './utils';
 
 export class TerraformModuleDatasource extends TerraformDatasource {
   static override readonly id = 'terraform-module';
@@ -81,8 +82,13 @@ export class TerraformModuleDatasource extends TerraformDatasource {
 
     try {
       // TODO: types (#7154)
-      // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-      pkgUrl = `${registryUrl}${serviceDiscovery['modules.v1']}${repository}`;
+
+      pkgUrl = createSDBackendURL(
+        registryUrl,
+        'modules.v1',
+        serviceDiscovery,
+        repository
+      );
       res = (await this.http.getJson<TerraformRelease>(pkgUrl)).body;
       const returnedName = res.namespace + '/' + res.name + '/' + res.provider;
       if (returnedName !== repository) {
@@ -126,8 +132,12 @@ export class TerraformModuleDatasource extends TerraformDatasource {
     let pkgUrl: string;
     try {
       // TODO: types (#7154)
-      // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
-      pkgUrl = `${registryUrl}${serviceDiscovery['modules.v1']}${repository}/versions`;
+      pkgUrl = createSDBackendURL(
+        registryUrl,
+        'modules.v1',
+        serviceDiscovery,
+        `${repository}/versions`
+      );
       res = (await this.http.getJson<TerraformModuleVersions>(pkgUrl)).body;
       if (res.modules.length < 1) {
         logger.warn({ pkgUrl }, 'Terraform registry result mismatch');
diff --git a/lib/modules/datasource/terraform-module/types.ts b/lib/modules/datasource/terraform-module/types.ts
index 7e32a0b8f6c617eff1e6974afe069d965ac831fb..c394ee55a463bdbe293e05cb8c34d5672693ce3f 100644
--- a/lib/modules/datasource/terraform-module/types.ts
+++ b/lib/modules/datasource/terraform-module/types.ts
@@ -32,3 +32,5 @@ export interface ServiceDiscoveryResult {
   'modules.v1'?: string;
   'providers.v1'?: string;
 }
+
+export type ServiceDiscoveryEndpointType = 'modules.v1' | 'providers.v1';
diff --git a/lib/modules/datasource/terraform-module/utils.spec.ts b/lib/modules/datasource/terraform-module/utils.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5477b6247335c6d5761dda7bcc0e89e029a3689c
--- /dev/null
+++ b/lib/modules/datasource/terraform-module/utils.spec.ts
@@ -0,0 +1,85 @@
+import { createSDBackendURL } from './utils';
+
+describe('modules/datasource/terraform-module/utils', () => {
+  describe('createSDBackendURL', () => {
+    const defaultRegistryURL = 'https://registry.example.com';
+
+    it('returns URL with relative SD for modules', () => {
+      const result = createSDBackendURL(
+        defaultRegistryURL,
+        'modules.v1',
+        {
+          'modules.v1': '/v1/modules/',
+        },
+        'hashicorp/consul/aws'
+      );
+      expect(result).toBe(
+        'https://registry.example.com/v1/modules/hashicorp/consul/aws'
+      );
+    });
+
+    it('returns URL with relative SD for providers', () => {
+      const result = createSDBackendURL(
+        defaultRegistryURL,
+        'providers.v1',
+        {
+          'providers.v1': '/v1/providers/',
+        },
+        'hashicorp/azure'
+      );
+      expect(result).toBe(
+        'https://registry.example.com/v1/providers/hashicorp/azure'
+      );
+    });
+
+    it('returns URL with absolute SD  for modules', () => {
+      const result = createSDBackendURL(
+        defaultRegistryURL,
+        'modules.v1',
+        {
+          'modules.v1': 'https://other.example.com/v1/modules/',
+        },
+        'hashicorp/consul/aws'
+      );
+      expect(result).toBe(
+        'https://other.example.com/v1/modules/hashicorp/consul/aws'
+      );
+    });
+
+    it('returns URL with absolute SD for providers and missing trailing slash', () => {
+      const result = createSDBackendURL(
+        defaultRegistryURL,
+        'providers.v1',
+        {
+          'providers.v1': 'https://other.example.com/providers',
+        },
+        'hashicorp/azure'
+      );
+      expect(result).toBe(
+        'https://other.example.com/providers/hashicorp/azure'
+      );
+    });
+
+    it('returns URL with with empty SD', () => {
+      const result = createSDBackendURL(
+        defaultRegistryURL,
+        'providers.v1',
+        {
+          'providers.v1': '',
+        },
+        'hashicorp/azure'
+      );
+      expect(result).toBe('https://registry.example.com/hashicorp/azure');
+    });
+
+    it('returns URL with with missing SD', () => {
+      const result = createSDBackendURL(
+        defaultRegistryURL,
+        'providers.v1',
+        {},
+        'hashicorp/azure'
+      );
+      expect(result).toBe('https://registry.example.com/hashicorp/azure');
+    });
+  });
+});
diff --git a/lib/modules/datasource/terraform-module/utils.ts b/lib/modules/datasource/terraform-module/utils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..02195cd8253c37b18ef9d84d5807ee3616112321
--- /dev/null
+++ b/lib/modules/datasource/terraform-module/utils.ts
@@ -0,0 +1,19 @@
+import { joinUrlParts, validateUrl } from '../../../util/url';
+import type {
+  ServiceDiscoveryEndpointType,
+  ServiceDiscoveryResult,
+} from './types';
+
+export function createSDBackendURL(
+  registryURL: string,
+  sdType: ServiceDiscoveryEndpointType,
+  sdResult: ServiceDiscoveryResult,
+  subPath: string
+): string {
+  const sdEndpoint = sdResult[sdType] ?? '';
+  const fullPath = joinUrlParts(sdEndpoint, subPath);
+  if (validateUrl(fullPath)) {
+    return fullPath;
+  }
+  return joinUrlParts(registryURL, fullPath);
+}
diff --git a/lib/modules/datasource/terraform-provider/index.ts b/lib/modules/datasource/terraform-provider/index.ts
index c968a5eecf7e20288efcee0457cfd4c25bf40b06..018549f4a17721ce1f732c2dc713ffcacb74f7af 100644
--- a/lib/modules/datasource/terraform-provider/index.ts
+++ b/lib/modules/datasource/terraform-provider/index.ts
@@ -9,6 +9,7 @@ import { regEx } from '../../../util/regex';
 import * as hashicorpVersioning from '../../versioning/hashicorp';
 import { TerraformDatasource } from '../terraform-module/base';
 import type { ServiceDiscoveryResult } from '../terraform-module/types';
+import { createSDBackendURL } from '../terraform-module/utils';
 import type { GetReleasesConfig, ReleaseResult } from '../types';
 import type {
   TerraformBuild,
@@ -97,7 +98,12 @@ export class TerraformProviderDatasource extends TerraformDatasource {
     registryUrl: string,
     repository: string
   ): Promise<ReleaseResult> {
-    const backendURL = `${registryUrl}${serviceDiscovery['providers.v1']}${repository}`;
+    const backendURL = createSDBackendURL(
+      registryUrl,
+      'providers.v1',
+      serviceDiscovery,
+      repository
+    );
     const res = (await this.http.getJson<TerraformProvider>(backendURL)).body;
     const dep: ReleaseResult = {
       releases: res.versions.map((version) => ({
@@ -128,7 +134,12 @@ export class TerraformProviderDatasource extends TerraformDatasource {
     registryUrl: string,
     repository: string
   ): Promise<ReleaseResult> {
-    const backendURL = `${registryUrl}${serviceDiscovery['providers.v1']}${repository}/versions`;
+    const backendURL = createSDBackendURL(
+      registryUrl,
+      'providers.v1',
+      serviceDiscovery,
+      `${repository}/versions`
+    );
     const res = (await this.http.getJson<TerraformProviderVersions>(backendURL))
       .body;
     const dep: ReleaseResult = {
@@ -211,7 +222,12 @@ export class TerraformProviderDatasource extends TerraformDatasource {
       logger.trace(`Failed to retrieve service discovery from ${registryURL}`);
       return null;
     }
-    const backendURL = `${registryURL}${serviceDiscovery['providers.v1']}${repository}`;
+    const backendURL = createSDBackendURL(
+      registryURL,
+      'providers.v1',
+      serviceDiscovery,
+      repository
+    );
     const versionsResponse = (
       await this.http.getJson<TerraformRegistryVersions>(
         `${backendURL}/versions`