diff --git a/lib/config/presets/npm/index.ts b/lib/config/presets/npm/index.ts
index 9c9fa9b8b5ceb8170068590ef4e34d9ed4207c82..ab79ac34b84b31df1856539e6cc37745df205680 100644
--- a/lib/config/presets/npm/index.ts
+++ b/lib/config/presets/npm/index.ts
@@ -19,9 +19,8 @@ export async function getPreset({
 }: PresetConfig): Promise<Preset> {
   let dep;
   try {
-    const { headers, packageUrl } = resolvePackage(packageName);
-    const body = (await http.getJson<NpmResponse>(packageUrl, { headers }))
-      .body;
+    const { packageUrl } = resolvePackage(packageName);
+    const body = (await http.getJson<NpmResponse>(packageUrl)).body;
     dep = body.versions[body['dist-tags'].latest];
   } catch (err) {
     throw new Error(PRESET_DEP_NOT_FOUND);
diff --git a/lib/datasource/npm/__snapshots__/get.spec.ts.snap b/lib/datasource/npm/__snapshots__/get.spec.ts.snap
index 829071366665515f10971879155b2e35658e14c9..492541626254dad01d7b4f0ec1b27f8003acd77e 100644
--- a/lib/datasource/npm/__snapshots__/get.spec.ts.snap
+++ b/lib/datasource/npm/__snapshots__/get.spec.ts.snap
@@ -83,6 +83,7 @@ Array [
     "headers": Object {
       "accept": "application/json",
       "accept-encoding": "gzip, deflate, br",
+      "authorization": "Bearer XXX",
       "host": "registry.npmjs.org",
       "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)",
     },
@@ -93,6 +94,7 @@ Array [
     "headers": Object {
       "accept": "application/json",
       "accept-encoding": "gzip, deflate, br",
+      "authorization": "Bearer XXX",
       "host": "registry.npmjs.org",
       "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)",
     },
@@ -407,22 +409,6 @@ Array [
 ]
 `;
 
-exports[`datasource/npm/get no auth "@myco:registry=https://test.org
-_authToken=XXX" 1`] = `
-Array [
-  Object {
-    "headers": Object {
-      "accept": "application/json",
-      "accept-encoding": "gzip, deflate, br",
-      "host": "test.org",
-      "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)",
-    },
-    "method": "GET",
-    "url": "https://test.org/@myco%2Ftest",
-  },
-]
-`;
-
 exports[`datasource/npm/get no auth "@myco:registry=https://test.org" 1`] = `
 Array [
   Object {
diff --git a/lib/datasource/npm/__snapshots__/index.spec.ts.snap b/lib/datasource/npm/__snapshots__/index.spec.ts.snap
index 4aac8cf03d2daf7c69b4f0b4a0969b605990ce27..ae5b688b281f1e5eddc03176c78570230af668c9 100644
--- a/lib/datasource/npm/__snapshots__/index.spec.ts.snap
+++ b/lib/datasource/npm/__snapshots__/index.spec.ts.snap
@@ -6,6 +6,7 @@ Array [
     "headers": Object {
       "accept": "application/json",
       "accept-encoding": "gzip, deflate, br",
+      "authorization": "Bearer abcdefghijklmnopqrstuvwxyz",
       "host": "registry.npmjs.org",
       "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)",
     },
diff --git a/lib/datasource/npm/get.spec.ts b/lib/datasource/npm/get.spec.ts
index d4f9524b09873b585f37363a838bb9917e4f2f77..94b2e6b685af9735307506081e7c867a00eeac26 100644
--- a/lib/datasource/npm/get.spec.ts
+++ b/lib/datasource/npm/get.spec.ts
@@ -18,6 +18,7 @@ describe('datasource/npm/get', () => {
     jest.clearAllMocks();
     resetMemCache();
     hostRules.clear();
+    setNpmrc();
   });
 
   describe('has bearer auth', () => {
@@ -85,7 +86,6 @@ describe('datasource/npm/get', () => {
 
   describe('no auth', () => {
     const configs = [
-      `@myco:registry=https://test.org\n_authToken=XXX`,
       `@myco:registry=https://test.org\n//test.org/sub/:_authToken=XXX`,
       `@myco:registry=https://test.org\n//test.org/sub/:_auth=dGVzdDp0ZXN0`,
       `@myco:registry=https://test.org`,
diff --git a/lib/datasource/npm/get.ts b/lib/datasource/npm/get.ts
index 417dd3e7bf4f9f3030c62201d414a3e928ed1e7d..7356b7de3a81808de7a110bcc9e00148baee7d49 100644
--- a/lib/datasource/npm/get.ts
+++ b/lib/datasource/npm/get.ts
@@ -4,7 +4,6 @@ import { logger } from '../../logger';
 import { ExternalHostError } from '../../types/errors/external-host-error';
 import * as packageCache from '../../util/cache/package';
 import type { Http } from '../../util/http';
-import type { HttpOptions } from '../../util/http/types';
 import { id } from './common';
 import { resolvePackage } from './npmrc';
 import type { NpmDependency, NpmRelease, NpmResponse } from './types';
@@ -63,7 +62,7 @@ export async function getDependency(
     return JSON.parse(memcache[packageName]) as NpmDependency;
   }
 
-  const { headers, packageUrl, registryUrl } = resolvePackage(packageName);
+  const { packageUrl, registryUrl } = resolvePackage(packageName);
 
   // Now check the persistent cache
   const cacheNamespace = 'datasource-npm';
@@ -78,18 +77,8 @@ export async function getDependency(
 
   const uri = url.parse(packageUrl);
 
-  if (uri.host === 'registry.npmjs.org' && !uri.pathname.startsWith('/@')) {
-    // Delete the authorization header for non-scoped public packages to improve http caching
-    // Otherwise, authenticated requests are not cacheable until the registry adds "public" to Cache-Control
-    // Ref: https://greenbytes.de/tech/webdav/rfc7234.html#caching.authenticated.responses
-    delete headers.authorization;
-  }
-
   try {
-    const opts: HttpOptions = {
-      headers,
-    };
-    const raw = await http.getJson<NpmResponse>(packageUrl, opts);
+    const raw = await http.getJson<NpmResponse>(packageUrl);
     const res = raw.body;
     if (!res.versions || !Object.keys(res.versions).length) {
       // Registry returned a 200 OK but with no versions
diff --git a/lib/datasource/npm/index.spec.ts b/lib/datasource/npm/index.spec.ts
index f373ac6f51575b0e46fad72aadf257aa77a99584..ec457904a471dd8115cdac9998a8bca3447562a2 100644
--- a/lib/datasource/npm/index.spec.ts
+++ b/lib/datasource/npm/index.spec.ts
@@ -1,5 +1,4 @@
 import mockDate from 'mockdate';
-import _registryAuthToken from 'registry-auth-token';
 import { getPkgReleases } from '..';
 import * as httpMock from '../../../test/http-mock';
 import { GlobalConfig } from '../../config/global';
@@ -9,11 +8,8 @@ import { NpmDatasource, getNpmrc, resetCache, setNpmrc } from '.';
 
 const datasource = NpmDatasource.id;
 
-jest.mock('registry-auth-token');
 jest.mock('delay');
 
-const registryAuthToken: jest.Mock<_registryAuthToken.NpmCredentials> =
-  _registryAuthToken as never;
 let npmResponse: any;
 
 describe('datasource/npm/index', () => {
@@ -237,10 +233,6 @@ describe('datasource/npm/index', () => {
   });
 
   it('should not send an authorization header if public package', async () => {
-    registryAuthToken.mockReturnValueOnce({
-      type: 'Basic',
-      token: '1234',
-    });
     httpMock
       .scope('https://registry.npmjs.org', {
         badheaders: ['authorization'],
@@ -253,17 +245,17 @@ describe('datasource/npm/index', () => {
   });
 
   it('should send an authorization header if provided', async () => {
-    registryAuthToken.mockReturnValueOnce({
-      type: 'Basic',
-      token: '1234',
-    });
     httpMock
       .scope('https://registry.npmjs.org', {
         reqheaders: { authorization: 'Basic 1234' },
       })
       .get('/@foobar%2Fcore')
       .reply(200, { ...npmResponse, name: '@foobar/core' });
-    const res = await getPkgReleases({ datasource, depName: '@foobar/core' });
+    const res = await getPkgReleases({
+      datasource,
+      depName: '@foobar/core',
+      npmrc: '_auth = 1234',
+    });
     expect(res).toMatchSnapshot();
     expect(httpMock.getTrace()).toMatchSnapshot();
   });
diff --git a/lib/datasource/npm/npmrc.spec.ts b/lib/datasource/npm/npmrc.spec.ts
index de5f5000f5611fa017e75ff06d5cdcd4ff40ef8b..78f337551bce74dfb1d6e9a5f3540868f343d115 100644
--- a/lib/datasource/npm/npmrc.spec.ts
+++ b/lib/datasource/npm/npmrc.spec.ts
@@ -1,7 +1,13 @@
+import ini from 'ini';
 import { mocked } from '../../../test/util';
 import { GlobalConfig } from '../../config/global';
 import * as _sanitize from '../../util/sanitize';
-import { getNpmrc, setNpmrc } from './npmrc';
+import {
+  convertNpmrcToRules,
+  getMatchHostFromNpmrcHost,
+  getNpmrc,
+  setNpmrc,
+} from './npmrc';
 
 jest.mock('../../util/sanitize');
 
@@ -13,7 +19,124 @@ describe('datasource/npm/npmrc', () => {
     GlobalConfig.reset();
     jest.resetAllMocks();
   });
-
+  describe('getMatchHostFromNpmrcHost()', () => {
+    it('parses //host', () => {
+      expect(getMatchHostFromNpmrcHost('//registry.npmjs.org')).toBe(
+        'registry.npmjs.org'
+      );
+    });
+    it('parses //host/path', () => {
+      expect(
+        getMatchHostFromNpmrcHost('//registry.company.com/some/path')
+      ).toBe('https://registry.company.com/some/path');
+    });
+    it('parses https://host', () => {
+      expect(getMatchHostFromNpmrcHost('https://registry.npmjs.org')).toBe(
+        'https://registry.npmjs.org'
+      );
+    });
+  });
+  describe('convertNpmrcToRules()', () => {
+    it('handles naked auth', () => {
+      expect(convertNpmrcToRules(ini.parse('_auth=abc123\n')))
+        .toMatchInlineSnapshot(`
+        Object {
+          "hostRules": Array [
+            Object {
+              "authType": "Basic",
+              "hostType": "npm",
+              "token": "abc123",
+            },
+          ],
+        }
+      `);
+    });
+    it('handles host, path and auth', () => {
+      expect(
+        convertNpmrcToRules(ini.parse('//some.test/with/path:_auth=abc123'))
+      ).toMatchInlineSnapshot(`
+        Object {
+          "hostRules": Array [
+            Object {
+              "authType": "Basic",
+              "hostType": "npm",
+              "matchHost": "https://some.test/with/path",
+              "token": "abc123",
+            },
+          ],
+        }
+      `);
+    });
+    it('handles host, path, port and auth', () => {
+      expect(
+        convertNpmrcToRules(
+          ini.parse('//some.test:8080/with/path:_authToken=abc123')
+        )
+      ).toMatchInlineSnapshot(`
+        Object {
+          "hostRules": Array [
+            Object {
+              "hostType": "npm",
+              "matchHost": "https://some.test:8080/with/path",
+              "token": "abc123",
+            },
+          ],
+        }
+      `);
+    });
+    it('handles naked authToken', () => {
+      expect(convertNpmrcToRules(ini.parse('_authToken=abc123\n')))
+        .toMatchInlineSnapshot(`
+        Object {
+          "hostRules": Array [
+            Object {
+              "hostType": "npm",
+              "token": "abc123",
+            },
+          ],
+        }
+      `);
+    });
+    it('handles host authToken', () => {
+      expect(
+        convertNpmrcToRules(
+          ini.parse(
+            '@fontawesome:registry=https://npm.fontawesome.com/\n//npm.fontawesome.com/:_authToken=abc123'
+          )
+        )
+      ).toMatchInlineSnapshot(`
+        Object {
+          "hostRules": Array [
+            Object {
+              "hostType": "npm",
+              "matchHost": "https://npm.fontawesome.com/",
+              "token": "abc123",
+            },
+          ],
+        }
+      `);
+    });
+    it('handles username and _password', () => {
+      expect(
+        convertNpmrcToRules(
+          ini.parse(
+            `//my-registry.example.com/npm-private/:_password=dGVzdA==\n//my-registry.example.com/npm-private/:username=bot\n//my-registry.example.com/npm-private/:always-auth=true`
+          )
+        )
+      ).toMatchInlineSnapshot(`
+        Object {
+          "hostRules": Array [
+            Object {
+              "hostType": "npm",
+              "matchHost": "https://my-registry.example.com/npm-private/",
+              "password": "test",
+              "username": "bot",
+            },
+          ],
+        }
+      `);
+    });
+  });
   it('sanitize _auth', () => {
     setNpmrc('_auth=test');
     expect(sanitize.addSecretForSanitizing).toHaveBeenCalledWith('test');
@@ -23,23 +146,19 @@ describe('datasource/npm/npmrc', () => {
   it('sanitize _authtoken', () => {
     setNpmrc('//registry.test.com:_authToken=test\n_authToken=${NPM_TOKEN}');
     expect(sanitize.addSecretForSanitizing).toHaveBeenCalledWith('test');
-    expect(sanitize.addSecretForSanitizing).toHaveBeenCalledTimes(1);
+    expect(sanitize.addSecretForSanitizing).toHaveBeenCalledTimes(2);
   });
 
   it('sanitize _password', () => {
     setNpmrc(
       `registry=https://test.org\n//test.org/:username=test\n//test.org/:_password=dGVzdA==`
     );
+    expect(sanitize.addSecretForSanitizing).toHaveBeenNthCalledWith(1, 'test');
     expect(sanitize.addSecretForSanitizing).toHaveBeenNthCalledWith(
-      1,
-      'dGVzdA=='
-    );
-    expect(sanitize.addSecretForSanitizing).toHaveBeenNthCalledWith(2, 'test');
-    expect(sanitize.addSecretForSanitizing).toHaveBeenNthCalledWith(
-      3,
+      2,
       'dGVzdDp0ZXN0'
     );
-    expect(sanitize.addSecretForSanitizing).toHaveBeenCalledTimes(3);
+    expect(sanitize.addSecretForSanitizing).toHaveBeenCalledTimes(2);
   });
 
   it('sanitize _authtoken with high trust', () => {
diff --git a/lib/datasource/npm/npmrc.ts b/lib/datasource/npm/npmrc.ts
index 31591f35e0989aa735ab7eefa7b132fe9a3fef7a..712ceb87351107d0b2d0817133618b89cd694200 100644
--- a/lib/datasource/npm/npmrc.ts
+++ b/lib/datasource/npm/npmrc.ts
@@ -1,17 +1,14 @@
 import url from 'url';
 import is from '@sindresorhus/is';
 import ini from 'ini';
-import registryAuthToken from 'registry-auth-token';
 import getRegistryUrl from 'registry-auth-token/registry-url.js';
 import { GlobalConfig } from '../../config/global';
 import { logger } from '../../logger';
-import type { OutgoingHttpHeaders } from '../../util/http/types';
-import { maskToken } from '../../util/mask';
+import type { HostRule } from '../../types';
+import * as hostRules from '../../util/host-rules';
 import { regEx } from '../../util/regex';
-import { addSecretForSanitizing } from '../../util/sanitize';
-import { fromBase64, toBase64 } from '../../util/string';
-import { ensureTrailingSlash } from '../../util/url';
-import type { Npmrc, PackageResolution } from './types';
+import { fromBase64 } from '../../util/string';
+import type { Npmrc, NpmrcRules, PackageResolution } from './types';
 
 let npmrc: Record<string, any> = {};
 let npmrcRaw = '';
@@ -37,21 +34,56 @@ function envReplace(value: any, env = process.env): any {
   });
 }
 
-const envRe = regEx(/(\\*)\$\{([^}]+)\}/);
-// TODO: better add to host rules (#9588)
-function sanitize(key: string, val: string): void {
-  if (!val || envRe.test(val)) {
-    return;
+export function getMatchHostFromNpmrcHost(input: string): string {
+  if (input.startsWith('//')) {
+    const matchHost = input.replace('//', '');
+    if (matchHost.includes('/')) {
+      return 'https://' + matchHost;
+    }
+    return matchHost;
   }
-  if (key.endsWith('_authToken') || key.endsWith('_auth')) {
-    addSecretForSanitizing(val);
-  } else if (key.endsWith(':_password')) {
-    addSecretForSanitizing(val);
-    const password = fromBase64(val);
-    addSecretForSanitizing(password);
-    const username: string = npmrc[key.replace(':_password', ':username')];
-    addSecretForSanitizing(toBase64(`${username}:${password}`));
+  return input;
+}
+
+export function convertNpmrcToRules(npmrc: Record<string, any>): NpmrcRules {
+  const rules: NpmrcRules = {
+    hostRules: [],
+  };
+  const hostType = 'npm';
+  const hosts: Record<string, HostRule> = {};
+  for (const [key, value] of Object.entries(npmrc)) {
+    if (!is.nonEmptyString(value)) {
+      continue;
+    }
+    const keyParts = key.split(':');
+    const keyType = keyParts.pop();
+    let matchHost = '';
+    if (keyParts.length) {
+      matchHost = getMatchHostFromNpmrcHost(keyParts.join(':'));
+    }
+    const rule: HostRule = hosts[matchHost] || {};
+    if (keyType === '_authToken' || keyType === '_auth') {
+      rule.token = value;
+      if (keyType === '_auth') {
+        rule.authType = 'Basic';
+      }
+    } else if (keyType === 'username') {
+      rule.username = value;
+    } else if (keyType === '_password') {
+      rule.password = fromBase64(value);
+    } else {
+      continue; // don't add the rule
+    }
+    hosts[matchHost] = rule;
   }
+  for (const [matchHost, rule] of Object.entries(hosts)) {
+    const hostRule = { ...rule, hostType };
+    if (matchHost) {
+      hostRule.matchHost = matchHost;
+    }
+    rules.hostRules.push(hostRule);
+  }
+  return rules;
 }
 
 export function setNpmrc(input?: string): void {
@@ -65,9 +97,6 @@ export function setNpmrc(input?: string): void {
     npmrc = ini.parse(input.replace(regEx(/\\n/g), '\n'));
     const { exposeAllEnv } = GlobalConfig.get();
     for (const [key, val] of Object.entries(npmrc)) {
-      if (!exposeAllEnv) {
-        sanitize(key, val);
-      }
       if (
         !exposeAllEnv &&
         key.endsWith('registry') &&
@@ -82,12 +111,14 @@ export function setNpmrc(input?: string): void {
         return;
       }
     }
-    if (!exposeAllEnv) {
-      return;
+    if (exposeAllEnv) {
+      for (const key of Object.keys(npmrc)) {
+        npmrc[key] = envReplace(npmrc[key]);
+      }
     }
-    for (const key of Object.keys(npmrc)) {
-      npmrc[key] = envReplace(npmrc[key]);
-      sanitize(key, npmrc[key]);
+    const npmrcRules = convertNpmrcToRules(npmrc);
+    if (npmrcRules.hostRules.length) {
+      npmrcRules.hostRules.forEach((hostRule) => hostRules.add(hostRule));
     }
   } else if (npmrc) {
     logger.debug('Resetting npmrc');
@@ -108,24 +139,5 @@ export function resolvePackage(packageName: string): PackageResolution {
     registryUrl,
     encodeURIComponent(packageName).replace(regEx(/^%40/), '@')
   );
-  const headers: OutgoingHttpHeaders = {};
-  let authInfo = registryAuthToken(registryUrl, { npmrc, recursive: true });
-  if (
-    !authInfo &&
-    npmrc &&
-    npmrc._authToken &&
-    ensureTrailingSlash(registryUrl) ===
-      ensureTrailingSlash(npmrc?.registry || '')
-  ) {
-    authInfo = { type: 'Bearer', token: npmrc._authToken };
-  }
-
-  if (authInfo?.type && authInfo.token) {
-    headers.authorization = `${authInfo.type} ${authInfo.token}`;
-    logger.trace(
-      { token: maskToken(authInfo.token), npmName: packageName },
-      'Using auth (via npmrc) for npm lookup'
-    );
-  }
-  return { headers, packageUrl, registryUrl };
+  return { packageUrl, registryUrl };
 }
diff --git a/lib/datasource/npm/types.ts b/lib/datasource/npm/types.ts
index aa4ff75f9a6797595bc75716d275157ca09df55f..f6153bcfa0c81d60177ee10c827a4bd943620047 100644
--- a/lib/datasource/npm/types.ts
+++ b/lib/datasource/npm/types.ts
@@ -1,6 +1,10 @@
-import type { OutgoingHttpHeaders } from '../../util/http/types';
+import type { HostRule } from '../../types';
 import type { Release, ReleaseResult } from '../types';
 
+export interface NpmrcRules {
+  hostRules?: HostRule[];
+}
+
 export interface NpmResponse {
   _id: string;
   name?: string;
@@ -43,7 +47,6 @@ export interface NpmDependency extends ReleaseResult {
 export type Npmrc = Record<string, any>;
 
 export interface PackageResolution {
-  headers: OutgoingHttpHeaders;
   packageUrl: string;
   registryUrl: string;
 }