npm config set proxy: Install Packages Behind a Proxy
npm config set proxy and https-proxy, the right .npmrc file, yarn and pnpm equivalents, noproxy for private registries, and keeping passwords out of your repo.
npm config set proxy is usually the first thing people type when npm install sits on a spinner and then dies with ECONNREFUSED or ETIMEDOUT. On its own it often isn't enough. npm fetches from https://registry.npmjs.org/, so the setting that matters is https-proxy, the value lands in a file you may not expect, yarn and pnpm read proxies from different places, and the password ends up in plain text. Every error string and behaviour in this guide was reproduced on our build box on 2026-10-30 against a local test proxy, using npm 11.19.1 on Node 24.22.0, pnpm 12.16.1, Yarn 4.18.1 and Yarn 1.22.22.
The Error Text That Points at a Proxy Problem
Match what your terminal printed against this table before changing anything. Each line is copied from a real run, with the stack trace trimmed.
| What npm prints | What it means |
|---|---|
| `npm error code ECONNREFUSED` ... `reason: connect ECONNREFUSED 127.0.0.1:9` | npm tried the proxy address and nothing was listening there |
| `npm error code ETIMEDOUT` ... `reason: connect ETIMEDOUT 10.255.255.1:3128` | The proxy host (or the registry, if no proxy is set) never answered |
| `npm error code ENOTFOUND` ... `getaddrinfo ENOTFOUND npm.internal.sparkproxy.io` | DNS could not resolve the registry or proxy hostname |
| `npm error code E407` / `407 Proxy Authentication Required - GET https://registry.npmjs.org/is-number` | The proxy was reached and rejected the credentials, or none were sent |
| `npm error code SELF_SIGNED_CERT_IN_CHAIN` | Something between you and the registry is re-signing TLS |
| `npm error code UNABLE_TO_VERIFY_LEAF_SIGNATURE` | Same cause, but the inspecting proxy sent only its leaf certificate |
When the failure is a connection problem, npm 11 appends its own hint:
npm error network This is a problem related to network connectivity.
npm error network In most cases you are behind a proxy or have bad network settings.
npm error network
npm error network If you are behind a proxy, please make sure that the 'proxy' config is set properly. See: 'npm help config'
Two details save a lot of guessing. The address in connect ECONNREFUSED 127.0.0.1:9 is the proxy npm is actually using, not the registry, so it tells you which config won. And E407 is good news of a sort: networking works, only the username or password is wrong.
The ETIMEDOUT run took about 21 seconds even with --fetch-timeout=15000, because the connection attempt sat on the OS's own connect timeout first. With npm's defaults (fetch-retries 2, fetch-timeout 300000 ms per the npm config reference) the same mistake can look like a hang for several minutes, which is why so many people report "npm install just freezes".
Four Checks Before You Edit Any Config
Work down the list and stop at the first step that shows something wrong.
1. See every proxy value npm has loaded, and which file it came from.
npm config list
The output groups settings under comments like ; "user" config from C:\Users\you\.npmrc and ; "project" config from /work/app/.npmrc. npm masks the password in this view (https-proxy = "http://alice:***@127.0.0.1:18081"). Don't try npm config get https-proxy to see the raw value: npm 11 refuses with The https-proxy option is protected, and cannot be retrieved in this way.
2. Check the environment. npm falls back to HTTPS_PROXY, https_proxy, HTTP_PROXY and http_proxy when the config keys are unset, and noproxy defaults to NO_PROXY. A stale variable exported in a shell profile is a common culprit.
env | grep -i _proxy # macOS / Linux / Git Bash
Get-ChildItem Env: | Where-Object Name -like '*proxy*' # PowerShell
3. Prove the proxy itself works, outside npm.
curl -sS -o /dev/null -w "%{http_code}\n" \
-x "http://USER:PASS@gateway.sparkproxy.io:11000" \
https://registry.npmjs.org/is-number
200 means the proxy and credentials are fine and the problem is npm's config. 407 means the credentials are wrong. A curl connection error means the network can't reach the proxy at all, and no npm setting will change that.
4. Run one install with request logging.
npm install is-number --loglevel http --fetch-retries=0
Setting --fetch-retries=0 makes a bad setting fail in seconds instead of minutes while you test.
Scraping at scale? Skip the blocks.
Fast, unblockable datacentre proxies with unlimited bandwidth.
Setting proxy and https-proxy
npm has two keys. Per the npm config reference, proxy is "a proxy to use for outgoing http requests" and https-proxy is "a proxy to use for outgoing https requests". The default registry is https://, so https-proxy carries nearly all traffic. Set both so plain-HTTP registries and tarball URLs are covered too:
npm config set proxy http://USER:PASS@gateway.sparkproxy.io:11000
npm config set https-proxy http://USER:PASS@gateway.sparkproxy.io:11000
Yes, https-proxy normally starts with http://. That scheme describes how npm talks to the proxy; the registry's TLS session then runs inside a CONNECT tunnel. Our test proxy logged exactly one CONNECT registry.npmjs.org:443 per install, with the credentials attached. The Node.js proxy rotation guide covers the same tunnel from inside your own code with agents, so it isn't repeated here.
With SparkProxy, port 11000 gives Random Rotation and port 11002 gives Sticky Sessions, where the exit IP holds for 5 minutes. For pulling public packages, rotation is fine: the registry doesn't care which IP a tarball request comes from. Choose 11002 when you want one install's connections to share an exit address, for example while comparing logs on both sides.
URL-encode special characters in the password. We set a password of p@ss#rd raw and npm failed with:
npm error code ENOTFOUND
npm error network request to https://registry.npmjs.org/is-number failed, reason: getaddrinfo ENOTFOUND ss
The first @ ended the userinfo, so npm tried to resolve ss as the proxy host. Encoded as p%40ss%23rd, the same install succeeded and the proxy received the decoded password. Encode @ as %40, # as %23, : as %3A and / as %2F, but leave the colon between username and password alone.
To remove a value, use npm config delete https-proxy, adding --location=project if it lives in the project file. When the last key goes, npm deletes the empty file.
Which .npmrc File Your Setting Lands In
The npmrc documentation lists four files, read in priority order:
| File | Path | Written by `npm config set` when |
|---|---|---|
| Per-project | `/path/to/project/.npmrc` (sibling of `package.json`) | you pass `--location=project` |
| Per-user | `~/.npmrc`, or wherever `userconfig` points | by default |
| Global | `$PREFIX/etc/npmrc` | you pass `--location=global` or `-g` |
| Built-in | inside npm's install folder | never; meant for distro packagers |
Command-line flags and npm_config_* environment variables override all four.
The default trips people up. The npm config reference states that location defaults to "user" unless --global is passed. We ran npm config set https-proxy ... inside a project folder and the project gained nothing; the line went to the user file. Only npm config set proxy ... --location=project created ./.npmrc.
That default is the safe one. A per-user setting follows you across every project on the machine and never sits inside a Git working tree. The project file is for settings the whole team should share, and a proxy password is not one of them.
Two more rules from the docs matter here. A project .npmrc "is not read in global mode, such as when running npm install -g", so a global install ignores a proxy you set per project. And since npm 11.2.0, unknown keys in .npmrc produce a warning such as warn Unknown user config "electron_mirror", with the docs saying a future major version will treat them as errors. Don't stash pnpm- or Yarn-only settings in a shared .npmrc to keep everything in one file.
noproxy for Private and Internal Registries
A company registry such as Verdaccio, Artifactory or Nexus usually lives on the internal network. Sending requests for it through an external proxy either fails outright, because the proxy can't route to private addresses, or reaches it from an IP the registry's allowlist doesn't recognise. With a rotating pool the exit IP changes constantly, so an allowlist on the registry side can't keep up. The fix is to keep that host off the proxy:
; .npmrc (project): safe to commit, holds no secrets
@acme:registry=https://npm.internal.sparkproxy.io/
noproxy=npm.internal.sparkproxy.io,localhost,127.0.0.1
npm describes noproxy as "domain extensions that should bypass any proxies", accepting a comma-delimited string, with the default taken from NO_PROXY. "Extensions" means suffix matching. With https-proxy pointed at a dead port, --noproxy=npmjs.org still let the install reach registry.npmjs.org directly, and NO_PROXY=.npmjs.org did the same.
That suffix behaviour cuts both ways. noproxy=sparkproxy.io would exempt every subdomain, which might include hosts you meant to proxy. List the narrowest hostnames that work.
One thing noproxy won't change is a lockfile. If package-lock.json was generated against an internal mirror, the resolved URLs point there. npm's replace-registry-host setting (default "npmjs") only rewrites URLs that came from registry.npmjs.org, so a lockfile full of internal mirror URLs keeps using that host even after you change registry.
Yarn and pnpm Read Proxies Differently
All three tools accept a proxy URL, but they don't read the same files or honour the same variables. This table reflects the four versions we ran:
| npm 11.19.1 | Yarn 1.22.22 | Yarn 4.18.1 | pnpm 12.16.1 | |
|---|---|---|---|---|
| Set command | `npm config set https-proxy URL` | `yarn config set https-proxy URL` | `yarn config set httpsProxy URL` | `pnpm config set https-proxy URL` |
| Where it was written | `~/.npmrc` | `~/.yarnrc` | `./.yarnrc.yml` (project) | ` |
| Reads proxy from project `.npmrc` | Yes | Yes | No | Yes, literal values only |
| Honours `HTTPS_PROXY` env | Yes | Yes | No | Yes |
| Bypass list | `noproxy` / `NO_PROXY` | `NO_PROXY` env only | per-host `networkSettings` | `noProxy` / `noproxy` / `NO_PROXY` |
| Expands `${VAR}` in a project file proxy URL | Yes | not tested | Yes | No (warns or ignores) |
| Shows the raw password | No (`***`, `get` refused) | Yes | Yes | Yes |
A few rows need explanation.
Yarn 4 ignores HTTPS_PROXY. With HTTPS_PROXY and HTTP_PROXY pointed at a dead port, Yarn 4 still installed a fresh package directly. Its yarnrc reference documents httpProxy and httpsProxy settings plus a general rule that settings can come from environment variables "prefixed" and in snake case, so the variable it reads is YARN_HTTPS_PROXY. That one worked. Yarn 4 also has no noProxy key; the closest tool is networkSettings, which sets proxy and TLS options per hostname pattern.
# .yarnrc.yml (Yarn 4)
httpsProxy: "http://${PROXY_USER}:${PROXY_PASS}@gateway.sparkproxy.io:11000"
Yarn 1 reads .npmrc but not its noproxy. It used https-proxy from a project .npmrc, then failed with tunneling socket could not be established, cause=connect ECONNREFUSED 127.0.0.1:9 even though the same file listed the registry under noproxy. Exporting NO_PROXY=registry.yarnpkg.com did bypass it.
pnpm 12 splits config across files. Its settings page says that "only auth, registry, TLS, proxy, and local-address settings are read from .npmrc files", with everything else in pnpm-workspace.yaml or the global config.yaml. Its network reference documents the fallback chain: httpsProxy, then the legacy proxy, then HTTPS_PROXY. pnpm also limits proxied connections to 50 per origin when maxsockets is unset, compared with npm's documented maxsockets default of 15.
pnpm's errors look nothing like npm's. A wrong password produced ERR_PNPM_TARBALL_FETCH_TARBALL ending in tunnel error: proxy authorization required, and a dead proxy ended in tunnel error: failed to create underlying connection.
How Proxy Passwords Leak From .npmrc
npm's own registry credentials get some protection. The npmrc docs require _auth, _authToken, username, _password and the certificate keys to be scoped to a registry URL "so that npm will never send credentials to the wrong host". Proxy URLs are not on that list. They're just strings, stored in plain text wherever you set them. Here's how they escape:
- Someone runs the set command with
--location=project, or copies a working.npmrcinto the repo "so the build works for everyone". The nextgit add .publishes it. - Docker build context.
COPY . .picks up a project.npmrcunless.dockerignoreexcludes it, and then the password lives in an image layer. The Docker proxy guide explains why the predefinedHTTPS_PROXYbuild args avoid that problem. - Every npm script can read it. npm exports config to scripts as
npm_config_*variables. A script that printedprocess.env.npm_config_https_proxyshowed the full URL, password included, coming from~/.npmrc. Any dependency's install script sees the same value. - Logs and screenshots.
npm config listmasks the password, butyarn config get httpsProxy,yarn config list(Yarn 1) andpnpm config get https-proxyall printed it in full in our runs. CI logs that echo config leak it too.
What doesn't leak: npm pack --dry-run on a project containing .npmrc listed only index.js and package.json, because npm always leaves .npmrc out of published tarballs. Git has no such rule.
Keeping Credentials Out of the Repository
Pick whichever option fits your setup. The first one removes the secret entirely; the last one only catches mistakes.
Option 1: whitelist the machine's IP, then commit a proxy URL with no password. If the proxy supports IP authorisation, there's nothing secret left to leak. SparkProxy supports both IP whitelist and user:pass auth; the proxy authentication explainer covers how each one is checked. This works for office networks and CI runners with fixed egress addresses, not for laptops that roam.
https-proxy=http://gateway.sparkproxy.io:11000
proxy=http://gateway.sparkproxy.io:11000
Option 2: environment variables, set outside the repo. All four tools except Yarn 4 picked up HTTPS_PROXY, and Yarn 4 reads YARN_HTTPS_PROXY. In CI, store the full URL as a secret and export it.
Option 3: ${VAR} placeholders in the config file. npm and Yarn 4 expanded placeholders in a committed file:
https-proxy=http://${PROXY_USER}:${PROXY_PASS}@gateway.sparkproxy.io:11000
There are two traps. First, npm's docs say an undefined variable "is left unreplaced". When we ran npm without exporting the variables, the proxy received the literal username ${PROXY_USER} and npm reported E407. A missing secret looks exactly like a wrong password. Append ? (as in ${PROXY_PASS?}) and npm substitutes an empty string instead, which at least makes the failure obvious.
Second, pnpm won't do this from a project file, on purpose. pnpm 12 printed:
[WARN] Ignored project-level request destination "https-proxy": environment variables are not expanded in repository-controlled registry or proxy URLs.
and then installed directly, without the proxy. pnpm's auth docs explain the reason: a project .npmrc arrives with the repository, so a malicious repository could otherwise send your CI tokens to a server it controls (advisory GHSA-3qhv-2rgh-x77r). The same placeholder in pnpm-workspace.yaml was dropped silently in our run, with no warning at all. On a network where direct connections are blocked, that ends in a timeout that doesn't mention the proxy. For pnpm, use HTTPS_PROXY or the user-level config.
Option 4: catch it before commit. This script fails when a package-manager config file holds a proxy URL with an inline password, while allowing ${VAR} placeholders and commented lines:
// scan-npmrc.mjs: fail if a package-manager config file holds a proxy password
import { readdirSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
const FILES = new Set(['.npmrc', '.yarnrc', '.yarnrc.yml', 'pnpm-workspace.yaml']);
const SKIP = new Set(['node_modules', '.git', '.yarn', '.pnpm-store']);
// scheme://user:password@host where the password is not a ${VAR} placeholder
const CRED_URL = /\b[a-z][a-z0-9+.-]*:\/\/[^\s:@/]+:(?!\$\{)[^\s@/]+@/i;
function* walk(dir) {
for (const e of readdirSync(dir, { withFileTypes: true })) {
if (e.isDirectory() && !SKIP.has(e.name)) yield* walk(join(dir, e.name));
else if (e.isFile() && FILES.has(e.name)) yield join(dir, e.name);
}
}
let hits = 0;
for (const file of walk(process.argv[2] ?? '.')) {
readFileSync(file, 'utf8').split(/\r?\n/).forEach((line, i) => {
if (CRED_URL.test(line) && !/^\s*[#;]/.test(line)) {
hits++;
console.error(`${file}:${i + 1}: proxy URL with an inline password`);
}
});
}
process.exit(hits ? 1 : 0);
Run node scan-npmrc.mjs . as a CI step or Git pre-commit hook. On a test tree it flagged a plain-text .npmrc and an encoded password in .yarnrc.yml, skipped a ${PROXY_PASS} line, a commented line and anything under node_modules, and exited 1. Add .npmrc to .dockerignore as well, and if a password ever reached a remote, rotate it. Deleting the file in a later commit leaves it in history.
When the Proxy Is Not the Problem
Some failures look like proxy problems and aren't. No amount of npm config set proxy will fix these.
| Symptom | Real cause | Fix |
|---|---|---|
| `SELF_SIGNED_CERT_IN_CHAIN` or `UNABLE_TO_VERIFY_LEAF_SIGNATURE` | A corporate proxy decrypts and re-signs TLS | Trust its CA with `cafile` |
| `E401` or `E403` from a private registry | Registry token missing or wrong | Scoped `//host/:_authToken` |
| `ENOTFOUND` on an internal hostname | Internal DNS not available, or proxy can't route inside | `noproxy` that host, fix VPN/DNS |
| `git+ssh://` dependency hangs | SSH ignores HTTP proxy settings | Switch to `git+https://` or configure SSH separately |
| Git dependency over HTTPS fails while registry installs work | Git reads its own proxy config | `git config --global http.proxy URL` or `HTTPS_PROXY` |
TLS inspection deserves a closer look, because the usual internet advice (npm config set strict-ssl false) switches off certificate checking for every registry request. We reproduced it with a test proxy that re-signed registry traffic with its own CA. npm failed with self-signed certificate in certificate chain when the proxy sent the full chain, and with unable to verify the first certificate; if the root CA is installed locally, try running Node.js with --use-system-ca when it sent only the leaf. Both of these fixed it without disabling verification:
npm config set cafile /etc/ssl/certs/corp-inspection-ca.pem
# or, for npm and any other Node process in this shell:
export NODE_EXTRA_CA_CERTS=/etc/ssl/certs/corp-inspection-ca.pem
Get the CA file from your IT team. The npmrc docs show node-options = "${NODE_OPTIONS?} --use-system-ca" as an example, which tells Node to trust the operating system's store if the CA is already installed there. Yarn 4 calls its equivalent httpsCaFilePath.
A plain forward proxy like SparkProxy's gateway never causes this. It tunnels the encrypted bytes and never sees the certificate. If you get these errors through a commercial proxy, something else on the path is inspecting traffic.
The git row comes from Git's own docs: http.proxy overrides the proxy "normally configured using the http_proxy, https_proxy, and all_proxy environment variables".
A Setup That Works on Laptops and CI
Put the pieces together and you end up with three layers, each holding only what belongs there.
Committed project .npmrc: registry routing and bypasses, no secrets.
@acme:registry=https://npm.internal.sparkproxy.io/
noproxy=npm.internal.sparkproxy.io,localhost,127.0.0.1
Each developer's ~/.npmrc: the proxy, set once with the default location.
npm config set https-proxy "http://USER:PASS@gateway.sparkproxy.io:11000"
npm config set proxy "http://USER:PASS@gateway.sparkproxy.io:11000"
CI: the proxy URL as a secret, exported as both variable names so npm, pnpm, Yarn 1 and Yarn 4 all see it.
# .github/workflows/install.yml
jobs:
install:
runs-on: ubuntu-latest
env:
HTTPS_PROXY: ${{ secrets.SPARKPROXY_URL }}
YARN_HTTPS_PROXY: ${{ secrets.SPARKPROXY_URL }}
NO_PROXY: npm.internal.sparkproxy.io,localhost,127.0.0.1
steps:
- uses: actions/checkout@v4
- run: node scan-npmrc.mjs .
- run: npm ci --fetch-retries=4
On cost and sizing: a large monorepo install moves a lot of tarball data, but SparkProxy plans include unlimited bandwidth and are sold as packs, not per GB, so a cold npm ci doesn't add traffic charges. What you size is concurrency. npm opens up to 15 connections per origin by default and pnpm up to 50 through a proxy, so parallel CI jobs multiply quickly. Plans start from $75/month (USD, from the SparkProxy site, read 2026-10-09), with a 24-hour free trial and no credit card. That's enough time to point one pipeline at gateway.sparkproxy.io:11000 and check the install times before you commit.
Frequently asked questions
FAQ
proxy is used for plain http:// requests and https-proxy for https:// requests. The default registry is https://registry.npmjs.org/, so https-proxy is the one that carries almost every install. Set both to the same URL.
In your user file, ~/.npmrc, because npm's location setting defaults to "user". Add --location=project to write ./.npmrc in the project, or --location=global for $PREFIX/etc/npmrc. Run npm config list to see which file each value came from.
The proxy is reachable but rejected the credentials. Check for unencoded @, # or : characters in the password, and for an unset ${VAR} placeholder, which npm sends literally. Test the same URL with curl -x to rule out npm.
Add the registry hostname to noproxy in .npmrc, or to the NO_PROXY environment variable. npm matches by domain suffix, so noproxy=npm.internal.sparkproxy.io covers that host and its subdomains. Yarn 1 honours only NO_PROXY, and Yarn 4 uses networkSettings.
For pnpm, run pnpm config set https-proxy URL or export HTTPS_PROXY. pnpm ignores ${VAR} placeholders in a project .npmrc. For Yarn 4, set httpsProxy in .yarnrc.yml or export YARN_HTTPS_PROXY, because it ignores HTTPS_PROXY. Yarn 1 accepts yarn config set https-proxy URL.
Only if it contains no password. Commit registry routing, noproxy and a credential-free proxy URL when the machine's IP is whitelisted. Keep usernames and passwords in ~/.npmrc or CI secrets, add .npmrc to .dockerignore, and rotate any password that reached Git history.
Get 20% off your first month
Premium datacentre proxies with unlimited bandwidth. Use the code at checkout.
Save up to 15% more on quarterly, half-yearly and yearly plans
Related articles
Sticky Session IP Changing Mid-Flow? Causes and Fixes
Sticky session IP changing mid-login or checkout? Check leaks, session ID format, the 5-minute window, parallel connections and exit churn in order.
httpx proxies Argument Removed? Fix ProxyError and 407
The httpx proxies argument is gone in 0.28. Fix the TypeError, then ProxyError 407, SOCKS ImportError and RemoteProtocolError, with verbatim errors and code.
Rotating Proxy Not Rotating? Fix Same-IP Responses
Rotating proxy same IP every request? Your client reuses one CONNECT tunnel or a sticky setting survived. Fix it in requests, httpx, Node, Playwright.
