diff --git a/lib/modules/manager/npm/extract/__snapshots__/index.spec.ts.snap b/lib/modules/manager/npm/extract/__snapshots__/index.spec.ts.snap
index f9808f6b9def2168a7214bb5aa20d064abe92102..165138b1c3e47dd6fdeb44884af7a7cdd71b4aa1 100644
--- a/lib/modules/manager/npm/extract/__snapshots__/index.spec.ts.snap
+++ b/lib/modules/manager/npm/extract/__snapshots__/index.spec.ts.snap
@@ -59,7 +59,6 @@ exports[`modules/manager/npm/extract/index .extractPackageFile() extracts engine
       "depName": "foo",
       "depType": "devDependencies",
       "prettyDepType": "devDependency",
-      "skipReason": "any-version",
     },
     {
       "currentValue": "file:../foo/bar",
diff --git a/lib/modules/manager/npm/extract/index.spec.ts b/lib/modules/manager/npm/extract/index.spec.ts
index 98ff5aac49b88dd1fd8f21fa957a453b45a987dc..bf5095ee46b317546f69a6a7932313a5fb971b78 100644
--- a/lib/modules/manager/npm/extract/index.spec.ts
+++ b/lib/modules/manager/npm/extract/index.spec.ts
@@ -353,7 +353,7 @@ describe('modules/manager/npm/extract/index', () => {
         deps: [
           { depName: 'angular', currentValue: '1.6.0' },
           { depName: '@angular/cli', currentValue: '1.6.0' },
-          { depName: 'foo', currentValue: '*', skipReason: 'any-version' },
+          { depName: 'foo', currentValue: '*' },
           {
             depName: 'bar',
             currentValue: 'file:../foo/bar',
diff --git a/lib/modules/manager/npm/extract/index.ts b/lib/modules/manager/npm/extract/index.ts
index ac2947c6edcf1144fdb13ad416e5ce596f17c766..d395a4940d5237163a9beea4e0bf5d05bebab252 100644
--- a/lib/modules/manager/npm/extract/index.ts
+++ b/lib/modules/manager/npm/extract/index.ts
@@ -271,9 +271,6 @@ export async function extractPackageFile(
     }
     if (isValid(dep.currentValue)) {
       dep.datasource = NpmDatasource.id;
-      if (dep.currentValue === '*') {
-        dep.skipReason = 'any-version';
-      }
       if (dep.currentValue === '') {
         dep.skipReason = 'empty';
       }
diff --git a/lib/modules/versioning/npm/index.spec.ts b/lib/modules/versioning/npm/index.spec.ts
index c56323ab65332ea254c0636e8527ba5733539c29..7b70d5eee3f43d5ec6c8d6cdfc7a825199178dde 100644
--- a/lib/modules/versioning/npm/index.spec.ts
+++ b/lib/modules/versioning/npm/index.spec.ts
@@ -5,19 +5,44 @@ describe('modules/versioning/npm/index', () => {
     version                                          | isValid
     ${'17.04.0'}                                     | ${false}
     ${'1.2.3'}                                       | ${true}
+    ${'*'}                                           | ${true}
+    ${'x'}                                           | ${true}
+    ${'X'}                                           | ${true}
+    ${'1'}                                           | ${true}
     ${'1.2.3-foo'}                                   | ${true}
     ${'1.2.3foo'}                                    | ${false}
     ${'~1.2.3'}                                      | ${true}
+    ${'1.2'}                                         | ${true}
+    ${'1.2.x'}                                       | ${true}
+    ${'1.2.X'}                                       | ${true}
+    ${'1.2.*'}                                       | ${true}
+    ${'~1.2.3'}                                      | ${true}
     ${'^1.2.3'}                                      | ${true}
     ${'>1.2.3'}                                      | ${true}
     ${'renovatebot/renovate'}                        | ${false}
     ${'renovatebot/renovate#main'}                   | ${false}
     ${'https://github.com/renovatebot/renovate.git'} | ${false}
   `('isValid("$version") === $isValid', ({ version, isValid }) => {
-    const res = !!semver.isValid(version);
+    const res = semver.isValid(version);
     expect(res).toBe(isValid);
   });
 
+  test.each`
+    versions                                          | range      | maxSatisfying
+    ${['2.3.3.', '2.3.4', '2.4.5', '2.5.1', '3.0.0']} | ${'*'}     | ${'3.0.0'}
+    ${['2.3.3.', '2.3.4', '2.4.5', '2.5.1', '3.0.0']} | ${'x'}     | ${'3.0.0'}
+    ${['2.3.3.', '2.3.4', '2.4.5', '2.5.1', '3.0.0']} | ${'X'}     | ${'3.0.0'}
+    ${['2.3.3.', '2.3.4', '2.4.5', '2.5.1', '3.0.0']} | ${'2'}     | ${'2.5.1'}
+    ${['2.3.3.', '2.3.4', '2.4.5', '2.5.1', '3.0.0']} | ${'2.*'}   | ${'2.5.1'}
+    ${['2.3.3.', '2.3.4', '2.4.5', '2.5.1', '3.0.0']} | ${'2.3'}   | ${'2.3.4'}
+    ${['2.3.3.', '2.3.4', '2.4.5', '2.5.1', '3.0.0']} | ${'2.3.*'} | ${'2.3.4'}
+  `(
+    'getSatisfyingVersion("$versions","$range") === $maxSatisfying',
+    ({ versions, range, maxSatisfying }) => {
+      expect(semver.getSatisfyingVersion(versions, range)).toBe(maxSatisfying);
+    }
+  );
+
   test.each`
     version            | isSingle
     ${'1.2.3'}         | ${true}
@@ -59,6 +84,7 @@ describe('modules/versioning/npm/index', () => {
     ${'>= 0.0.1 < 0.0.4'}   | ${'bump'}     | ${'0.0.4'}     | ${'0.0.5'}              | ${'>= 0.0.5 < 0.0.6'}
     ${'>= 0.0.1 < 1'}       | ${'bump'}     | ${'1.0.0'}     | ${'1.0.1'}              | ${'>= 1.0.1 < 2'}
     ${'>= 0.0.1 < 1'}       | ${'bump'}     | ${'1.0.0'}     | ${'1.0.1'}              | ${'>= 1.0.1 < 2'}
+    ${'*'}                  | ${'bump'}     | ${'1.0.0'}     | ${'1.0.1'}              | ${null}
     ${'<=1.2.3'}            | ${'widen'}    | ${'1.0.0'}     | ${'1.2.3'}              | ${'<=1.2.3'}
     ${'<=1.2.3'}            | ${'widen'}    | ${'1.0.0'}     | ${'1.2.4'}              | ${'<=1.2.4'}
     ${'>=1.2.3'}            | ${'widen'}    | ${'1.0.0'}     | ${'1.2.3'}              | ${'>=1.2.3'}
diff --git a/lib/modules/versioning/npm/range.ts b/lib/modules/versioning/npm/range.ts
index 9e2458c8e448b1663325355f1ad5a72a0dd37fa5..8ddfad58d35fbd427d550e66c831d0ada9df1482 100644
--- a/lib/modules/versioning/npm/range.ts
+++ b/lib/modules/versioning/npm/range.ts
@@ -3,6 +3,7 @@ import semver from 'semver';
 import semverUtils from 'semver-utils';
 import { logger } from '../../../logger';
 import { regEx } from '../../../util/regex';
+import { isSemVerXRange } from '../semver/common';
 import type { NewValueConfig } from '../types';
 
 const {
@@ -63,6 +64,9 @@ export function getNewValue({
   currentVersion,
   newVersion,
 }: NewValueConfig): string | null {
+  if (rangeStrategy === 'bump' && isSemVerXRange(currentValue)) {
+    return null;
+  }
   if (rangeStrategy === 'pin' || isVersion(currentValue)) {
     return newVersion;
   }
diff --git a/lib/modules/versioning/semver/common.spec.ts b/lib/modules/versioning/semver/common.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..372ede4bdd9ae7650439a37e6ab4db04ecdfdb4f
--- /dev/null
+++ b/lib/modules/versioning/semver/common.spec.ts
@@ -0,0 +1,16 @@
+import { isSemVerXRange } from './common';
+
+describe('modules/versioning/semver/common', () => {
+  test.each`
+    range      | expected
+    ${'*'}     | ${true}
+    ${'x'}     | ${true}
+    ${'X'}     | ${true}
+    ${''}      | ${true}
+    ${'1'}     | ${false}
+    ${'1.2'}   | ${false}
+    ${'1.2.3'} | ${false}
+  `('isSemVerXRange("range") === $expected', ({ range, expected }) => {
+    expect(isSemVerXRange(range)).toBe(expected);
+  });
+});
diff --git a/lib/modules/versioning/semver/common.ts b/lib/modules/versioning/semver/common.ts
new file mode 100644
index 0000000000000000000000000000000000000000..58f1230a01933eb68e4e9ba09c782459f3006ba2
--- /dev/null
+++ b/lib/modules/versioning/semver/common.ts
@@ -0,0 +1,10 @@
+const SEMVER_X_RANGE = ['*', 'x', 'X', ''] as const;
+type SemVerXRangeArray = typeof SEMVER_X_RANGE;
+export type SemVerXRange = SemVerXRangeArray[number];
+
+/**
+ * https://docs.npmjs.com/cli/v6/using-npm/semver#x-ranges-12x-1x-12-
+ */
+export function isSemVerXRange(range: string): range is SemVerXRange {
+  return SEMVER_X_RANGE.includes(range as SemVerXRange);
+}
diff --git a/lib/workers/repository/process/lookup/index.spec.ts b/lib/workers/repository/process/lookup/index.spec.ts
index f89a35406d75c74719c4849b5be4a39755a20868..5a0dd27cab6068e45ef78ce91b4f0fd4e1d4a4ef 100644
--- a/lib/workers/repository/process/lookup/index.spec.ts
+++ b/lib/workers/repository/process/lookup/index.spec.ts
@@ -334,6 +334,88 @@ describe('workers/repository/process/lookup/index', () => {
       ]);
     });
 
+    it.each`
+      strategy             | updates
+      ${'update-lockfile'} | ${[{ isLockfileUpdate: true, newValue: '*', newVersion: '0.9.7', updateType: 'minor' }, { isLockfileUpdate: true, newValue: '*', newVersion: '1.4.1', updateType: 'major' }]}
+      ${'pin'}             | ${[{ newValue: '0.4.0', updateType: 'pin' }, { newValue: '0.9.7', updateType: 'minor' }, { newValue: '1.4.1', updateType: 'major' }]}
+    `(
+      'supports for x-range-all for replaceStrategy = $strategy (with lockfile)',
+      async ({ strategy, updates }) => {
+        config.currentValue = '*';
+        config.rangeStrategy = strategy;
+        config.lockedVersion = '0.4.0';
+        config.depName = 'q';
+        config.datasource = NpmDatasource.id;
+        httpMock
+          .scope('https://registry.npmjs.org')
+          .get('/q')
+          .reply(200, qJson);
+        expect(await lookup.lookupUpdates(config)).toMatchObject({ updates });
+      }
+    );
+
+    it.each`
+      strategy
+      ${'widen'}
+      ${'bump'}
+      ${'replace'}
+    `(
+      'doesnt offer updates for x-range-all (with lockfile) when replaceStrategy = $strategy',
+      async ({ strategy }) => {
+        config.currentValue = 'x';
+        config.rangeStrategy = strategy;
+        config.lockedVersion = '0.4.0';
+        config.depName = 'q';
+        config.datasource = NpmDatasource.id;
+        httpMock
+          .scope('https://registry.npmjs.org')
+          .get('/q')
+          .reply(200, qJson);
+        expect((await lookup.lookupUpdates(config)).updates).toEqual([]);
+      }
+    );
+
+    it('supports pinning for x-range-all (no lockfile)', async () => {
+      config.currentValue = '*';
+      config.rangeStrategy = 'pin';
+      config.depName = 'q';
+      config.datasource = NpmDatasource.id;
+      httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson);
+      expect(await lookup.lookupUpdates(config)).toMatchObject({
+        updates: [{ newValue: '1.4.1', updateType: 'pin' }],
+      });
+    });
+
+    it('covers pinning an unsupported x-range-all value', async () => {
+      config.currentValue = '';
+      config.rangeStrategy = 'pin';
+      config.depName = 'q';
+      config.datasource = NpmDatasource.id;
+      httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson);
+      expect((await lookup.lookupUpdates(config)).updates).toEqual([]);
+    });
+
+    it.each`
+      strategy
+      ${'widen'}
+      ${'bump'}
+      ${'update-lockfile'}
+      ${'replace'}
+    `(
+      'doesnt offer updates for x-range-all (no lockfile) when replaceStrategy = $strategy',
+      async ({ strategy }) => {
+        config.currentValue = 'X';
+        config.rangeStrategy = strategy;
+        config.depName = 'q';
+        config.datasource = NpmDatasource.id;
+        httpMock
+          .scope('https://registry.npmjs.org')
+          .get('/q')
+          .reply(200, qJson);
+        expect((await lookup.lookupUpdates(config)).updates).toEqual([]);
+      }
+    );
+
     it('ignores pinning for ranges when other upgrade exists', async () => {
       config.currentValue = '~0.9.0';
       config.rangeStrategy = 'pin';