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.
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:
- The shell environment. Variables you define with
exportare inherited by the processes you start from that shell. They end when you close the terminal. - 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.
- 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:
http://user:pass@pr.proxynet.io:8000The 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.
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.
Temporary setup with environment variables
The fastest route is to define the variables for the current session:
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; echoIf 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, and we do not repeat it here. For the cURL side options and SOCKS5 usage, see How to Use cURL with 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:
https_proxy="http://user:pass@pr.proxynet.io:8000" curl -s https://api.ipify.orghttp_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.
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.
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 page and in SOCKS vs. HTTP Proxy: Which One Should You Choose?.
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 but ignored, with no shell expansion. So a reference like $http_proxy does not work here, you have to write the value out:
# /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.1The 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 and 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, the security policy may reject that request.
sudo -E apt updateIf 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.
// /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:
Acquire::http::Proxy::repo.yourcompany.local "DIRECT";There is one thing to watch in the file name: according to the apt.conf manual, 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:
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 # undoIf 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:
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 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:
git config --global http.proxyAuthMethod basicIf 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: command line options override environment variables, and those override the configuration file.
# 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:
[global]
proxy = http://user:pass@pr.proxynet.io:8000If 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, the HTTP_PROXY and HTTPS_PROXY environment variables are also honoured, and the default for the noproxy option is the NO_PROXY variable.
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 # undoTo 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, the setting goes into the proxies key in the /etc/docker/daemon.json file:
{
"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:
[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, 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:
{
"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, How to Set Up Proxy Settings on Mac and Safari and How to Set Up Proxy Settings on an Android Phone.
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:
- See the variables. The output of
env | grep -i proxyshould show the values you expect and their uppercase twins. - See the exit address. The address returned by
curl -s https://api.ipify.orgshould be the proxy's address. - Watch the connection. In the
curl -voutput, look for theUses proxy env variableline and aTryingline 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. - Test each tool separately. The commands
sudo apt update,git ls-remote <address>,pip download --no-deps sixandnpm view express versionuse 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.
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 thehttps_proxyvariable; if it is missing they try to go direct. - Starting the
https_proxyvalue withhttps://. Unless your proxy terminates TLS, the value starts withhttp://. - Writing the password without encoding it. The
@inside it splits the address and the result is a407. We listed the other causes of407in HTTP Status Codes in Web Scraping: 403, 407, 429, 503. - Writing a port in the
no_proxylist. The entryserver:8080matches 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 debugandnpm config listwill reveal an old value. - Writing
127.0.0.1inside 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.
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 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 or go straight to our HTTPS Proxy page.




