Ubuntu has no single switch that sends every program through a proxy. There are four separate places to set it — the desktop dialogue, the shell environment, apt, and snap — and a handful of programs that ignore all four. Here is each one, in the order that stops you chasing ghosts.
On Ubuntu a proxy is a convention rather than a kernel setting. Nothing intercepts outgoing
connections; each program looks somewhere for a proxy address and honours it if it finds one.
Command-line tools read the http_proxy and https_proxy environment
variables, GUI apps built on GLib read GNOME's own setting, apt reads its own config
files, and snap packages read a value stored in snapd. Configuring the system means writing the same
address in all four places, and knowing which programs still will not care.
Throughout, the proxy is written as a full URL with credentials, which is what every one of these mechanisms expects:
http://USERNAME:PASSWORD@HOST:PORT
If your password contains @, :, / or #, percent-encode
it — p@ss becomes p%40ss — or authentication will fail in a way that looks like
a wrong password.
Open Settings → Network → Network Proxy, switch it from Automatic to Manual and fill in the HTTP and HTTPS host and port. This is the right place to start because it takes effect immediately for GNOME apps, GNOME Software, and Chromium-based browsers, which read the desktop setting rather than the environment.
Two things about that dialogue surprise people. It has no username and password fields, so an authenticated proxy will pop up a credentials prompt per application rather than working silently. And it does not affect terminals you already have open, nor most command-line tools at all.
The same values are stored in gsettings, which is easier to script and to verify:
gsettings set org.gnome.system.proxy mode 'manual' gsettings set org.gnome.system.proxy.http host 'gw.pyproxy.com' gsettings set org.gnome.system.proxy.http port 1111 gsettings set org.gnome.system.proxy.https host 'gw.pyproxy.com' gsettings set org.gnome.system.proxy.https port 1111 gsettings set org.gnome.system.proxy ignore-hosts "['localhost','127.0.0.0/8','::1','192.168.0.0/16']" # read it back gsettings list-recursively org.gnome.system.proxy | sort
Leave the SOCKS host empty unless you genuinely have a SOCKS proxy. PyProxy residential traffic is HTTP and HTTPS only, so filling the SOCKS row in with the same gateway details produces connections that fail for no visible reason.
This is the layer that matters for real work — curl, wget,
git, pip, npm, most Python and Go programs and nearly every
container tool read these:
export http_proxy="http://USERNAME-country-us:PASSWORD@gw.pyproxy.com:1111" export https_proxy="$http_proxy" export no_proxy="localhost,127.0.0.1,::1,.local" # uppercase copies, because some tools only read those export HTTP_PROXY="$http_proxy" export HTTPS_PROXY="$https_proxy" export NO_PROXY="$no_proxy"
Note that https_proxy still starts with http://. That is not a typo: the
scheme describes how you talk to the proxy, not what the proxy fetches. HTTPS destinations travel
through a CONNECT tunnel over that same plain connection, and the TLS session inside it is
end-to-end with the target site.
Typed into a shell, those lines die with the shell. To make them permanent, pick the right file:
/etc/environmenthttp_proxy="http://USERNAME-country-us:PASSWORD@gw.pyproxy.com:1111" https_proxy="http://USERNAME-country-us:PASSWORD@gw.pyproxy.com:1111" no_proxy="localhost,127.0.0.1,::1,.local" HTTP_PROXY="http://USERNAME-country-us:PASSWORD@gw.pyproxy.com:1111" HTTPS_PROXY="http://USERNAME-country-us:PASSWORD@gw.pyproxy.com:1111" NO_PROXY="localhost,127.0.0.1,::1,.local"
/etc/environment is read by PAM at login, and it is not a shell script. There is
no export, no command substitution and no $variable expansion — write every
value out in full. Changes apply at the next login, not to the session you are in.
~/.profilePut the export lines from above in ~/.profile (or
~/.bash_profile). This version is a shell script, so $http_proxy references
work. Apply it without logging out using source ~/.profile. Note that
~/.bashrc is read by interactive shells only, so variables set there are missing from
scripts run by cron or systemd.
sudo resets the
environment, so sudo curl … and sudo pip install … go out directly. Use
sudo -E to keep the variables, or add Defaults env_keep +=
"http_proxy https_proxy no_proxy" via sudo visudo. Never put credentials in a
sudoers file that others can read.Set http_proxy without no_proxy and you will break local traffic. A request
to http://localhost:8000 is dutifully handed to the proxy, the proxy tries to resolve
"localhost" from its own side of the world, and your development server appears to be down. The
symptoms are a hanging request, a 502, or a 403 from the proxy — never anything that mentions the proxy
by name.
So always set no_proxy, and be precise about its syntax, because it is unusually
fussy:
http://localhost in no_proxy matches nothing.localhost does not cover
127.0.0.1. Include both, plus ::1 if anything on the box speaks IPv6.ignore-hosts accepts
192.168.0.0/16; curl's no_proxy does not, and matches on suffixes instead. For
a private range, list the prefix as text: 192.168., 10...internal.example.com covers
api.internal.example.com, and the dot is the portable spelling of a suffix match.169.254.169.254, or cloud-init and
the SDKs will hang at boot.Ports do not take part in matching in most clients, so localhost covers every port on
it. To bypass everything for one command, curl --noproxy '*' https://example.com.
apt is the classic "why does everything work except updates" case. It runs under
sudo — so your variables are gone — and it reads its own config tree. Create a dedicated
file rather than editing apt.conf:
sudo tee /etc/apt/apt.conf.d/95proxies >/dev/null <<'EOF' Acquire::http::Proxy "http://USERNAME-country-us:PASSWORD@gw.pyproxy.com:1111"; Acquire::https::Proxy "http://USERNAME-country-us:PASSWORD@gw.pyproxy.com:1111"; EOF sudo chmod 600 /etc/apt/apt.conf.d/95proxies
Semicolons at the end of each line are required. Files in that directory are read in name order, so
a numeric prefix keeps yours predictable. To exempt one host — a local mirror, say — add
Acquire::http::Proxy::mirror.example.com "DIRECT";. Check the effective value with:
apt-config dump | grep -i proxy
Snap packages run confined and do not inherit your shell environment either, so snapd holds its own copy:
sudo snap set system proxy.http="http://USERNAME-country-us:PASSWORD@gw.pyproxy.com:1111" sudo snap set system proxy.https="http://USERNAME-country-us:PASSWORD@gw.pyproxy.com:1111" sudo snap get system proxy
That covers snapd's own downloads. Applications shipped as snaps — Firefox and Chromium on recent Ubuntu releases among them — still keep their internal proxy preferences, which is the next problem.
Anything that ships its own HTTP stack has its own switch. This is not Ubuntu being inconsistent; it is simply what happens when a proxy is a convention. The ones that catch people out:
| Program | Where the proxy actually lives |
|---|---|
| Firefox | Settings → Network Settings. Defaults to "Use system proxy settings", but a snap build may not see yours. |
| Docker daemon | A systemd drop-in at /etc/systemd/system/docker.service.d/http-proxy.conf, then systemctl daemon-reload. Builds need ~/.docker/config.json as well. |
| Any systemd service | Environment="http_proxy=…" in a drop-in. Services never read /etc/environment or your profile. |
| Java | -Dhttp.proxyHost, -Dhttp.proxyPort, -Dhttps.proxyHost, -Dhttp.nonProxyHosts. |
| git | Honours http_proxy for HTTPS remotes, but git@… SSH remotes never use an HTTP proxy. Set git config --global http.proxy to be explicit. |
| ssh, dig, ping | Not HTTP at all. No environment variable applies; use an SSH ProxyCommand, or leave DNS and ICMP direct. |
The pattern to remember: environment variables reach HTTP clients and nothing else. UDP, ICMP and raw sockets are unaffected, so a proxy set this way is never a privacy guarantee for the whole machine.
Check the variables exist, then check that traffic really leaves through the proxy:
env | grep -i proxy curl -s https://httpbin.org/ip curl -s --noproxy '*' https://httpbin.org/ip
The two curl lines are the actual test. The first should report the proxy's exit
address, the second your own — if they match, the proxy is not being used. For the full story on which
connection was made, add -v and look for a CONNECT line:
curl -v https://example.com -o /dev/null 2>&1 | grep -iE 'connect|proxy|407'
A 407 means the address and port are right and the credentials are wrong; a hang usually
means a firewall. Finish by testing each layer on its own: sudo apt update for apt,
snap refresh --list for snap, and a GNOME app for the desktop setting.
All four layers point at one endpoint, so the work above is done once. With a rotating residential
gateway the options ride in the username — USERNAME-country-us selects the exit country,
and a session token holds one address for about thirty minutes:
curl -x http://USERNAME-country-us:PASSWORD@gw.pyproxy.com:1111 https://httpbin.org/ip
PyProxy sells residential, ISP, mobile and datacenter traffic on one balance, billed per gigabyte with no expiry, so an Ubuntu box configured once keeps working whether you use it today or next month.
Because apt runs under sudo, which strips your environment variables by default, and because apt reads its own configuration. Put the proxy in a file under /etc/apt/apt.conf.d/ using Acquire::http::Proxy and it will work regardless of the shell environment.
For every user on the machine, add the variables to /etc/environment, which is read at login by PAM. For your own account only, export them from ~/.profile. Both take effect at the next login rather than immediately, so re-login or source the file.
It lists destinations that must bypass the proxy. Without localhost, 127.0.0.1 and ::1 in no_proxy, your local development server and any local API call are sent out through the proxy and come back as a timeout or a 502, which looks like a broken application.
Anything with its own HTTP stack has its own setting: Firefox, the Docker daemon, Java, snap-confined apps and many language runtimes never read the shell environment or the GNOME dialogue. Each has to be configured on its own.