diff --git a/docs/usage/nuget.md b/docs/usage/nuget.md
index 5c4dc6f2c41cf9aa275d7cfab928ceca662cf658..e40b8db321bd416c47f11384c8b4b9f11cd5df5c 100644
--- a/docs/usage/nuget.md
+++ b/docs/usage/nuget.md
@@ -40,6 +40,18 @@ Renovate by default performs all lookups on `https://api.nuget.org/v3/index.json
 
 If this example we defined 3 nuget feeds. Packages resolving will process feeds consequentially. It means that if package will be resolved in second feed renovate won't look in last one.
 
+### Protocol versions
+
+NuGet supports two protocol versions, `v2` and `v3`, which NuGet client and server need to agree on. Renovate as a NuGet client supports both versions and will use `v2` unless the configured feed URL ends with `index.json` (which mirrors the behavior of the official NuGet client). If you have `v3` feed that does not match this pattern (e.g. JFrog Artifactory) you need to help Renovate by appending `#protocolVersion=3` to the registry URL:
+
+```json
+"nuget": {
+  "registryUrls": [
+    "http://myV3feed#protocolVersion=3"
+  ]
+}
+```
+
 ## Authenticated feeds
 
 Credentials for authenticated/private feeds can be provided via host rules in the configuration options (file or command line parameter).
diff --git a/lib/datasource/nuget/index.spec.ts b/lib/datasource/nuget/index.spec.ts
index e14f02dedec8acd2bd6d08b81c1283fc1199d6dc..0c30b13a372381a965847cccc345c24295d4c827 100644
--- a/lib/datasource/nuget/index.spec.ts
+++ b/lib/datasource/nuget/index.spec.ts
@@ -107,6 +107,17 @@ describe('datasource/nuget', () => {
       ).toBeNull();
     });
 
+    it('extracts feed version from registry URL hash', async () => {
+      const config = {
+        lookupName: 'nunit',
+        registryUrls: ['https://my-registry#protocolVersion=3'],
+      };
+      await nuget.getPkgReleases({
+        ...config,
+      });
+      expect(got.mock.calls[0][0]).toEqual('https://my-registry/');
+    });
+
     it('queries the default nuget feed if no registries are supplied', async () => {
       await nuget.getPkgReleases({
         ...configNoRegistryUrls,
diff --git a/lib/datasource/nuget/index.ts b/lib/datasource/nuget/index.ts
index e35d8b2299018d61bf6b741e58b5395a9e717da3..092cac6bb6ef22265770af6d71a1efa2be336381 100644
--- a/lib/datasource/nuget/index.ts
+++ b/lib/datasource/nuget/index.ts
@@ -6,17 +6,24 @@ import { GetReleasesConfig, ReleaseResult } from '../common';
 
 export { id } from './common';
 
-function detectFeedVersion(url: string): 2 | 3 | null {
+function parseRegistryUrl(
+  registryUrl: string
+): { feedUrl: string; protocolVersion: number } {
   try {
-    const parsecUrl = urlApi.parse(url);
-    // Official client does it in the same way
-    if (parsecUrl.pathname.endsWith('.json')) {
-      return 3;
+    const parsedUrl = urlApi.parse(registryUrl);
+    let protocolVersion = 2;
+    const protolVersionRegExp = /#protocolVersion=(2|3)/;
+    const protocolVersionMatch = protolVersionRegExp.exec(parsedUrl.hash);
+    if (protocolVersionMatch) {
+      parsedUrl.hash = '';
+      protocolVersion = Number.parseInt(protocolVersionMatch[1], 10);
+    } else if (parsedUrl.pathname.endsWith('.json')) {
+      protocolVersion = 3;
     }
-    return 2;
+    return { feedUrl: urlApi.format(parsedUrl), protocolVersion };
   } catch (e) {
-    logger.debug({ e }, `nuget registry failure: can't parse ${url}`);
-    return null;
+    logger.debug({ e }, `nuget registry failure: can't parse ${registryUrl}`);
+    return { feedUrl: registryUrl, protocolVersion: null };
   }
 }
 
@@ -27,13 +34,13 @@ export async function getPkgReleases({
   logger.trace(`nuget.getPkgReleases(${lookupName})`);
   let dep: ReleaseResult = null;
   for (const feed of registryUrls || [v3.getDefaultFeed()]) {
-    const feedVersion = detectFeedVersion(feed);
-    if (feedVersion === 2) {
-      dep = await v2.getPkgReleases(feed, lookupName);
-    } else if (feedVersion === 3) {
-      const queryUrl = await v3.getQueryUrl(feed);
+    const { feedUrl, protocolVersion } = parseRegistryUrl(feed);
+    if (protocolVersion === 2) {
+      dep = await v2.getPkgReleases(feedUrl, lookupName);
+    } else if (protocolVersion === 3) {
+      const queryUrl = await v3.getQueryUrl(feedUrl);
       if (queryUrl !== null) {
-        dep = await v3.getPkgReleases(feed, queryUrl, lookupName);
+        dep = await v3.getPkgReleases(feedUrl, queryUrl, lookupName);
       }
     }
     if (dep != null) {