Exporting http_proxy from /etc/profile.d covers about half of a Rocky Linux or Fedora machine. We blocked direct web traffic on two test boxes and sent everything through a Squid proxy. Shells, curl, git and Podman worked straight away, while sudo dnf, dnf’s own cache timer and our systemd test services still tried to reach the internet directly and failed, because none of them read the shell’s environment.
This guide shows you how to set the proxy in each place that needs it, from login shells and sudo to dnf, systemd services, git and containers. It also covers the case of the variable names, the format of no_proxy and proxies that need a password. We tested every step in October 2026 on Rocky Linux 10.2 and Fedora 45, against Squid 6.10, with ports 80 and 443 blocked on the clients so a request could only succeed through the proxy. Rocky Linux is built to match RHEL, so these commands should work the same on RHEL 10 and AlmaLinux 10, although we did not test those two.
Know where each part of the system reads its proxy
There is no single system-wide proxy switch on RHEL-family systems. Each part of the system reads its proxy from its own place, and a setting in one place does nothing for the others. This is the map we confirmed on both test machines, and the rest of the guide follows it top to bottom.
| What makes the request | Where it reads the proxy | What to set |
|---|---|---|
| Your shell, curl, wget, git, Podman | Environment variables | /etc/profile.d/proxy.sh |
Anything run with sudo | Environment, after sudo strips it | env_keep in /etc/sudoers.d/ |
| dnf, including its timers | /etc/dnf/dnf.conf | proxy= |
| systemd services | The unit’s environment | A unit drop-in or DefaultEnvironment |
| Containers | Inherited from Podman’s environment | Nothing, or --http-proxy=false to stop it |
Desktop sessions are the one area we left out. GNOME keeps its own proxy setting for browsers and desktop apps, under Settings, Network, Network Proxy, so set it there on a workstation. Everything below applies to servers and to the command line on a desktop.
RHEL adds one more place. subscription-manager and its rhsmcertd service read their own proxy from the [server] section of /etc/rhsm/rhsm.conf (proxy_hostname, proxy_port, proxy_user and proxy_password), and the rhsm.conf man page says those settings override the environment variables. Registration is the first thing to break behind a proxy on RHEL, so set them before you register. Our lab ran Rocky and Fedora, so we did not test this part.
Set the proxy variables for every shell
A script in /etc/profile.d is the standard way to give every user the same proxy, because Rocky and Fedora source every .sh file there for login shells. Create the file.
sudo vi /etc/profile.d/proxy.sh
Add the variables below, with your proxy’s host and port in place of proxy.example.internal:3128 and your own internal network in no_proxy. The last line copies the values into uppercase names as well, for reasons the next section shows:
export http_proxy="http://proxy.example.internal:3128"
export https_proxy="http://proxy.example.internal:3128"
export no_proxy="localhost,127.0.0.1,::1,.example.internal,10.0.1.0/24"
export HTTP_PROXY="$http_proxy" HTTPS_PROXY="$https_proxy" NO_PROXY="$no_proxy"
The https_proxy value starts with http:// on purpose. It describes how to talk to the proxy, not the site you are visiting, and most corporate proxies accept plain HTTP and tunnel HTTPS through it with CONNECT. Log out and back in, then check that all six variables are present:
env | grep -i _proxy= | sort
You should see the lowercase and uppercase forms of each variable. If only the lowercase three appear, check the last line of the file, which is the one that creates the uppercase copies.
http_proxy=http://proxy.example.internal:3128
HTTP_PROXY=http://proxy.example.internal:3128
https_proxy=http://proxy.example.internal:3128
HTTPS_PROXY=http://proxy.example.internal:3128
no_proxy=localhost,127.0.0.1,::1,.example.internal,10.0.1.0/24
NO_PROXY=localhost,127.0.0.1,::1,.example.internal,10.0.1.0/24
Then prove a real request goes through the proxy. curl’s verbose output names the variable it used and shows the CONNECT handshake:
curl -sv -o /dev/null https://example.com/ 2>&1 | grep -E "proxy env|CONNECT|HTTP/" | head -5
A 200 Connection established from the proxy means the tunnel is up. If curl prints no “Uses proxy env variable” line, the variables are not in that shell, so open a new login session before you debug anything else:
* Uses proxy env variable no_proxy == 'localhost,127.0.0.1,::1,.example.internal,10.0.1.0/24'
* Uses proxy env variable https_proxy == 'http://proxy.example.internal:3128'
* CONNECT tunnel: HTTP/1.1 negotiated
> CONNECT example.com:443 HTTP/1.1
< HTTP/1.1 200 Connection established
Non-interactive SSH commands such as ssh host 'curl ...' picked up the same six variables on both releases. Their shell reads ~/.bashrc, which pulls in /etc/bashrc and through it the profile.d scripts. If you’d rather not use a script, /etc/environment also worked for SSH sessions on both machines. It takes plain name=value lines with no export and no variable expansion, so every value has to be written out in full, uppercase copies included. Neither file reaches systemd services, which we fix further down.
Set both lowercase and uppercase names
Tools disagree about which case they read, and the disagreement is easy to miss because the failure looks like a network problem. We set one variable at a time in an empty environment and fetched a page with each tool. The proxy was the only way out.
| Variable set | curl (both releases) | wget 1.24 (Rocky 10) | wget2 2.2 (Fedora 45) |
|---|---|---|---|
http_proxy (http:// URL) | Used | Used | Used |
HTTP_PROXY (http:// URL) | Ignored | Not tested | Not tested |
https_proxy | Used | Used | Used |
HTTPS_PROXY | Used | Ignored | Ignored |

curl ignores uppercase HTTP_PROXY deliberately, because a CGI script would receive a request’s Proxy: header under that name. Both wget versions ignore uppercase HTTPS_PROXY and failed with exit code 4. So the lowercase names are the ones every tool we tested reads, and the uppercase copies are for programs that only look for uppercase names. Set both, as the file above does, and you never have to work out which kind a given tool is.
Write a no_proxy list every tool understands
no_proxy lists the hosts that should be reached directly, such as internal web servers and the metadata service on a cloud VM. Its format is not standardised, and CIDR ranges are where tools part ways. We put 10.0.1.0/24 in no_proxy and requested a web server inside that range with three tools.
- curl matched the CIDR and connected directly, on both releases.
- wget2 on Fedora 45 matched it too.
- wget 1.24 on Rocky 10 ignored the CIDR and sent the request through the proxy.
- Python’s standard-library
urllibignored the CIDR on both releases, whilerequestsand the copy pip vendors matched it and connected directly on Rocky.
The proxy’s access log confirms the wget result. It shows a request for an internal address that should never have reached the proxy, and it came from the Rocky client.
sudo awk '$7 ~ /:8080/ {print $3, $4, $6, $7}' /var/log/squid/access.log
There is one line, from the Rocky client’s wget, for a server that should have been reached directly. Neither curl nor Fedora’s wget2 appears in the log.
10.0.1.51 TCP_MISS/200 GET http://10.0.1.50:8080/
That request still worked, because the proxy could reach the internal server, so nothing told us it had taken the long way round. On a network where the proxy cannot reach internal ranges, the same request fails and looks like an outage. Keep the CIDR in the list for the tools that support it, and add the internal hosts you care about by name or domain suffix (.example.internal matches every host under it). For scripts that use plain urllib, list the exact IP addresses as well: with no_proxy=10.0.1.50, urllib bypassed the proxy for that host as expected.
Keep the proxy variables under sudo
sudo resets the environment by default, and the proxy variables are not on its keep list on either release. Run the check as yourself and then through sudo.
env | grep -c -i _proxy=; sudo env | grep -c -i _proxy=
Your shell has all six variables and the sudo environment has none, which is why sudo curl and sudo dnf fail on a machine where the same commands work without sudo:
6
0
sudo -E keeps them for a single command, but you have to remember it every time. A sudoers drop-in fixes it for good. Edit it with visudo, which checks the syntax before saving, because a broken sudoers file can lock you out of sudo completely:
sudo visudo -f /etc/sudoers.d/proxy
Add this one line, which adds the six names to the variables sudo already keeps. If visudo reports a syntax error, choose the option to edit again rather than saving.
Defaults env_keep += "http_proxy https_proxy no_proxy HTTP_PROXY HTTPS_PROXY NO_PROXY"
Run sudo env | grep -c -i _proxy= again and it returns 6. On our Rocky box, sudo dnf makecache also started working with this drop-in alone, because dnf honours the variables when they reach it. We still set dnf’s own option in the next section, since dnf does not always run under your sudo. Keep the drop-in to machines where every sudo user is trusted with full root, because it lets a user with narrow sudo rights point root’s downloads at a proxy of their choosing.
Configure the proxy for dnf
Both releases refresh dnf metadata in the background with dnf-makecache.timer, and that service starts from systemd with no shell environment at all. With only the shell variables set, starting it by hand failed on both machines. This is Fedora’s error:
>>> Curl error (7): Could not connect to server for https://mirrors.fedoraproject.org/metalink?repo=fedora-cisco-openh264-45&arch=x86_64 [Failed to connect to mirrors.fedoraproject.org:443 after 0 ms: Could not connect to server] - https://mirrors.fedoraproject.org/metalink?repo=fedora-cisco-openh264-45&arch=x86_64
The proxy= option in /etc/dnf/dnf.conf applies to every dnf run, however it starts. On Rocky Linux, open the file.
sudo vi /etc/dnf/dnf.conf
Add the proxy line at the end of the [main] section. Ours looked like this afterwards.
[main]
gpgcheck=1
installonly_limit=3
clean_requirements_on_remove=True
best=True
skip_if_unavailable=False
proxy=http://proxy.example.internal:3128
Fedora’s dnf5 reads the same file, and its config-manager command writes the option there for you, so there is no file to edit by hand.
sudo dnf config-manager setopt proxy=http://proxy.example.internal:3128
sudo grep -rn proxy /etc/dnf/
The grep shows the line landed in /etc/dnf/dnf.conf, not in a drop-in directory. You can therefore use the same file on both releases when you add credentials later.
/etc/dnf/dnf.conf:4:proxy=http://proxy.example.internal:3128
Start the cache service again on either release and confirm its result. success means dnf reached the mirrors through the proxy without any help from your shell:
sudo systemctl start dnf-makecache.service; systemctl show -p Result --value dnf-makecache.service
Both machines returned the same single word. If you get exit-code instead, read the service’s journal, which names the mirror it could not reach.
success
Give systemd services the proxy
Services start with the environment systemd gives them, so profile.d and /etc/environment never reach them. We showed this with a small test unit that fetches a page with curl. It failed on both machines while every shell on them could use the proxy. Two fixes work, and they differ in how much they touch.
A drop-in for one service is the one we’d use in production, because only the service that needs the internet gets the proxy. Our test unit, fetch-test.service, is a oneshot service that fetches a page with curl. To reproduce the test, save it as /etc/systemd/system/fetch-test.service and run sudo systemctl daemon-reload; otherwise use your own service’s name in the commands below.
[Unit]
Description=Fetch a page through the proxy
[Service]
Type=oneshot
ExecStart=/usr/bin/curl -sS -m 10 -o /dev/null -w "%%{http_code}\n" https://example.com/
Starting it at this point fails, because the unit has no proxy. Open an override for it.
sudo systemctl edit fetch-test.service
Add the variables in a [Service] section. systemd does not read shell syntax, so each one is a separate Environment= line. Type them between the two comment markers at the top of the editor, because anything below “### Edits below this comment will be discarded” is thrown away.
[Service]
Environment="http_proxy=http://proxy.example.internal:3128"
Environment="https_proxy=http://proxy.example.internal:3128"
Environment="no_proxy=localhost,127.0.0.1,::1,.example.internal,10.0.1.0/24"
systemctl edit reloads systemd when you save. Restart the service and check its journal; our test unit went from “Failed with result ‘exit-code'” to a clean finish:
sudo systemctl start fetch-test.service; journalctl -u fetch-test.service -n 2 --no-pager -o cat | tail -2
Both lines confirm the run exited cleanly. A unit that still fails after this usually loads an EnvironmentFile= that sets the same variables, which overrides Environment=, or has a later drop-in of its own, so check systemctl cat for the full picture. These blocks set only the lowercase names, which is what curl in our test unit read; add HTTP_PROXY, HTTPS_PROXY and NO_PROXY lines with the same values if the service’s program only reads uppercase names.
fetch-test.service: Deactivated successfully.
Finished fetch-test.service - Fetch a page through the proxy.
To give every service the proxy instead, set DefaultEnvironment for the system manager. It saves you a drop-in per service, but every unit gets the variables, including ones you’d rather keep off the proxy. Create the directory and the file.
sudo mkdir -p /etc/systemd/system.conf.d
sudo vi /etc/systemd/system.conf.d/proxy.conf
Put all the variables on one DefaultEnvironment line, each in its own quotes.
[Manager]
DefaultEnvironment="http_proxy=http://proxy.example.internal:3128" "https_proxy=http://proxy.example.internal:3128" "no_proxy=localhost,127.0.0.1,::1,.example.internal,10.0.1.0/24"
Run sudo systemctl daemon-reload. On both releases that was enough for the test unit to succeed, with no reboot and no daemon-reexec, and systemctl show-environment then listed the three variables. Services that are already running keep their old environment until you restart them.
Use the proxy with git and Podman
Neither needs a config file of its own. A shallow git clone over HTTPS and podman pull docker.io/library/alpine both worked as a normal user with only the profile.d variables, and the proxy logged each connection:
sudo awk '{print $3, $4, $7}' /var/log/squid/access.log | grep -E 'github.com|docker.io' | sort -u
Both clients tunnelled to GitHub and to Docker Hub’s registry and auth endpoints. You can use the same log view to confirm any tool you are unsure about.
10.0.1.51 TCP_TUNNEL/200 auth.docker.io:443
10.0.1.51 TCP_TUNNEL/200 github.com:443
10.0.1.51 TCP_TUNNEL/200 registry-1.docker.io:443
10.0.1.52 TCP_TUNNEL/200 auth.docker.io:443
10.0.1.52 TCP_TUNNEL/200 github.com:443
10.0.1.52 TCP_TUNNEL/200 registry-1.docker.io:443
Set git config --global http.proxy only when git needs a different proxy from the rest of the system. Podman goes one step further than most people expect, because it copies the host’s proxy variables into every container it starts:
podman run --rm docker.io/library/alpine:latest env | grep -i _proxy
All six variables arrive inside the container, so package managers and downloads in it use the proxy too:
NO_PROXY=localhost,127.0.0.1,::1,.example.internal,10.0.1.0/24
http_proxy=http://proxy.example.internal:3128
https_proxy=http://proxy.example.internal:3128
HTTP_PROXY=http://proxy.example.internal:3128
HTTPS_PROXY=http://proxy.example.internal:3128
no_proxy=localhost,127.0.0.1,::1,.example.internal,10.0.1.0/24
Pass --http-proxy=false to podman run when a container must not see the proxy, and the count drops to zero. One thing that looks like a proxy failure is not one. BusyBox wget in the Alpine image failed on an HTTPS URL with “error getting response: Invalid argument”. Plain HTTP through the same proxy worked, and curl inside the same container got a 200. BusyBox wget simply cannot tunnel HTTPS through a proxy, so install curl in the image instead of debugging the proxy. The Podman and Buildah guide for Rocky Linux covers the rest of the container setup.
Authenticate to a proxy that needs a password
A proxy that wants credentials answers 407 to a request without them. curl reports it as CONNECT tunnel failed, response 407. The credentials go into the proxy URL, and any special character in the password has to be percent-encoded, because @ and : are the characters the URL uses to separate the user, password and host. We used a password of P@ss:w0rd, and sending it unencoded broke the URL itself:
curl: (5) Unsupported proxy syntax in 'http://proxyuser:P@ss:[email protected]:3129': Port number was not a decimal number between 0 and 65535
Encode @ as %40 and : as %3A. Python’s urllib.parse.quote encodes any password for you when you pass safe="", which also covers slashes that the default leaves alone.
python3 -c 'import sys,urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' 'P@ss:w0rd'
It prints the encoded form, ready to paste into the URL.
P%40ss%3Aw0rd
Put the result in your proxy variables.
export https_proxy="http://proxyuser:P%40ss%[email protected]:3129"
With the encoded URL, curl, Rocky’s wget and Python’s urllib all fetched the page on both releases. Fedora 45’s wget2 did not. It decoded the password before splitting the URL and tried to connect to a host called ss:
15.145005.111 resolving ss:80...
Failed to resolve 'ss' (Name or service not known)
With a plain password and no special characters, wget2 still failed against the authenticating proxy, and so did its --http-proxy-user and --http-proxy-password options. On Fedora we’d use curl for anything behind an authenticating proxy, or the classic wget, which Fedora still packages as wget1 and which logged in fine. dnf needs the credentials before it can download it.
dnf takes the username and password as separate options instead of inside the URL, written exactly as they are, with no encoding. Add them under the proxy= line in /etc/dnf/dnf.conf:
proxy=http://proxy.example.internal:3129
proxy_username=proxyuser
proxy_password=P@ss:w0rd
With those lines in place, dnf reached the mirrors on both releases with the shell variables removed. Now install wget1 and check that it logs in through the encoded https_proxy you exported.
sudo dnf install wget1
wget1 -q -O /dev/null https://example.com/ && echo ok
The dnf credentials come at a cost. dnf.conf is mode 644, so the password is readable by every user on the machine, and our unprivileged test user printed it with a plain cat. You can lock the file down to root, but on Fedora that stops every unprivileged dnf command, so read the differences below before you run it. sudo dnf keeps working on both releases either way.
sudo chmod 600 /etc/dnf/dnf.conf
This is how the two releases reacted to the locked file.
- Rocky 10’s dnf4 kept working for root, and unprivileged queries such as
dnf repoquerystill ran. - Fedora 45’s dnf5 refused to run for any unprivileged user, with “Unable to access configuration file”.
- Moving the credentials into a 600 drop-in under
/etc/dnf/libdnf5.conf.d/gave the same dnf5 error.

The same exposure applies to profile.d, which every user can read. On servers, we’d rather have the proxy team allow the servers by source IP, so no password needs to be stored anywhere. If credentials are unavoidable, use an account that can do nothing but reach the proxy, and on Fedora accept that unprivileged dnf queries need sudo once the file is locked down.
Check that traffic really uses the proxy
A command that succeeds has not proved it used the proxy if the machine can still reach the internet directly. We blocked outbound ports 80 and 443 on the test clients for exactly that reason. On a network that allows direct traffic, check from the proxy’s side instead, by watching its access log while you run each tool. Every request you expect should appear there with your server’s address, and an internal host should never appear.

If you run the proxy yourself on Rocky Linux 10, check its access rules before you suspect the clients. Squid’s stock squid.conf ships with http_access allow localnet commented out, so it denied our LAN clients with a 403 until we enabled that line. Our Squid on Rocky Linux guide walks through that setup, and the system-wide proxy guide for Ubuntu and Debian covers the same layers on apt-based machines.
# user name for authenticating to an http proxy, if needed
proxy_user =
should be proxy_username =
no?
Correct.
Edwin is correct; it needs to be “proxy_username=”.
Additionally, in the sentence right below the “Configure System-Wide Proxy settings on CLI” section, it says, “We will add a shell script file under /etc/profile.d/proxy.sh”. However, the command shown right afterwards is “sudo vi /etc/profile”. This causes confusion for readers when the command used references a different file than what was specified right before that.
This has been captured correctly in the article. Thanks for the insights
sorry could i ask you to fix?
Thank you so much for teaching. This article help me setup my Cent OS 7.
Thanks for the positive comment.
Hi Josphat, thanks for the article.
I have a few questions that I hope you can clarify.
On my Centos 7 / Rocky servers I have set PROXY_URL=ip_address:8080/ (without http before the ip) and I see that it works, the proxy processes the requests, so the http before the IP address is not imperative?
On Ubuntu I have verified that without http before the IP address it does not work, so you have to put it on.
Furthermore, I also see that both http and https traffic are proxed, even if I have not set the line: https_proxy=http://ip_address:8080/, is this normal behavior?
Many thanks in advance!