diff --git a/config/custom-environment-variables.yml b/config/custom-environment-variables.yml
index 56919b467f1a48eebd55374ccf60fb618a90cd8b..4e47cb9d24ccd38305992f1d598432f3f889a383 100644
--- a/config/custom-environment-variables.yml
+++ b/config/custom-environment-variables.yml
@@ -52,6 +52,8 @@ public:
       authorizedOrigins: 'NPM_ORIGINS'
     obs:
       authorizedOrigins: 'OBS_ORIGINS'
+    pypi:
+      baseUri: 'PYPI_URL'
     sonar:
       authorizedOrigins: 'SONAR_ORIGINS'
     teamcity:
diff --git a/config/default.yml b/config/default.yml
index 5a6fb103934867d7e1294ceee7112c0485172c5c..cea2edad7b60dc9a3bbabb2cb820e962de2b3805 100644
--- a/config/default.yml
+++ b/config/default.yml
@@ -22,6 +22,8 @@ public:
       restApiVersion: '2022-11-28'
     obs:
       authorizedOrigins: 'https://api.opensuse.org'
+    pypi:
+      baseUri: 'https://pypi.org'
     weblate:
       authorizedOrigins: 'https://hosted.weblate.org'
     trace: false
diff --git a/core/server/server.js b/core/server/server.js
index d6c6256a1165edb7fd64801d27be4baf807ffdb6..65c8a52f7502597d0aa07e29a26ec69314735914 100644
--- a/core/server/server.js
+++ b/core/server/server.js
@@ -139,6 +139,9 @@ const publicConfigSchema = Joi.object({
     nexus: defaultService,
     npm: defaultService,
     obs: defaultService,
+    pypi: {
+      baseUri: requiredUrl,
+    },
     sonar: defaultService,
     teamcity: defaultService,
     weblate: defaultService,
diff --git a/doc/server-secrets.md b/doc/server-secrets.md
index f7406b6d8bb8907bb0c429a4665ef5f1227771bd..1518ec0a609f91d8f1e94efa2c0c596e742b9cfb 100644
--- a/doc/server-secrets.md
+++ b/doc/server-secrets.md
@@ -283,6 +283,13 @@ The Pepy API requires authentication. To obtain a key,
 Create an account, sign in and obtain generate a key on your
 [account page](https://www.pepy.tech/user).
 
+### PyPI
+
+- `PYPI_URL` (yml: `public.pypi.baseUri`)
+
+`PYPI_URL` can be used to optionally send all the PyPI requests to a Self-hosted Pypi registry,
+users can also override this by query parameter `pypiBaseUrl`.
+
 ### SymfonyInsight (formerly Sensiolabs)
 
 - `SL_INSIGHT_USER_UUID` (yml: `private.sl_insight_userUuid`)
diff --git a/services/pypi/pypi-base.js b/services/pypi/pypi-base.js
index df4a38eb0f4f92981478c960ca318a95141db0c0..344d249d4e7cd0fb7a3dd764d6e3a0973ff36bd2 100644
--- a/services/pypi/pypi-base.js
+++ b/services/pypi/pypi-base.js
@@ -1,5 +1,7 @@
 import Joi from 'joi'
-import { BaseJsonService } from '../index.js'
+import config from 'config'
+import { optionalUrl } from '../validators.js'
+import { BaseJsonService, queryParam, pathParam } from '../index.js'
 
 const schema = Joi.object({
   info: Joi.object({
@@ -18,18 +20,38 @@ const schema = Joi.object({
     .required(),
 }).required()
 
+export const queryParamSchema = Joi.object({
+  pypiBaseUrl: optionalUrl,
+}).required()
+
+export const pypiPackageParam = pathParam({
+  name: 'packageName',
+  example: 'Django',
+})
+
+export const pypiBaseUrlParam = queryParam({
+  name: 'pypiBaseUrl',
+  example: 'https://pypi.org',
+})
+
+export const pypiGeneralParams = [pypiPackageParam, pypiBaseUrlParam]
+
 export default class PypiBase extends BaseJsonService {
   static buildRoute(base) {
     return {
       base,
       pattern: ':egg+',
+      queryParamSchema,
     }
   }
 
-  async fetch({ egg }) {
+  async fetch({ egg, pypiBaseUrl = null }) {
+    const defaultpypiBaseUrl =
+      config.util.toObject().public.services.pypi.baseUri
+    pypiBaseUrl = pypiBaseUrl || defaultpypiBaseUrl
     return this._requestJson({
       schema,
-      url: `https://pypi.org/pypi/${egg}/json`,
+      url: `${pypiBaseUrl}/pypi/${egg}/json`,
       httpErrors: { 404: 'package or version not found' },
     })
   }
diff --git a/services/pypi/pypi-downloads.service.js b/services/pypi/pypi-downloads.service.js
index 45106f0e6502a01ec71cf70a48026405af8f09b0..14c988b0e3da7b9fb1ea829c7b5e7285221ed7cb 100644
--- a/services/pypi/pypi-downloads.service.js
+++ b/services/pypi/pypi-downloads.service.js
@@ -1,7 +1,8 @@
 import Joi from 'joi'
 import { nonNegativeInteger } from '../validators.js'
-import { BaseJsonService, pathParams } from '../index.js'
+import { BaseJsonService, pathParam } from '../index.js'
 import { renderDownloadsBadge } from '../downloads.js'
+import { pypiPackageParam } from './pypi-base.js'
 
 const schema = Joi.object({
   data: Joi.object({
@@ -42,15 +43,15 @@ export default class PypiDownloads extends BaseJsonService {
         summary: 'PyPI - Downloads',
         description:
           'Python package downloads from [pypistats](https://pypistats.org/)',
-        parameters: pathParams(
-          {
+        parameters: [
+          pathParam({
             name: 'period',
             example: 'dd',
             schema: { type: 'string', enum: this.getEnum('period') },
             description: 'Daily, Weekly, or Monthly downloads',
-          },
-          { name: 'packageName', example: 'Django' },
-        ),
+          }),
+          pypiPackageParam,
+        ],
       },
     },
   }
diff --git a/services/pypi/pypi-format.service.js b/services/pypi/pypi-format.service.js
index e1d02bc9fe21ab99b5f5024f7a1ac868be624625..4c995620192f44fdb1574f411eae518cc9a39f74 100644
--- a/services/pypi/pypi-format.service.js
+++ b/services/pypi/pypi-format.service.js
@@ -1,5 +1,4 @@
-import { pathParams } from '../index.js'
-import PypiBase from './pypi-base.js'
+import PypiBase, { pypiGeneralParams } from './pypi-base.js'
 import { getPackageFormats } from './pypi-helpers.js'
 
 export default class PypiFormat extends PypiBase {
@@ -11,10 +10,7 @@ export default class PypiFormat extends PypiBase {
     '/pypi/format/{packageName}': {
       get: {
         summary: 'PyPI - Format',
-        parameters: pathParams({
-          name: 'packageName',
-          example: 'Django',
-        }),
+        parameters: pypiGeneralParams,
       },
     },
   }
@@ -40,8 +36,8 @@ export default class PypiFormat extends PypiBase {
     }
   }
 
-  async handle({ egg }) {
-    const packageData = await this.fetch({ egg })
+  async handle({ egg }, { pypiBaseUrl }) {
+    const packageData = await this.fetch({ egg, pypiBaseUrl })
     const { hasWheel, hasEgg } = getPackageFormats(packageData)
     return this.constructor.render({ hasWheel, hasEgg })
   }
diff --git a/services/pypi/pypi-framework-versions.service.js b/services/pypi/pypi-framework-versions.service.js
index 6c42f8969b6f322b0ee785587e917189dbd3700c..5777f8b35ad937b8751b30f985410c077d323d46 100644
--- a/services/pypi/pypi-framework-versions.service.js
+++ b/services/pypi/pypi-framework-versions.service.js
@@ -1,5 +1,5 @@
 import { InvalidResponse, pathParams } from '../index.js'
-import PypiBase from './pypi-base.js'
+import PypiBase, { pypiBaseUrlParam } from './pypi-base.js'
 import { sortPypiVersions, parseClassifiers } from './pypi-helpers.js'
 
 const frameworkNameMap = {
@@ -63,7 +63,7 @@ export default class PypiFrameworkVersion extends PypiBase {
             schema: { type: 'string', enum: Object.keys(frameworkNameMap) },
           },
           { name: 'packageName', example: 'plone.volto' },
-        ),
+        ).concat(pypiBaseUrlParam),
       },
     },
   }
@@ -80,7 +80,7 @@ export default class PypiFrameworkVersion extends PypiBase {
     }
   }
 
-  async handle({ frameworkName, packageName }) {
+  async handle({ frameworkName, packageName }, { pypiBaseUrl }) {
     const classifier = frameworkNameMap[frameworkName]
       ? frameworkNameMap[frameworkName].classifier
       : frameworkName
@@ -88,7 +88,7 @@ export default class PypiFrameworkVersion extends PypiBase {
       ? frameworkNameMap[frameworkName].name
       : frameworkName
     const regex = new RegExp(`^Framework :: ${classifier} :: ([\\d.]+)$`)
-    const packageData = await this.fetch({ egg: packageName })
+    const packageData = await this.fetch({ egg: packageName, pypiBaseUrl })
     const versions = parseClassifiers(packageData, regex)
 
     if (versions.length === 0) {
diff --git a/services/pypi/pypi-implementation.service.js b/services/pypi/pypi-implementation.service.js
index 8da1079c15223451cb97e583ed0968edee6b60ae..8a3c6a7a0cf9ac8669b96a9fa33f56c553bd6254 100644
--- a/services/pypi/pypi-implementation.service.js
+++ b/services/pypi/pypi-implementation.service.js
@@ -1,5 +1,4 @@
-import { pathParams } from '../index.js'
-import PypiBase from './pypi-base.js'
+import PypiBase, { pypiGeneralParams } from './pypi-base.js'
 import { parseClassifiers } from './pypi-helpers.js'
 
 export default class PypiImplementation extends PypiBase {
@@ -11,10 +10,7 @@ export default class PypiImplementation extends PypiBase {
     '/pypi/implementation/{packageName}': {
       get: {
         summary: 'PyPI - Implementation',
-        parameters: pathParams({
-          name: 'packageName',
-          example: 'Django',
-        }),
+        parameters: pypiGeneralParams,
       },
     },
   }
@@ -28,8 +24,8 @@ export default class PypiImplementation extends PypiBase {
     }
   }
 
-  async handle({ egg }) {
-    const packageData = await this.fetch({ egg })
+  async handle({ egg }, { pypiBaseUrl }) {
+    const packageData = await this.fetch({ egg, pypiBaseUrl })
 
     let implementations = parseClassifiers(
       packageData,
diff --git a/services/pypi/pypi-license.service.js b/services/pypi/pypi-license.service.js
index 4bb89dc498cfb1019a501105507c4894f98309ab..11fe595feb704b5a50475dace9ab156cfb646aa2 100644
--- a/services/pypi/pypi-license.service.js
+++ b/services/pypi/pypi-license.service.js
@@ -1,6 +1,5 @@
-import { pathParams } from '../index.js'
 import { renderLicenseBadge } from '../licenses.js'
-import PypiBase from './pypi-base.js'
+import PypiBase, { pypiGeneralParams } from './pypi-base.js'
 import { getLicenses } from './pypi-helpers.js'
 
 export default class PypiLicense extends PypiBase {
@@ -12,10 +11,7 @@ export default class PypiLicense extends PypiBase {
     '/pypi/l/{packageName}': {
       get: {
         summary: 'PyPI - License',
-        parameters: pathParams({
-          name: 'packageName',
-          example: 'Django',
-        }),
+        parameters: pypiGeneralParams,
       },
     },
   }
@@ -24,8 +20,8 @@ export default class PypiLicense extends PypiBase {
     return renderLicenseBadge({ licenses })
   }
 
-  async handle({ egg }) {
-    const packageData = await this.fetch({ egg })
+  async handle({ egg }, { pypiBaseUrl }) {
+    const packageData = await this.fetch({ egg, pypiBaseUrl })
     const licenses = getLicenses(packageData)
     return this.constructor.render({ licenses })
   }
diff --git a/services/pypi/pypi-python-versions.service.js b/services/pypi/pypi-python-versions.service.js
index 49db5cb050bc7625e795d70a36a90619b8041a08..3527fee063811bdb6d5b9359038262bc24c73110 100644
--- a/services/pypi/pypi-python-versions.service.js
+++ b/services/pypi/pypi-python-versions.service.js
@@ -1,6 +1,5 @@
 import semver from 'semver'
-import { pathParams } from '../index.js'
-import PypiBase from './pypi-base.js'
+import PypiBase, { pypiGeneralParams } from './pypi-base.js'
 import { parseClassifiers } from './pypi-helpers.js'
 
 export default class PypiPythonVersions extends PypiBase {
@@ -12,10 +11,7 @@ export default class PypiPythonVersions extends PypiBase {
     '/pypi/pyversions/{packageName}': {
       get: {
         summary: 'PyPI - Python Version',
-        parameters: pathParams({
-          name: 'packageName',
-          example: 'Django',
-        }),
+        parameters: pypiGeneralParams,
       },
     },
   }
@@ -48,8 +44,8 @@ export default class PypiPythonVersions extends PypiBase {
     }
   }
 
-  async handle({ egg }) {
-    const packageData = await this.fetch({ egg })
+  async handle({ egg }, { pypiBaseUrl }) {
+    const packageData = await this.fetch({ egg, pypiBaseUrl })
 
     const versions = parseClassifiers(
       packageData,
diff --git a/services/pypi/pypi-status.service.js b/services/pypi/pypi-status.service.js
index 12d85e39cc5b391c7fb77ab8c679ac292966c5dd..769c376c57fd40dd36112bc1fae5a37c92f5fdbd 100644
--- a/services/pypi/pypi-status.service.js
+++ b/services/pypi/pypi-status.service.js
@@ -1,5 +1,4 @@
-import { pathParams } from '../index.js'
-import PypiBase from './pypi-base.js'
+import PypiBase, { pypiGeneralParams } from './pypi-base.js'
 import { parseClassifiers } from './pypi-helpers.js'
 
 export default class PypiStatus extends PypiBase {
@@ -11,10 +10,7 @@ export default class PypiStatus extends PypiBase {
     '/pypi/status/{packageName}': {
       get: {
         summary: 'PyPI - Status',
-        parameters: pathParams({
-          name: 'packageName',
-          example: 'Django',
-        }),
+        parameters: pypiGeneralParams,
       },
     },
   }
@@ -40,8 +36,8 @@ export default class PypiStatus extends PypiBase {
     }
   }
 
-  async handle({ egg }) {
-    const packageData = await this.fetch({ egg })
+  async handle({ egg }, { pypiBaseUrl }) {
+    const packageData = await this.fetch({ egg, pypiBaseUrl })
 
     // Possible statuses:
     // - Development Status :: 1 - Planning
diff --git a/services/pypi/pypi-version.service.js b/services/pypi/pypi-version.service.js
index 04e018be975f6551a01caafeb5c2e2f5df972aa8..98fbb034fd9e003f9ad52068f39f052570ec81d3 100644
--- a/services/pypi/pypi-version.service.js
+++ b/services/pypi/pypi-version.service.js
@@ -1,7 +1,6 @@
-import { pathParams } from '../index.js'
 import { pep440VersionColor } from '../color-formatters.js'
 import { renderVersionBadge } from '../version.js'
-import PypiBase from './pypi-base.js'
+import PypiBase, { pypiGeneralParams } from './pypi-base.js'
 
 export default class PypiVersion extends PypiBase {
   static category = 'version'
@@ -12,10 +11,7 @@ export default class PypiVersion extends PypiBase {
     '/pypi/v/{packageName}': {
       get: {
         summary: 'PyPI - Version',
-        parameters: pathParams({
-          name: 'packageName',
-          example: 'nine',
-        }),
+        parameters: pypiGeneralParams,
       },
     },
   }
@@ -26,10 +22,10 @@ export default class PypiVersion extends PypiBase {
     return renderVersionBadge({ version, versionFormatter: pep440VersionColor })
   }
 
-  async handle({ egg }) {
+  async handle({ egg }, { pypiBaseUrl }) {
     const {
       info: { version },
-    } = await this.fetch({ egg })
+    } = await this.fetch({ egg, pypiBaseUrl })
     return this.constructor.render({ version })
   }
 }
diff --git a/services/pypi/pypi-wheel.service.js b/services/pypi/pypi-wheel.service.js
index ce151cb0553c1706f08e7a4025a78c02641a478a..915ead1b6d8e2b0d0fd82078e91fe648f2aac2fe 100644
--- a/services/pypi/pypi-wheel.service.js
+++ b/services/pypi/pypi-wheel.service.js
@@ -1,5 +1,4 @@
-import { pathParams } from '../index.js'
-import PypiBase from './pypi-base.js'
+import PypiBase, { pypiGeneralParams } from './pypi-base.js'
 import { getPackageFormats } from './pypi-helpers.js'
 
 export default class PypiWheel extends PypiBase {
@@ -11,10 +10,7 @@ export default class PypiWheel extends PypiBase {
     '/pypi/wheel/{packageName}': {
       get: {
         summary: 'PyPI - Wheel',
-        parameters: pathParams({
-          name: 'packageName',
-          example: 'Django',
-        }),
+        parameters: pypiGeneralParams,
       },
     },
   }
@@ -35,8 +31,8 @@ export default class PypiWheel extends PypiBase {
     }
   }
 
-  async handle({ egg }) {
-    const packageData = await this.fetch({ egg })
+  async handle({ egg }, { pypiBaseUrl }) {
+    const packageData = await this.fetch({ egg, pypiBaseUrl })
     const { hasWheel } = getPackageFormats(packageData)
     return this.constructor.render({ hasWheel })
   }