If npm install stops in Iran with an ETIMEDOUT or ECONNRESET error, the problem is not your project: the official npm registry is not reachable from inside Iran. The fix is changing one address. Point your registry at the Novin Cloud mirror and packages are served from domestic infrastructure with no VPN involved. It is a single command, and it works the same way for npm, Yarn, pnpm and Bun.
Below you will see exactly what each error means, how to switch the registry in all four package managers, how to fix package-lock.json so it stops reaching for the foreign server, and how to carry the same setting into your Dockerfile and CI/CD pipeline.
Why does npm install fail in Iran?
The official npm registry is hosted at registry.npmjs.org. From Iran it either does not respond at all, or responds with high latency and frequent drops. The result is that installing a few megabytes of dependencies takes minutes or breaks halfway through.
The problem does not end there. Packages such as sharp, puppeteer and node-sass download compiled binaries from GitHub Releases or foreign CDNs during installation. Even if the main registry answers, those side downloads can fail independently.
In production the stakes are higher. When your CI/CD pipeline depends on a server outside the country for every build, deployment stability is effectively out of your hands. A few minutes of disruption on an international route turns the build red without a single line of your code having changed.
Common errors and what they actually mean
| Error code | What happened | Fix |
|---|---|---|
| ETIMEDOUT | The TCP connection to the registry was not established in time | Switch the registry to the domestic mirror |
| ECONNRESET | The connection dropped mid-transfer | Switch the registry to the domestic mirror |
| ERR_SOCKET_TIMEOUT | The response did not complete within the timeout | Switch the registry and raise fetch-timeout |
| 403 Forbidden | The request was rejected based on geographic location | Switch the registry to the domestic mirror |
| ENOTFOUND | The domain name did not resolve to an IP | Check DNS, then switch the registry |
| EAI_AGAIN | Temporary failure in name resolution | Set a stable DNS resolver and switch the registry |
| EINTEGRITY | The file checksum does not match the value recorded in the lock file | Clear the cache and rebuild the lock file |
All of these errors share one thing: none of them is a bug in your code. Every one of them comes down to the network path to the registry.
What is the Novin Cloud npm mirror?
A mirror is a copy of public software repositories hosted inside Iran. When you request a package, the same file is delivered from domestic infrastructure instead of a foreign server. The Novin Cloud mirrors service mirrors more than 79 public repositories: Linux distributions, language package managers, Docker images, Helm charts and DevOps tool binaries.
Three properties make it practical for developers in Iran:
- Public and free — no account, API key or login is required to pull packages.
- Virtual repository type — one address that covers both the local cache and the upstream repository behind the scenes. You never need to look for
-localor-remotenames. - Standard tooling — the mirror runs on JFrog Artifactory and exposes a dedicated npm API path, so your usual
npmcommands work unchanged.
The npm registry address used throughout this article is:
https://mirror.novin.cloud/artifactory/api/npm/npm/
Configuring npm in three minutes
The full path from error to a successful install is five steps.
Check the current registry so you know where you are starting from:
npm config get registryPoint the registry at the domestic mirror. This writes the setting into your user
~/.npmrcand applies to every project:npm config set registry https://mirror.novin.cloud/artifactory/api/npm/npm/Verify it. The second command makes a real request to the registry; if a version number comes back, the path is healthy:
npm config get registry npm view express versionClear the old cache and modules. This step is often skipped, and then people wonder why the errors persist:
npm cache clean --force rm -rf node_modulesInstall:
npm install
Per-project instead of system-wide
If you want the setting to apply to a single project and travel with the git repository for your teammates, create an .npmrc next to package.json:
registry=https://mirror.novin.cloud/artifactory/api/npm/npm/
npm resolves configuration in this order, highest priority first: command line flags, environment variables, the project .npmrc, the user .npmrc, and finally npm's built-in defaults. In other words, the project file always wins over the user setting — the full details are in the official npm documentation.
The important part: package-lock.json
This is the detail most guides leave out. For every dependency, package-lock.json stores a full resolved URL pointing at registry.npmjs.org. Until you rewrite those URLs, npm may still reach for the foreign server even with the registry correctly configured.
You have two options. The first rewrites the URLs in place:
sed -i 's#https://registry.npmjs.org#https://mirror.novin.cloud/artifactory/api/npm/npm#g' package-lock.json
The second deletes the lock file and regenerates it:
rm -f package-lock.json
npm install
The first option is safer because it keeps exact dependency versions untouched; the second is simpler but may shift minor dependency versions. On team projects, choose the first and commit the rewritten file.
Configuring Yarn, pnpm and Bun
All three other JavaScript package managers use the same registry; only the location of the setting differs.
Yarn
Yarn version 1 (Classic):
yarn config set registry https://mirror.novin.cloud/artifactory/api/npm/npm/
yarn config get registry
Yarn Berry (version 2 and later) reads the setting from the project .yarnrc.yml:
npmRegistryServer: "https://mirror.novin.cloud/artifactory/api/npm/npm/"
If your project already has an .npmrc with a registry setting, that is enough and you do not need to repeat it.
pnpm
pnpm config set registry https://mirror.novin.cloud/artifactory/api/npm/npm/
pnpm config get registry
pnpm view express version
pnpm also reads .npmrc, so project-level configuration behaves exactly as it does with npm.
Bun
Bun takes the setting from the project bunfig.toml (or ~/.bunfig.toml for a global setting):
[install]
registry = "https://mirror.novin.cloud/artifactory/api/npm/npm/"
Bun reads .npmrc as well, so if the project already has one, bunfig.toml is not required. To verify:
bun add express --dry-run
Detailed guides for all four tools are available in the Novin Cloud npm mirror documentation and on the Yarn, pnpm and Bun pages.
Using it in Docker and CI/CD
Inside a Docker image, prefer an environment variable over running npm config set: it adds no extra layer and applies across every build stage.
FROM node:22-alpine
ENV NPM_CONFIG_REGISTRY=https://mirror.novin.cloud/artifactory/api/npm/npm/
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
CMD ["node", "server.js"]
The equivalent variable for each tool:
| Tool | Environment variable |
|---|---|
| npm | NPM_CONFIG_REGISTRY |
| pnpm | NPM_CONFIG_REGISTRY |
| Yarn Classic | YARN_REGISTRY |
| Yarn Berry | YARN_NPM_REGISTRY_SERVER |
| Bun | BUN_CONFIG_REGISTRY |
If your base image cannot be pulled from Iran either, the next failure shows up at docker pull. For that you need to route the Docker registry through the domestic mirror as well; the Novin Cloud Docker registry does exactly that, and its guide is in the Docker and OCI documentation.
For builds running on managed Kubernetes, set the same variable in the Job definition or in the runner ConfigMap so that every build Pod uses the domestic mirror. If you do not have a cluster yet, our quick Kubernetes cluster setup article is a good starting point.
Troubleshooting what is left
If you still get errors after switching the registry, one of these four situations applies.
The old registry address still appears in the log
Something with higher priority is overriding your setting. Check what npm actually resolves:
npm config list -l | grep registry
The culprit is usually one of: the project .npmrc, the user .npmrc, or the resolved URLs inside package-lock.json discussed above.
EINTEGRITY errors
This means the checksum of the downloaded file does not match the value recorded in the lock file. Clear the cache and rebuild:
npm cache clean --force
rm -rf node_modules package-lock.json
npm install
A 502 response from the mirror
A 502 is usually a temporary service-side issue and clears on retry. If it persists, report it through a support ticket.
Private or scoped packages
If your organization hosts private packages on another registry, you do not have to move everything at once. Define a separate registry for that scope and let the rest of the traffic go through the mirror:
registry=https://mirror.novin.cloud/artifactory/api/npm/npm/
@my-company:registry=https://npm.my-company.ir/
The same logic applies to publishing: the mirror is a read-only repository. If you intend to run npm publish, specify the target registry explicitly in that command.
Packages that download separate binaries
Some packages reach out to an external URL during their postinstall step to fetch a compiled binary. Because that download does not go through the npm registry, switching the registry alone does not fix it. Puppeteer, which downloads Chromium separately, is the best-known example.
You have three practical options. First, disable the automatic download and use a system-installed build:
export PUPPETEER_SKIP_DOWNLOAD=true
npm install puppeteer
Second, use a Docker base image that already ships the required browser or library. Third, if the binary is published on GitHub Releases, use the Novin Cloud GitHub Releases mirror, documented in the GitHub Releases mirror guide.
The general rule: whenever an install hangs or fails, read the log and look at the last address it tried to reach. If it is not the registry domain, the problem is in the postinstall step, not in your registry configuration.
Frequently asked questions
Does the Novin Cloud mirror cost anything?
No. The mirror is a public, free service and requires no account, API key or login to pull packages. Changing the registry address is all it takes.
Are the packages I pull from the mirror identical to the originals?
Yes. The repository is a virtual type, meaning the same file is fetched from upstream and cached. File checksums do not change, which is why npm ci works with an existing lock file without issues.
What happens if a package is not cached on the mirror yet?
The virtual repository forwards the request upstream, fetches the file and caches it for subsequent requests. The first request may be slightly slower; later ones are served from the domestic cache.
Can I publish my own package to this mirror?
No. The mirror is designed for pulling packages, not publishing. To publish, connect to your own target registry — the public npm registry or your organization's private one.
Does the mirror make sense for servers outside Iran?
If your build server is outside Iran with direct access, you will not see a meaningful difference. The mirror's main advantage is for developers and build servers located inside Iran.
Conclusion
npm install errors in Iran are almost always a network-path problem, not a code problem. By pointing the registry at the Novin Cloud mirror, rewriting the resolved URLs in your lock file, and carrying the same environment variable into your Dockerfile and CI/CD pipeline, installing packages goes from a daily obstacle to a dependable step — with no VPN and no sign-up.
Next step: visit the mirrors and repositories service page to see which of the 79-plus available repositories your project needs. If you are designing infrastructure for a new team, our VPS selection guide, cloud cost optimization article and the startup solutions page are the logical next stops.