---
title: "npm Registry Mirror Setup for China: pnpm, Yarn, Bun"
description: "Working npm mirror URLs, plus how to switch and revert registries in npm, pnpm, Yarn and Bun, and how to find which layer makes installs slow."
url: https://cdnping.org/en/blog/npm-registry-mirror-china-setup
language: en
published: 2026-10-21
category: "Developer tools"
tags: ["npm","pnpm","yarn","bun","registry mirror","frontend tooling"]
source: cdnping.org
license: Free to quote and summarize with a link to the url above.
---
# npm Registry Mirror Setup for China: pnpm, Yarn, Bun

The most widely used npm mirror in China is `https://registry.npmmirror.com`, and the official registry is `https://registry.npmjs.org`. The old `registry.npm.taobao.org` was shut down long ago, so if it is still in your config, replace it soon. Switching registries only changes where package metadata and tarballs are downloaded from. The other half of slow installs is usually binary downloads, lockfiles, and your local network, and we cover those separately below.

## First, which registry are you actually using?

Check the current state before changing anything. Many "I switched and it's still slow" cases turn out to be a config overridden somewhere else.

```bash
npm config get registry
npm config list            # shows which config file each setting comes from
pnpm config get registry
yarn config get registry          # yarn v1
yarn config get npmRegistryServer # yarn Berry (v2+)
```

npm config precedence, from highest to lowest: command-line flags > environment variables (`NPM_CONFIG_REGISTRY` / `npm_config_registry`) > the project `.npmrc` > the user-level `~/.npmrc` > the global config. If CI sets an environment variable, nothing you write in the project `.npmrc` takes effect.

While you are at it, check for leftovers of the old domain:

```bash
grep -rn "npm.taobao.org" ~/.npmrc ~/.yarnrc ~/.yarnrc.yml .npmrc .yarnrc .yarnrc.yml \
  package-lock.json yarn.lock 2>/dev/null
```

## How do you switch registries in each package manager?

Each tool gets three forms: global (only affects your machine), project-level (travels with the repo, so the team stays consistent), and one-off (a single command).

### npm

```bash
# Global: writes to ~/.npmrc
npm config set registry https://registry.npmmirror.com

# Project-level: create .npmrc in the project root
echo "registry=https://registry.npmmirror.com" > .npmrc

# One-off
npm install --registry=https://registry.npmmirror.com
```

### pnpm

pnpm reads the same `.npmrc`, so the project-level npm approach above works for pnpm too.

```bash
pnpm config set registry https://registry.npmmirror.com   # global
pnpm install --registry=https://registry.npmmirror.com    # one-off
```

### yarn v1 (Classic)

yarn v1 reads `.npmrc` and also has its own `.yarnrc`.

```bash
yarn config set registry https://registry.npmmirror.com   # global, writes to ~/.yarnrc
echo 'registry "https://registry.npmmirror.com"' > .yarnrc  # project-level
yarn install --registry https://registry.npmmirror.com     # one-off
```

### yarn Berry (v2 and later)

Berry does not read `.npmrc`, and the setting has a different name: `npmRegistryServer`.

```bash
# Project-level: writes to .yarnrc.yml in the current directory
yarn config set npmRegistryServer https://registry.npmmirror.com

# Global: writes to ~/.yarnrc.yml
yarn config set -H npmRegistryServer https://registry.npmmirror.com

# One-off: use an environment variable
YARN_NPM_REGISTRY_SERVER=https://registry.npmmirror.com yarn install
```

### bun

bun recommends `bunfig.toml`: put it in the repo root for project-level config, or at `~/.bunfig.toml` for global config.

```toml
[install]
registry = "https://registry.npmmirror.com"
```

Newer versions of bun also read `registry` from `.npmrc` and honor the `NPM_CONFIG_REGISTRY` environment variable. If some people on your team use npm and others use bun, a project-level `.npmrc` is the easiest shared config.

### Quick reference

| Tool | Project-level config file | Setting | One-off |
| --- | --- | --- | --- |
| npm | `.npmrc` | `registry` | `--registry=` |
| pnpm | `.npmrc` | `registry` | `--registry=` |
| yarn v1 | `.yarnrc` or `.npmrc` | `registry` | `--registry` |
| yarn Berry | `.yarnrc.yml` | `npmRegistryServer` | `YARN_NPM_REGISTRY_SERVER=` |
| bun | `bunfig.toml` or `.npmrc` | `install.registry` | `NPM_CONFIG_REGISTRY=` |

## How do you switch back to the official registry for publishing or the latest versions?

The mirror syncs from the official registry, so a newly published version may take a while to show up. Also, `npm publish` and `npm login` must go to the official registry, because the mirror does not accept publishes.

```bash
# Switch back permanently
npm config set registry https://registry.npmjs.org
yarn config set npmRegistryServer https://registry.npmjs.org   # Berry

# Specify it only when publishing
npm publish --registry=https://registry.npmjs.org
```

If your company runs a private registry, you can point just one scope at it without affecting other packages:

```ini
# .npmrc
registry=https://registry.npmmirror.com
@your-company:registry=https://npm.your-company.example/
```

The private registry URL here is only an example.

## Why does CI still hit the old address after you switched?

The cause is usually the lockfile. Different lockfiles record download URLs in different ways:

- **package-lock.json (npm)**: every package has a full `resolved` URL. Newer npm versions have a `replace-registry-host` setting whose default is `npmjs`, meaning npm only swaps `registry.npmjs.org` for the registry you currently configured. If the lockfile contains npmmirror or the old Taobao domain, npm will not replace it, so even if CI switches back to the official registry it still requests that address, and fails outright if the old domain is dead.
- **yarn.lock (v1)**: also contains full `resolved` URLs, and the old domain has to be replaced by hand.
- **yarn Berry's yarn.lock and pnpm-lock.yaml**: for packages on the default registry they generally record only the version and integrity hash, not the full URL, so they are barely affected.
- **bun.lock**: packages from the default registry usually do not have full URLs recorded. If in doubt, check with grep.

There are three fixes, ordered from smallest to largest change:

```bash
# 1. Have npm replace the host in every resolved URL, for this run only
npm ci --replace-registry-host=always

# 2. Replace the old domain in the lockfile with the official registry (check the diff before committing)
sed -i 's#https://registry.npm.taobao.org#https://registry.npmjs.org#g' package-lock.json

# 3. Delete the lockfile and regenerate it (this also upgrades dependencies, so use with care)
```

Our recommendation: keep the official registry URL in the lockfile, and let each person's machine and CI choose the actual registry through `.npmrc` or environment variables. That way the lockfile does not churn with personal habits, and switching registries does not require touching the repo. The integrity hash is independent of the download URL, so as long as the tarball downloaded from the mirror has identical content, verification passes.

## Why are electron, sharp, and playwright still slow after switching?

The npm packages themselves are tiny. The big downloads happen in the `postinstall` step, from a different domain: electron pulls binaries from GitHub Releases, playwright and puppeteer download browsers, and old versions of node-sass download prebuilt bindings. Changing the registry does not redirect any of these downloads.

To tell whether this is your problem, watch which step the install log stalls on.

```bash
npm install --foreground-scripts --loglevel=http
```

If it ends up stuck in some package's `postinstall`, and the log shows github.com, playwright, or storage-related domains, the slow part is the binary download, not the registry.

The usual fix is to point the download URL at a mirror through environment variables. npmmirror provides a binary mirror directory (`https://npmmirror.com/mirrors/`); use whatever paths actually exist in that directory:

```bash
export ELECTRON_MIRROR="https://npmmirror.com/mirrors/electron/"
export PLAYWRIGHT_DOWNLOAD_HOST="https://npmmirror.com/mirrors/playwright"
export SASS_BINARY_SITE="https://npmmirror.com/mirrors/node-sass"
```

A few things to keep in mind:

- Recent sharp versions publish their prebuilt files as `@img/sharp-*` npm packages, which go through the registry mirror along with everything else, so you generally do not need a binary URL. Older versions do.
- Many old tutorials put keys like `electron_mirror=...` in `.npmrc`. Newer npm warns about config keys it does not recognize, and says they will stop being supported in a future major version. Environment variables or the CI `env` block are safer.
- Newer puppeteer versions use different download variable names than before; follow its official documentation.
- If CI does not need a browser, use the tool's "skip download" variable to drop this step entirely.

## Where is it slow? A diagnostic flow

Switching registries solves the problem of being far from the origin server. If the slowness is in DNS, a proxy, or your local network, a new registry will not help. Work through the layers in this order:

**Step 1: measure connection time to the registry and to the binary download domains separately.**

```bash
for u in https://registry.npmjs.org/react \
         https://registry.npmmirror.com/react \
         https://github.com; do
  curl -o /dev/null -s -w "$u\n  dns=%{time_namelookup}s connect=%{time_connect}s tls=%{time_appconnect}s ttfb=%{time_starttransfer}s total=%{time_total}s\n" "$u"
done
```

These timings are cumulative: `dns` is name resolution, `connect` is when the TCP connection is established, `tls` is when the handshake completes, and `ttfb` is when the server returns the first byte. This breakdown matches how the cdnping [online speed test](/en) works: the home page measures public frontend CDNs for "first connection" (including DNS and the TLS handshake) and "round trip" (the median of 3 requests over a reused connection). Run it in your browser for a quick view of the rough gap between your network and CDN nodes inside and outside China. For cross-CDN comparisons see the [CDN speed ranking](/en/benchmark/cdn), and for how we measure see [methodology](/en/methodology). For what each of these timings means, see [AI API latency breakdown](/en/blog/api-latency-connect-rtt-ttft); the principle is the same as for package installs.

**Step 2: match the result to a fix.**

| Symptom | Likely cause | Fix |
| --- | --- | --- |
| `dns` alone takes several seconds | Slow or poisoned DNS resolution | Change DNS; see the [DNS over HTTPS setup guide](/en/blog/dns-over-https-setup-guide) |
| `dns` is normal, `connect` is high | Long route or packet loss to the origin | Switch to a mirror |
| Mirror `ttfb` is normal but install is still slow | Stuck on binary downloads | Configure binary mirrors as in the previous section |
| Sometimes fast, sometimes slow, some requests time out | Proxy rules or IPv6 egress mismatch | Check traffic routing and dual-stack |
| Both the official registry and the mirror are slow | Your local network itself has a problem | Run an overall network self-check first |

**Step 3: check your proxy.** npm reads the `proxy` and `https-proxy` settings and the `HTTPS_PROXY` environment variable. With a global proxy on, traffic to a China-based mirror may detour overseas and come back, which makes it slower. Use `npm config get https-proxy` and `env | grep -i proxy` to look for leftovers. To judge whether your routing rules are working, see [How Chinese websites see your real IP](/en/blog/how-chinese-sites-see-your-real-ip) and [how split tunneling works](/en/learn/split-tunneling).

If your system has both IPv4 and IPv6 but the proxy only handles one of them, you get "the same command is sometimes fast and sometimes times out." For troubleshooting, see [IPv6 proxy not taking effect](/en/blog/ipv6-dual-stack-proxy-mismatch).

**Step 4: still unclear? Run a full-path check.** The [developer network self-check list (2026 edition)](/en/blog/developer-network-self-check-2026) strings together egress IP, DNS, traffic routing, and speed testing in one sequence, and is worth running on a new machine or a new CI runner.

## Checklist before and after switching

- [ ] `npm config list` has no `npm.taobao.org`
- [ ] The project `.npmrc`, `.yarnrc.yml`, or `bunfig.toml` matches what the team agreed on
- [ ] The lockfile has no old domains; in npm projects, `resolved` consistently uses the official registry
- [ ] CI environment variables do not override the project config (or the override is intentional)
- [ ] The publish workflow explicitly passes `--registry=https://registry.npmjs.org`
- [ ] Binary download URLs for electron, playwright, and similar are in environment variables, not in `.npmrc`
- [ ] You have used curl to measure connection time to the registry and the binary domains, so you know which layer is slow

## Summary

Changing the mirror itself takes one line. If installs are still slow afterward, first use curl or the [online speed test](/en) to split out DNS, connection, and first-byte time, then use the table above to decide whether to change the registry, configure binary mirrors, or check your proxy and DNS.
