< Back

How to Configure Proxies Across Windows, macOS, and Linux: A Developer's Cross Platform Setup Guide

Tutorials

A scraper runs perfectly on a developer's MacBook, then returns nothing but connection refused errors the moment it lands on an Ubuntu build agent. Nine times out of ten the code is fine. What changed is the layer underneath it: the proxy was configured in macOS System Settings, and the Linux runner never saw it because Python's requests library reads HTTPS_PROXY from the environment and nothing else.

Proxy configuration looks trivial until you have to make it behave identically on three operating systems, inside containers, under a service account, and in CI. Every platform exposes several competing mechanisms, they resolve in different orders, and almost none of them apply to every process on the machine. This guide walks through each platform properly, then covers authentication, verification, and the mistakes that quietly cost teams days.

The Three Layers of Proxy Configuration

Before touching a single setting, it helps to understand that on every operating system there are three distinct layers, and they do not automatically talk to each other.

The system layer is what the OS itself advertises: Windows Internet Settings, the macOS network service configuration, the GNOME or KDE proxy panel. Browsers and some native apps honour it. Command line tools usually ignore it.

The environment layer is the set of HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, and NO_PROXY variables. This is the de facto standard for CLI tools, language runtimes, and containers. It is also the layer most likely to be missing in CI.

The application layer is per-tool configuration: .npmrc, pip.conf, git config http.proxy, a Selenium or Playwright launch argument, an SDK client object. Application settings almost always win over the other two.

The rule to internalise: configure the layer your workload actually reads, and configure the others only if you want browsers and installers to follow along.

Configuring Proxies on Windows

Windows is the most fragmented of the three because it has two separate HTTP stacks.

WinINET (user level, what browsers use)

The GUI path is Settings, then Network and Internet, then Proxy. Enter the host and port under manual proxy setup, add bypass entries for internal hosts, and save. This writes to the per-user registry key HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings. It affects Edge, Chrome (which inherits Windows settings by default), and any application built on WinINET. It does not affect services running under a different account.

WinHTTP (machine level, what services use)

Background services, scheduled tasks, and many .NET workloads use WinHTTP, which has its own store. Set it from an elevated prompt:

netsh winhttp set proxy proxy-server="http=gate.example.net:8000;https=gate.example.net:8000" bypass-list="localhost;127.0.0.1;*.internal"
netsh winhttp show proxy

If you already have the user settings right, netsh winhttp import proxy source=ie copies them across. Reset with netsh winhttp reset proxy.

Environment variables and PowerShell

For terminals and scripts, set the variables persistently:

setx HTTPS_PROXY "http://user:[email protected]:8000"
setx NO_PROXY "localhost,127.0.0.1,.internal"

Note that setx only affects new shells. Inside a running PowerShell session use $env:HTTPS_PROXY = "...". Also remember WSL is a separate world: distributions do not inherit Windows proxy settings, so the variables need to go into the Linux user's shell profile as well.

Configuring Proxies on macOS

macOS binds proxy settings to a network service, not to the machine. Configure Wi-Fi and your laptop loses the proxy the moment it docks to Ethernet. Always confirm which service you are editing.

The scriptable path is networksetup:

networksetup -listallnetworkservices
networksetup -setwebproxy "Wi-Fi" gate.example.net 8000
networksetup -setsecurewebproxy "Wi-Fi" gate.example.net 8000
networksetup -setproxybypassdomains "Wi-Fi" localhost 127.0.0.1 "*.internal"
networksetup -setwebproxystate "Wi-Fi" off

For SOCKS, use -setsocksfirewallproxy. To check what the system currently resolves to, scutil --proxy prints the live configuration dictionary, which is far more reliable than reading the UI.

Curl, Python, Node, and Homebrew on macOS all read environment variables rather than system settings, so add them to ~/.zshrc for interactive work. If you need a proxy for a background daemon started by launchd, put the variables in the plist under EnvironmentVariables, because login shell profiles are never sourced.

Configuring Proxies on Linux

Linux gives you the most control and the least consistency. Decide upfront whether the proxy is per user, system wide, or per service.

Shell and system wide variables

For a single user, append to ~/.bashrc or ~/.zshrc. For all users and non-interactive logins, use /etc/environment:

http_proxy="http://user:[email protected]:8000"
https_proxy="http://user:[email protected]:8000"
no_proxy="localhost,127.0.0.1,::1,.internal"

Set both lower and upper case forms. Different libraries look for different casing, and libcurl notably prefers lower case while many Python tools check upper case first.

Package managers

Package managers do not read your shell profile when invoked through sudo. APT needs Acquire::http::Proxy "http://gate.example.net:8000"; in /etc/apt/apt.conf.d/95proxies. DNF takes a proxy= line in /etc/dnf/dnf.conf. Snap uses its own snap set system proxy.http=....

systemd services and Docker

A systemd unit ignores everything except its own environment. Create a drop-in with systemctl edit myservice and add an [Service] block with Environment="HTTPS_PROXY=...". The Docker daemon is the same story: pulling images through a proxy requires a drop-in for docker.service, while containers themselves need the variables passed at build or run time.

Desktop and per command routing

GNOME keeps its own settings, reachable with gsettings set org.gnome.system.proxy mode 'manual'. For one off commands against tools that have no proxy support, proxychains4 wraps the process and forces its traffic through a SOCKS endpoint, which is useful for legacy binaries but should not be your production pattern.

Authentication and Credential Hygiene

Embedding user:pass in a URL works everywhere, which is exactly why it ends up in shell history, container image layers, and CI logs. Three habits fix most of it.

First, prefer IP whitelisting where your infrastructure has stable egress addresses, since it removes secrets from the config entirely. Second, when credentials are necessary, inject them from a secret store at runtime rather than baking them into /etc/environment or a Dockerfile. Third, URL encode special characters in passwords: an unescaped @ or # silently truncates the host and produces errors that look like DNS failures.

Also be aware that NO_PROXY matching is inconsistent across implementations. Some honour CIDR ranges, some only suffix matching, and some ignore ports. Test your bypass list rather than assuming it works.

Verifying the Setup and Catching Leaks

Never trust a config screen. Verify from the process that actually matters:

curl -s https://api.ipify.org
curl -s -x http://gate.example.net:8000 https://api.ipify.org
python -c "import requests;print(requests.get('https://api.ipify.org').text)"

If the plain call and the proxied call return the same address, your proxy is not being applied. Check DNS separately: many tools resolve hostnames locally before connecting, which leaks your real resolver even when traffic is proxied. Using socks5h:// instead of socks5:// pushes resolution to the proxy. A quick pass through a proxy tester confirms the exit IP, geolocation, and whether the endpoint is answering before you blame your code.

Common Mistakes That Waste Days

Configuring the GUI and testing in a terminal. The two layers are independent on all three platforms.

Forgetting sudo drops the environment. sudo -E preserves it, or configure the tool directly.

Assuming containers inherit the host. They do not, ever.

Leaving localhost out of the bypass list. Your local API calls start routing to an external endpoint and time out.

Mixing scheme and port. An HTTPS destination through an HTTP proxy is normal and correct: the scheme in HTTPS_PROXY describes how you reach the proxy, not the target.

Where Proxies Fit In

Cross platform configuration only pays off if the endpoint behind it behaves consistently. A setup that works on Windows and fails on Linux is usually a config problem. A setup that works everywhere but returns blocks half the time is an infrastructure problem, and no amount of netsh will fix it.

That is where pool quality matters. EnigmaProxy runs multiple pool types (residential, ISP, datacenter, and mobile) behind a consistent endpoint format, so the same HTTPS_PROXY string works identically on a developer laptop, a Docker image, and a CI runner. Teams that mix pools tend to route price monitoring and account sessions through residential proxies while keeping high volume internal fetches on datacenter IPs, and switching between them is a credential change rather than a rewrite.

The practical benefits for a cross platform stack are session control (sticky sessions where a workflow needs continuity, rotation where it needs diversity), broad geo-coverage for locale specific testing, ethically sourced residential IPs that stand up to compliance review, and pricing that scales predictably as your job count grows.

Strategic Insights: Where This Is Heading

Configuration is moving into code. Teams are abandoning machine level proxy settings in favour of declarative config: environment variables injected by the orchestrator, secrets pulled at runtime, no manual GUI steps. It reproduces cleanly and it audits cleanly.

Per process routing is replacing system wide proxies. Rather than forcing an entire host through one gateway, mature stacks assign different pools to different workloads inside the same application, using client level configuration.

Encrypted DNS complicates leak testing. With DoH enabled by default in more runtimes, resolution can bypass your proxy without any visible sign. Explicit socks5h usage and periodic leak checks will matter more, not less.

Egress identity is becoming a compliance artefact. Security teams increasingly want to know which IP ranges an application used and where those addresses came from. Documented sourcing is turning into a procurement requirement.

Conclusion

Getting proxies right across Windows, macOS, and Linux comes down to a single discipline: identify which layer your workload reads, configure that layer explicitly, and verify from the process itself rather than from a settings panel. Windows splits between WinINET and WinHTTP. macOS ties settings to network services. Linux needs the environment set for shells, systemd units, package managers, and containers separately.

Once the configuration is deterministic, the remaining variable is the network behind it. Pairing a clean cross platform setup with a provider like EnigmaProxy, which offers pool diversity and business-grade reliability, means the same three lines of config behave the same way whether they run on a laptop or a build farm.