---
title: "Linux Proxy Settings: Terminal, apt, Docker and Git"
description: "On Linux a proxy is temporary with environment variables and permanent through /etc/environment, while apt, git, pip, npm and Docker each read their own file."
url: https://proxynet.io/blog/linux-proxy-settings
date: 2026-09-19
author: "Acar Diveroli"
category: "Tutorial, Integration"
lang: en
---

# Linux Proxy Settings: Terminal, apt, Docker and Git

You set the proxy on a Linux server, `curl` works, and then `sudo apt update` still goes to the old address, `git clone` hangs and `docker build` cannot reach the network at all. That does not mean you configured something wrong. Linux has no central "proxy switch": your shell's environment variables are one layer, each tool's own configuration file is a second layer, and services running under systemd are a third, entirely separate one.

This article walks through those three layers in order. First the temporary and permanent environment variable setup, the difference between upper and lower case, the spelling rules for `no_proxy` and why `sudo` does not carry your environment over. Then the configuration for apt, git, pip, npm and Docker one by one, why the desktop setting does not affect the terminal, how to verify each step and the undo line for every setting.

> **Note: Short answer**
>
> For temporary use in a terminal, `export http_proxy=` and `export https_proxy=` are enough. If you want it to persist, write it to `~/.bashrc` for a single user or to `/etc/environment` system-wide. Tools that ignore environment variables want their own file: `/etc/apt/apt.conf.d/` for apt, `git config http.proxy` for git, `pip.conf` for pip, `.npmrc` for npm, `/etc/docker/daemon.json` for the Docker daemon. `sudo` replaces your environment variables with a clean environment by default; use `sudo -E` to carry them over.

## Why is there no single place for proxy settings on Linux?

Windows and macOS keep a system proxy record in the operating system, and most programs read it. Linux has no such record. Instead there is a convention going back to the 1990s: while running, a program looks at the environment variables `http_proxy`, `https_proxy`, `all_proxy` and `no_proxy` and uses them if they are set. This is not a standard, it is a widespread habit. You have to know tool by tool which one follows it.

Separating the three layers solves most problems:

1. **The shell environment.** Variables you define with `export` are inherited by the processes you start from that shell. They end when you close the terminal.
2. **The tool's own configuration.** apt, git, pip, npm and Docker have their own files. These files work independently of the environment variables and usually override them.
3. **The service environment.** A service started by systemd was not spawned from your shell, so it never sees your variables. It reads from its own unit file.

To understand why a setting "does not work", first ask which layer that tool is looking at.

## What format does the proxy address use?

In every method below the address is written the same way:

```text
http://user:pass@pr.proxynet.io:8000
```

The `http://` scheme is the protocol of the connection made with the proxy, and it stays the same even when you go to HTTPS addresses. The `user:pass@` part is only needed if you authenticate with a username and password; if you authorised your server's IP in the panel, you drop that part entirely. We covered the difference between the two methods in [Proxy Authentication: User:Pass vs IP Whitelist](/blog/proxy-authentication-methods).

If your password contains `@`, `:`, `#` or `/`, write them into the address with percent encoding: `%40` for `@`, `%23` for `#`. An unencoded `@` splits the address in the wrong place and the result is usually a `407 Proxy Authentication Required`. Encoding details and how libraries differ on this point are in the credentials section of [Using a Proxy in Node.js: Axios and node-fetch](/blog/nodejs-proxy).

## Temporary setup with environment variables

The fastest route is to define the variables for the current session:

```bash
export http_proxy="http://user:pass@pr.proxynet.io:8000"
export https_proxy="$http_proxy"
export no_proxy="localhost,127.0.0.1,.yourcompany.local"

curl -s https://api.ipify.org; echo
```

If the output shows the proxy's IP address instead of yours, the setting works. To remove the variables, `unset http_proxy https_proxy no_proxy` is enough.

We explained what these variables are, which tools read them and how they relate to tool files such as `.wgetrc` in [Using a Proxy with wget: Commands and Examples](/blog/wget-proxy), and we do not repeat it here. For the cURL side options and SOCKS5 usage, see [How to Use cURL with Proxy?](/blog/curl-proxy).

Two practical notes: without `export` the variable stays in the shell itself and is not passed to the programs you run. If you want a temporary setting for a single command, put the variable in front of the command and only that command is affected:

```bash
https_proxy="http://user:pass@pr.proxynet.io:8000" curl -s https://api.ipify.org
```

## http_proxy and HTTP_PROXY: why does case matter?

On Linux, environment variable names are case sensitive: `http_proxy` and `HTTP_PROXY` are two different variables. Tools do not treat them the same way.

cURL and everything that uses libcurl accepts both the lower and upper case spelling of the proxy variables and gives priority to the lower case one. The single exception is `http_proxy`: cURL reads it **in lower case only**. The reason is security. When a CGI script runs, the server turns the incoming request's headers into environment variables prefixed with `HTTP_`, which means a remotely sent `Proxy:` header can fill the `HTTP_PROXY` variable. The security problems this behaviour caused in the past are described in the [cURL documentation](https://everything.curl.dev/usingcurl/proxies/env.html).

In practice two rules will do:

- **Treat lower case as primary.** Write `http_proxy`, `https_proxy`, `no_proxy`.
- **Define both.** Some Java and Go tools only look for the upper case spelling. Setting both groups to the same value is the least surprising route.

```bash
export http_proxy="http://user:pass@pr.proxynet.io:8000"
export https_proxy="$http_proxy"
export no_proxy="localhost,127.0.0.1"
# define the uppercase twins as well
export HTTP_PROXY="$http_proxy" HTTPS_PROXY="$https_proxy" NO_PROXY="$no_proxy"
```

`all_proxy` is the protocol independent default; if a protocol specific variable exists, it takes precedence. If you are going to use SOCKS5, prefer the form `all_proxy="socks5h://user:pass@pr.proxynet.io:1080"`. With the `socks5h` scheme, domain name resolution happens on the proxy side. You can find the differences on our [SOCKS5 Proxy](https://proxynet.io/socks5-proxy) page and in [SOCKS vs. HTTP Proxy: Which One Should You Choose?](/blog/socks-vs-http-proxy).

## How is no_proxy written?

`no_proxy` is a comma separated list of addresses that should not go through the proxy. It has no standard, which is why you see small differences between tools. In our tests with cURL 8.21 the behaviour is as follows:

| Spelling | Result |
|---|---|
| `example.com` | `example.com` and `www.example.com` go direct, whatever the port |
| `.example.com` | covers the subdomains and the domain itself |
| `sub.example.com` | only that subdomain; `example.com` above it still goes through the proxy |
| `EXAMPLE.COM` | not case sensitive, it matches |
| `example.com:80` | **does not match**, port notation is not supported |
| `10.0.0.0/8` | CIDR notation works from cURL 7.86 onwards |
| `*` | a single asterisk keeps every address off the proxy |

There are two traps. The first is port notation: `no_proxy` is a host list, so if you write `server:port` that line matches nothing and the request silently goes to the proxy. The second is that CIDR does not work everywhere; Python's `urllib` module does not recognise the `10.0.0.0/8` entry in the same list and sends the address `10.1.2.3` to the proxy. If you want to keep an internal network out on the Python side, write the addresses one by one or by domain name.

Put at least these on the list: `localhost`, `127.0.0.1`, your internal domain if you have one, and the container network. Otherwise the request to your local development server also travels through the proxy and times out.

## Making it permanent: ~/.bashrc and /etc/environment

When you close the terminal, the `export` lines are gone. There are two places for persistence and their scopes differ.

**`~/.bashrc` for a single user.** Add the same `export` lines to the end of the file, then reload it with `source ~/.bashrc`. This file runs in interactive shells, which means it applies when you log in over SSH and type commands. If you use Zsh the equivalent is `~/.zshrc` and the lines carry over unchanged; if you use fish the file is `~/.config/fish/config.fish` and the syntax becomes `set -gx http_proxy "…"`.

**`/etc/environment` for the whole system.** This file is read not by the shell but by PAM's `pam_env` module. The variables are defined for every user who opens a session. Its format is strict: one `KEY=VALUE` per line, and the `export` keyword is [accepted for compatibility as the documentation states](https://man7.org/linux/man-pages/man8/pam_env.8.html) but ignored, with no shell expansion. So a reference like `$http_proxy` does not work here, you have to write the value out:

```ini
# /etc/environment contents, plain and unquoted
http_proxy=http://user:pass@pr.proxynet.io:8000
https_proxy=http://user:pass@pr.proxynet.io:8000
no_proxy=localhost,127.0.0.1
```

The change applies to new sessions; to see it in an open SSH session, log out and back in. To undo it, delete the lines and renew the session.

In both files the password sits in plain text. If there is more than one user on the server, prefer the user file over `/etc/environment`, or authorise the server's IP in the panel and move to password free use. For that scenario, which needs a fixed exit address, [ISP Proxy](https://proxynet.io/static-isp-residential-proxy) and [Datacenter Proxy](https://proxynet.io/datacenter-proxy) packages are a good fit.

## Why does sudo not see your proxy setting?

If `sudo apt update` ignores the proxy, the reason is usually this: for security, sudo replaces most of the calling user's environment variables with a clean environment. You tell it that you want to preserve the user's environment with the `-E` option; as stated in the [sudo manual](https://www.sudo.ws/docs/man/sudo.man/), the security policy may reject that request.

```bash
sudo -E apt update
```

If the command still does not use the proxy, two possibilities remain: either the sudoers configuration does not allow those variables through, or the tool is already looking at its own file rather than the environment variable. For apt the second is the sturdier solution, and it is the subject of the next section.

## apt proxy settings

apt does read environment variables, but its real home is the `/etc/apt/apt.conf.d/` directory. A fragment file you drop in there solves both the `sudo` problem and updates that run from cron.

```text
// /etc/apt/apt.conf.d/95proxy
Acquire::http::Proxy "http://user:pass@pr.proxynet.io:8000";
Acquire::https::Proxy "http://user:pass@pr.proxynet.io:8000";
```

The semicolon at the end of the line is mandatory. To keep a specific server off the proxy, write a line for that host with the `DIRECT` keyword:

```text
Acquire::http::Proxy::repo.yourcompany.local "DIRECT";
```

There is one thing to watch in the file name: according to the [apt.conf manual](https://manpages.debian.org/bookworm/apt/apt.conf.5.en.html), only fragments in that directory with no extension or the extension `conf`, and whose names contain nothing but letters, digits, hyphens, underscores and periods, are read. So `95proxy` and `95proxy.conf` are valid; `95proxy.bak` is ignored and apt says so with a notice. To undo the setting, delete the file or move it out of the directory, no extra command needed.

## git proxy settings

git uses libcurl for HTTP and HTTPS addresses, so it already reads the `http_proxy` and `https_proxy` variables. If you want a permanent and explicit setting, write it into its own configuration:

```bash
git config --global http.proxy "http://user:pass@pr.proxynet.io:8000"

git config --global --get http.proxy      # verify
git config --global --unset http.proxy    # undo
```

If you want it to apply only to a specific server, you can condition it on the address. That lets you go direct to your internal git server and through the proxy to the outside:

```bash
git config --global http.https://github.com.proxy "http://user:pass@pr.proxynet.io:8000"
```

One detail that came out of our test causes trouble quite often. The default for git's `http.proxyAuthMethod` setting is `anyauth`, and according to the [git-config documentation](https://git-scm.com/docs/git-config) this mode assumes the proxy answers an unauthenticated request with a `407` and a `Proxy-Authenticate` header. On proxies that cannot complete that discovery round, git stops with a `Proxy CONNECT aborted` error. The same command passes on the first try with `basic`:

```bash
git config --global http.proxyAuthMethod basic
```

If you want to try the setting for a single command, the `-c` flag does not touch the configuration at all: `git -c http.proxy=... ls-remote <address>`. If you fetch over SSH, none of these settings apply; SSH needs a `ProxyCommand` line in `~/.ssh/config`.

## pip proxy settings

pip has three routes and the order of precedence is written in the [pip documentation](https://pip.pypa.io/en/stable/topics/configuration/): command line options override environment variables, and those override the configuration file.

```bash
# 1) one-off
pip install --proxy "http://user:pass@pr.proxynet.io:8000" requests

# 2) environment variable; the PIP_<OPTION> pattern works for every option
export PIP_PROXY="http://user:pass@pr.proxynet.io:8000"
```

For a permanent setting use the `~/.config/pip/pip.conf` file. `/etc/pip.conf` for the whole system and `$VIRTUAL_ENV/pip.conf` for a single virtual environment accept the same format:

```ini
[global]
proxy = http://user:pass@pr.proxynet.io:8000
```

If you are not sure which file is being read, `pip config debug` lists all the paths and the values currently in effect. To undo it, write `pip config unset global.proxy` or delete the line from the file.

## npm proxy settings

npm reads `.npmrc` files in project, user, global and built-in order; the earlier one overrides the later. According to the [npm documentation](https://docs.npmjs.com/cli/v11/using-npm/config), the `HTTP_PROXY` and `HTTPS_PROXY` environment variables are also honoured, and the default for the `noproxy` option is the `NO_PROXY` variable.

```bash
npm config set proxy "http://user:pass@pr.proxynet.io:8000"
npm config set https-proxy "http://user:pass@pr.proxynet.io:8000"
npm config set noproxy "localhost,127.0.0.1,registry.yourcompany.local"

npm config delete proxy && npm config delete https-proxy   # undo
```

To make the setting apply only in a single repository, add `--location=project` to the commands; npm then writes the values to the `.npmrc` file in the project root. Do not commit that file to version control, it contains a password.

A small surprise: the `npm config get https-proxy` command does not show the value, it prints a "protected" warning. Options that contain credentials are closed to reading. To see the value, open the `.npmrc` file directly.

## Docker proxy settings

There is no single setting in Docker: the daemon and the containers read the setting from two independent places, and on top of that there is the classic address trap. Most of the confusion comes from this.

**1. The background process (pulling images).** `docker pull` and `docker push` are done by dockerd, not by your shell. According to the [Docker documentation](https://docs.docker.com/engine/daemon/proxy/), the setting goes into the `proxies` key in the `/etc/docker/daemon.json` file:

```json
{
  "proxies": {
    "http-proxy": "http://user:pass@pr.proxynet.io:8000",
    "https-proxy": "http://user:pass@pr.proxynet.io:8000",
    "no-proxy": "localhost,127.0.0.1,.yourcompany.local"
  }
}
```

The same job can also be done with a fragment file on the systemd side. These lines go into the `/etc/systemd/system/docker.service.d/http-proxy.conf` file:

```ini
[Service]
Environment="HTTP_PROXY=http://user:pass@pr.proxynet.io:8000"
Environment="HTTPS_PROXY=http://user:pass@pr.proxynet.io:8000"
Environment="NO_PROXY=localhost,127.0.0.1,.yourcompany.local"
```

With either method the setting takes effect after `sudo systemctl daemon-reload` and `sudo systemctl restart docker`. This pattern is not specific to Docker: every service running under systemd is told about a proxy this way, because the service is not spawned from your shell and never reads your `~/.bashrc`.

**2. Containers and builds.** Getting the application inside the container out to the network is a separate matter. As described in [its own documentation](https://docs.docker.com/engine/cli/proxy/), the Docker CLI reads the `proxies.default` block in the `~/.docker/config.json` file and passes those values to new containers and builds as environment variables:

```json
{
  "proxies": {
    "default": {
      "httpProxy": "http://user:pass@pr.proxynet.io:8000",
      "httpsProxy": "http://user:pass@pr.proxynet.io:8000",
      "noProxy": "localhost,127.0.0.1"
    }
  }
}
```

This file's settings do not affect the background process; they only pass into the container and build environment, and only to newly created ones. For one-off use, `docker run --env HTTP_PROXY=...` and `docker build --build-arg HTTP_PROXY=...` do the same job.

**3. The address trap.** If the proxy runs on the machine itself, do not write `http://127.0.0.1:8000` inside a container. The container has its own network namespace, and `127.0.0.1` there is the container itself. To reach the host, use the host's network address.

## Why does the desktop setting not affect the terminal?

On Ubuntu, the definition you make under **Settings > Network > Network Proxy** is written to GNOME's own settings store and affects the applications that read those values: GNOME's browser, the software centre, most desktop applications. The shell you open in the terminal does not read that store, so `curl` and `git` are unaffected. If you want to make the same setting from the command line, `gsettings set org.gnome.system.proxy mode 'manual'` and the related `host` and `port` keys are used, but the terminal side still needs the environment variable.

The practical result: if there is no desktop on the server, skip this section entirely. If you work on a desktop, you need to make both settings. For the equivalents on other operating systems, see [How to Set Up Proxy Settings in Windows and Chrome](/blog/windows-chrome-proxy-settings), [How to Set Up Proxy Settings on Mac and Safari](/blog/mac-safari-proxy-settings) and [How to Set Up Proxy Settings on an Android Phone](/blog/android-proxy-settings).

## Tool, file and undo table

| Tool | Where the setting lives | Undo |
|---|---|---|
| Shell (temporary) | `export http_proxy=…` | `unset http_proxy https_proxy no_proxy` |
| Shell (user) | `~/.bashrc`, `~/.zshrc` | Delete the line, reload with `source` |
| System wide | `/etc/environment` | Delete the line, renew the session |
| systemd service | `/etc/systemd/system/<name>.service.d/*.conf` | Delete the file, `daemon-reload` |
| apt | `/etc/apt/apt.conf.d/95proxy` | Move the file out of the directory |
| git | `git config --global http.proxy` | `git config --global --unset http.proxy` |
| pip | `~/.config/pip/pip.conf`, `--proxy` | `pip config unset global.proxy` |
| npm | `.npmrc` (`npm config set proxy`) | `npm config delete proxy` |
| Docker daemon | `/etc/docker/daemon.json` | Delete the key, restart the service |
| Docker containers | `~/.docker/config.json` | Delete the `proxies` block |
| GNOME desktop | Settings > Network > Network Proxy | Set the mode to "Disabled" |

## How do you verify that the setting works?

Four steps, in order:

1. **See the variables.** The output of `env | grep -i proxy` should show the values you expect and their uppercase twins.
2. **See the exit address.** The address returned by `curl -s https://api.ipify.org` should be the proxy's address.
3. **Watch the connection.** In the `curl -v` output, look for the `Uses proxy env variable` line and a `Trying` line going to the proxy address instead of the target. If those two lines are missing, the request is not going to the proxy at all.
4. **Test each tool separately.** The commands `sudo apt update`, `git ls-remote <address>`, `pip download --no-deps six` and `npm view express version` use their own configuration and each has to be verified on its own.

We collected the detailed steps for measurement and location verification in [Is My Proxy Working? How to Test a Proxy](/blog/how-to-test-a-proxy).

## Common mistakes

- **Forgetting `export`.** On its own, `http_proxy=...` leaves the variable in the shell only and does not pass it to the program you run.
- **Defining only `http_proxy`.** Requests going to HTTPS addresses look at the `https_proxy` variable; if it is missing they try to go direct.
- **Starting the `https_proxy` value with `https://`.** Unless your proxy terminates TLS, the value starts with `http://`.
- **Writing the password without encoding it.** The `@` inside it splits the address and the result is a `407`. We listed the other causes of `407` in [HTTP Status Codes in Web Scraping: 403, 407, 429, 503](/blog/http-status-codes-web-scraping).
- **Writing a port in the `no_proxy` list.** The entry `server:8080` matches nothing.
- **Expecting a service to follow a shell setting.** A systemd service does not read `~/.bashrc`; it needs a unit file or a fragment file.
- **Forgetting the configuration file and blaming the environment variable.** `git config --get http.proxy`, `pip config debug` and `npm config list` will reveal an old value.
- **Writing `127.0.0.1` inside a container.** The container has its own network namespace.

We covered the diagnosis of cases where the connection is never established one by one in [What Is a Proxy Error? Fix Proxy Server Not Responding](/blog/proxy-server-not-responding).

## Decision guide

| Need | Recommended setting |
|---|---|
| Sending a single command through the proxy | Put the variable in front of the command |
| Working in the terminal for a session | `export` lines |
| Permanent on the server, single user | `~/.bashrc` |
| Permanent on the server, all users | `/etc/environment` |
| Letting package updates through | `/etc/apt/apt.conf.d/95proxy` |
| git traffic to external repositories only | `http.<address>.proxy` |
| A single step in a CI workflow | `PIP_PROXY` or `npm_config_proxy` |
| A container build | `~/.docker/config.json` or `--build-arg` |
| A background service (dockerd, cron) | A systemd fragment file |
| Keeping the internal network out | `no_proxy` and `Acquire::…::DIRECT` |

## Frequently asked questions

### I made the settings but some programs still go direct, why?

Reading environment variables is a habit, not an obligation. Some tools written in Go look only for the uppercase spelling, and some Java applications expect their own `-Dhttp.proxyHost` parameter. Check the proxy heading in the program's documentation; if it has its own setting, the environment variable does not override it.

### Can I use a proxy without writing the password to a file?

Yes. If you authorise your server's exit IP address in the proxy panel, the address comes down to `http://pr.proxynet.io:8000` and no password sits in any file. On servers with a fixed IP this is the cleanest route.

### Can I use more than one proxy at the same time?

Environment variables hold a single address. You make the distinction by protocol (`http_proxy` and `https_proxy` differing) or by tool (`http.<address>.proxy` in git, a host specific line in apt). If you need a different exit address per request, a single address [Rotating Proxy](https://proxynet.io/rotating-proxy) package does that job for you.

### Do my SSH connections go through the proxy too?

No. `http_proxy` and its friends do not affect the SSH client. To route SSH through a proxy, a `ProxyCommand` line is defined in the `~/.ssh/config` file. This is also why addresses in the form `git clone git@...` ignore the `http.proxy` setting.

### Why do my cron jobs not see the proxy?

cron does not start jobs from your login shell and does not read your `~/.bashrc`. Either define the variables at the top of the crontab file or set them with `export` in the first lines of the script.

### `apt` got slower over the proxy, is that normal?

Package repositories are distributed geographically and a proxy can carry the traffic to another country. Using the proxy only for external sources and keeping local repository mirrors out with an `Acquire::http::Proxy::<host> "DIRECT";` line is usually enough.

## Summary

Proxy configuration on Linux has three layers: the shell environment, the tool's own configuration file and the service unit. Start with `export` in the terminal, use `~/.bashrc` or `/etc/environment` for persistence, then configure apt, git, pip, npm and Docker separately from their own files. Verify at every step with `curl -s https://api.ipify.org` and do not skip adding your internal network to the `no_proxy` list. For fixed, high volume work running on a server, take a look at our [data scraping solutions](/data-scraping) or go straight to our [HTTPS Proxy](https://proxynet.io/https-proxy) page.
