To fix pip install failures in Iran, point pip at a domestic mirror: pip config set global.index-url https://mirror.novin.cloud/artifactory/api/pypi/pypi/simple/. From that moment every Python package is delivered from Novin Cloud infrastructure inside the country — no sanctions blocks, no VPN, and at local network speed. Below we do the same for Poetry, uv, Pipenv, Conda, Docker and CI/CD pipelines.
Why does pip install fail in Iran?
The official Python package repository, PyPI, and its content delivery network restrict outbound requests from Iran. The result is three familiar error patterns every Python developer has seen: the connection dropping mid-download, long timeouts, and finally Could not fetch URL ... There was a problem confirming the ssl certificate.
The usual workarounds each carry a cost. A VPN on a production server is both unstable and hard to justify from a security standpoint. Manually downloading wheel files and copying them to the server does not resolve nested dependencies. Using a public proxy means handing your build traffic to a service you do not control.
A domestic mirror removes exactly this bottleneck: a mirrored copy of the upstream repository hosted inside Iran that replaces the foreign repository with a single URL change. We covered the same pattern for JavaScript in fixing npm install with the Novin Cloud mirror and for containers in the Docker Hub mirror guide.
What does the Novin Cloud PyPI mirror serve?
The Novin Cloud mirrors service hosts more than 79 public repositories on the mirror.novin.cloud domain — from Linux distributions and language package managers to Docker images and Helm charts. The service is public and free, and no account, API key or login is required to pull packages.
For the Python ecosystem, three endpoints matter:
| Purpose | URL |
|---|---|
| PyPI index for pip, Poetry and uv | https://mirror.novin.cloud/artifactory/api/pypi/pypi/simple/ |
| Conda main channel | https://mirror.novin.cloud/artifactory/api/conda/conda/main |
| conda-forge channel | https://mirror.novin.cloud/artifactory/api/conda/conda-forge |
| CPython source tarballs | https://mirror.novin.cloud/python-source/ |
The mirror runs on JFrog Artifactory, and the repositories you consume are of the virtual type: a single address that covers both the local cache and the upstream repository behind the scenes. That is why you never need to look for -local or -remote names. The full list lives in the all-repositories documentation.
Configuring pip: four methods, from temporary to permanent
1. The pip config command (recommended)
The simplest and most durable route is to let pip write the setting into your user configuration file:
pip config set global.index-url https://mirror.novin.cloud/artifactory/api/pypi/pypi/simple/
This creates or updates the configuration file at the correct path for your operating system, with no manual editing.
2. Editing pip.conf directly
If you want the setting inside an image, an install script or a version-controlled config file, write the file yourself. The format is INI:
[global]
index-url = https://mirror.novin.cloud/artifactory/api/pypi/pypi/simple/
The path depends on your operating system and the scope you want:
| Scope | Linux / macOS | Windows |
|---|---|---|
| User | ~/.config/pip/pip.conf (or ~/.pip/pip.conf) | %APPDATA%\pip\pip.ini |
| Global | /etc/pip.conf | C:\ProgramData\pip\pip.ini |
| Virtualenv | $VIRTUAL_ENV/pip.conf | %VIRTUAL_ENV%\pip.ini |
pip resolves precedence in this order: command-line options, then environment variables, then configuration files. Full details are in the official pip documentation.
3. The PIP_INDEX_URL environment variable
For Docker, CI, and anywhere you would rather not ship an extra file, an environment variable is the best fit. Every long pip option has an equivalent variable prefixed with PIP_:
export PIP_INDEX_URL=https://mirror.novin.cloud/artifactory/api/pypi/pypi/simple/
pip install -r requirements.txt
4. A one-off install with no config change
If you only need the mirror for a single install, the -i flag is enough and nothing on your system changes:
pip install requests -i https://mirror.novin.cloud/artifactory/api/pypi/pypi/simple/
Verifying that the setting took effect
These two commands show the current configuration and the real download path:
pip config list
pip download requests -d /tmp --no-deps -v | grep mirror
If the second command's output contains mirror.novin.cloud, the configuration is correct. To revert, simply unset it:
pip config unset global.index-url
Configuring Poetry with a domestic mirror
Poetry stores the package source in the project's pyproject.toml rather than in a global user config. That means the mirror configuration travels with your repository, and every team member and CI runner picks up the same URL automatically:
poetry source add --priority=primary novin-mirror https://mirror.novin.cloud/artifactory/api/pypi/pypi/simple/
The command produces the block below, which you can also write by hand:
[[tool.poetry.source]]
name = "novin-mirror"
url = "https://mirror.novin.cloud/artifactory/api/pypi/pypi/simple/"
priority = "primary"
The key decision is priority. Poetry offers three levels:
- primary — all primary sources are searched for every dependency, and as soon as you configure at least one primary source the implicit PyPI source is disabled. This is exactly what we want for bypassing sanctions.
- supplemental — searched only when no higher-priority source yields a compatible distribution.
- explicit — used only when a package configuration explicitly points at that source.
After adding the source, regenerate the lock file so the stored URLs are refreshed:
poetry lock
poetry install
The priority levels are documented in full in the official Poetry documentation.
uv, Pipenv and Conda
uv
uv supports both an environment variable and in-project configuration. The current recommended variable is UV_DEFAULT_INDEX; the older UV_INDEX_URL still works but is marked deprecated in the official documentation:
export UV_DEFAULT_INDEX=novin=https://mirror.novin.cloud/artifactory/api/pypi/pypi/simple/
uv sync
To pin the setting inside the project itself, add this block to pyproject.toml:
[[tool.uv.index]]
name = "novin"
url = "https://mirror.novin.cloud/artifactory/api/pypi/pypi/simple/"
default = true
Setting default = true makes this index replace PyPI.
Pipenv
In Pipenv the source is declared inside the Pipfile:
[[source]]
url = "https://mirror.novin.cloud/artifactory/api/pypi/pypi/simple/"
verify_ssl = true
name = "novin-mirror"
Conda
If you work with Conda, declare the channels in ~/.condarc:
channels:
- https://mirror.novin.cloud/artifactory/api/conda/conda/main
default_channels:
- https://mirror.novin.cloud/artifactory/api/conda/conda/main
To verify, run conda config --show channels followed by conda install numpy --dry-run -v. Step-by-step guides for all three tools are available in the Novin Cloud pip documentation and the Conda page.
If you build the Python interpreter itself from source or use pyenv, the tarball download can come from the mirror too:
export PYTHON_BUILD_MIRROR_URL=https://mirror.novin.cloud/python-source
pyenv install 3.12.6
Docker and CI/CD: where the mirror pays off most
On a laptop a failed package install is annoying; on a CI/CD pipeline the same failure means a red build and a delayed deployment. Because every build downloads dependencies from scratch, dependence on a foreign server does the most damage at exactly this point.
In a Dockerfile a single line is enough:
FROM python:3.12-slim
ENV PIP_INDEX_URL=https://mirror.novin.cloud/artifactory/api/pypi/pypi/simple/
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
In GitLab CI or GitHub Actions, declare the same variable in the job environment so every step uses the mirror:
variables:
PIP_INDEX_URL: "https://mirror.novin.cloud/artifactory/api/pypi/pypi/simple/"
If the Python base image itself is slow or unreachable, combining this setting with the Novin Cloud Docker registry keeps the whole build cycle domestic. For teams running builds on managed Kubernetes, declaring the variable in a shared ConfigMap applies the configuration once for every Pod. For a simpler single-machine setup, a Novin Cloud VPS alongside the mirror gives you a sanctions-free development environment. Startups assembling their infrastructure for the first time can begin with the startup solution.
Troubleshooting common errors
| Symptom | Likely cause | Fix |
|---|---|---|
| Traffic still goes to pypi.org | The setting is overridden at another level | Run pip config list; remember command-line beats environment variables, which beat config files |
| SSL certificate error | A proxy or VPN is still active | Clear http_proxy and https_proxy; the domestic mirror needs no proxy |
| A package is not found | A very recent release is not cached yet | Retry in a few minutes; the virtual repository fetches from upstream on first request |
| HTTP 502 | Temporary service-side disruption | Retry the request, and open a support ticket if it persists |
| Install fails in Docker but works locally | The user's pip.conf does not exist inside the image | Use ENV PIP_INDEX_URL instead of relying on the host config file |
One important note about lock files: if poetry.lock or uv.lock was generated against PyPI, the source URL is baked into the file. After switching sources, regenerate the lock file so installs genuinely come from the mirror.
Frequently asked questions
Is the Novin Cloud PyPI mirror free?
Yes. The mirror service is public and free, and pulling packages requires no account, API key or login.
Are all PyPI packages available on the mirror?
The repository is of the virtual type: any request not already cached is fetched from upstream and stored. The package coverage is therefore the same as PyPI — only the delivery path becomes domestic.
Can I publish my company's private packages to this URL?
No. This endpoint is read-only and serves public packages. Publishing private packages requires your own dedicated repository.
How do I revert to the previous configuration?
Run pip config unset global.index-url and pip goes back to PyPI. In Poetry, remove the source block from pyproject.toml; in Conda, drop the channel with conda config --remove channels.
Does my server have to be in Iran to use the mirror?
No, but the speed benefit is greatest when your server or workstation is inside Iran, since traffic is served over the domestic path.
Conclusion
Configuring a PyPI mirror is a one-line change with three payoffs: no dependence on a VPN, domestic download speed, and — most importantly — reliable CI/CD builds. For pip it is a single pip config set, for Poetry a [[tool.poetry.source]] block with primary priority, and for Docker one ENV line.
If the rest of your toolchain is affected by sanctions too, the same approach works for npm, Docker, Maven and Linux repositories. The full list is on the Novin Cloud mirrors page and in the mirrors introduction in the documentation. The Novin Cloud mirror service is available without registration — just change the index URL and watch your first pip install succeed.