netsh winhttp vs Windows proxy settings: which Windows programs use which proxy?
Settings, netsh winhttp or environment variables: which proxy browsers, services, Windows Update, PowerShell, .NET, curl, pip and Go read on Windows.
Key points
- Windows has three proxy configurations, not one: the per-user Internet Options (WinINET, what Settings > Proxy edits), the WinHTTP configuration (
netsh winhttp), and environment variables. Each program reads one of them, sometimes two, and never all three. - "I set the proxy in Settings and the service still has no internet" is expected. Settings writes the signed-in user's WinINET configuration, and Microsoft's comparison table gives WinINET no services support. A service running as LocalSystem never sees your user's settings.
netsh winhttpfixes Windows Update scans and other WinHTTP callers using the default access type. It does not reach a .NETHttpClient, PowerShell 7, curl, git, npm, pip or Go, whose documented sources are environment variables or the user's settings.- Microsoft's Win32 netsh reference marks
set proxyandshow proxydeprecated in favour ofset advproxyandshow advproxy, which take JSON withProxy,ProxyBypass,AutoconfigUrlandAutoDetect, and auserormachinescope. - Windows bypass lists are semicolon separated and understand
<local>(any host name without a dot).NO_PROXYis comma separated, and<local>means nothing to it.
Windows keeps three separate proxy configurations, and each program reads only the one its HTTP stack was written for. Settings > Network & internet > Proxy (the same values as Internet Options) is the signed-in user's WinINET configuration, read by browsers, by .NET and PowerShell 7 when no environment variable is set, and by Python. netsh winhttp sets the WinHTTP configuration, read by services and system components such as Windows Update's scan. Environment variables (HTTPS_PROXY, NO_PROXY) are what curl, git, npm and Go read, and they beat the user's settings in .NET and Python. Setting one layer does nothing for programs that read another, which is why "it works in the browser" proves nothing about a service or a build tool.
| Program | Per-user Internet Options (WinINET) | netsh winhttp (WinHTTP) | Environment variables | PAC / WPAD |
|---|---|---|---|---|
| Edge, Chrome | Yes, "use system proxy settings" | No | Not a documented source | Yes, Chromium evaluates the PAC itself |
| Windows Update, scan for updates | Only as a fallback after certain errors | Yes, tried first | No | WPAD from DHCP or DNS |
| A service calling WinHTTP with the default access type | No | Yes, only this | No | Only if the code asks for autoproxy |
WinHTTP app using AUTOMATIC_PROXY (Windows 8.1 and later) | Yes | Yes ("system and per-user proxy settings") | No | Through the user's configuration |
.NET HttpClient (.NET Core 3.0 and later) | Yes, if no variable is set | Not in the documented path | Yes, checked first | Not stated by Microsoft |
.NET Framework WebRequest | Yes, unless app.config sets a proxy | Not in the documented path | Not in the documented path | Not stated by Microsoft |
| PowerShell 7.0 and later | Yes, if no variable is set (uses HttpClient.DefaultProxy) | Not in the documented path | Yes, checked first | Not stated by Microsoft |
Python urllib, requests, pip | Manual proxy and bypass only, if no variable is set | No | Yes, checked first | No |
| curl.exe, git (HTTP) | No | No | Yes, plus their own config | No |
| npm | No | No | Yes, plus .npmrc | No |
Go net/http | No | No | Yes, only this | No |
The rest of this page is what each column means, which commands set it, and how to make all three agree on a managed laptop.
The three layers#
WinINET, the per-user configuration. This is what Internet Options and the modern Settings page edit. It lives under the user's own registry hive (HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings, where Python reads ProxyEnable, ProxyServer and ProxyOverride), so every account on the machine has its own copy, including LocalSystem. It holds a manual proxy, a bypass list, a PAC URL ("Use setup script") and the "Automatically detect settings" switch that turns on WPAD. How a PAC is found and evaluated is in PAC files and WPAD.
WinHTTP, the machine configuration. WinHTTP is the HTTP stack Microsoft built for servers and services. Its static configuration is set with netsh winhttp, which Microsoft's Win32 reference says to use "anywhere you would previously have used the deprecated tool ProxyCfg.exe". A program calling WinHttpOpen with WINHTTP_ACCESS_TYPE_DEFAULT_PROXY gets that configuration, and Microsoft is explicit that this access type "does not inherit browser proxy settings". WinHTTP has its own WPAD client too, available "starting with WinHTTP version 5.1", but a program has to ask for it.
Environment variables. Windows itself attaches no meaning to HTTPS_PROXY; it is just a variable that some runtimes choose to read, set per user or per machine with setx or the System Properties dialog. One Windows quirk matters here. curl's manual notes that http_proxy "is only available in lower case" and then adds that "some systems, like Windows, do not differentiate between environment variables using different case". On Windows, HTTP_PROXY and http_proxy are the same variable, so the lowercase-only defence that curl through a proxy describes on Linux cannot be relied on.
A newer WinHTTP access type blurs the first two layers. WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, "Supported in Windows 8.1 and newer", "Uses system and per-user proxy settings (including the Internet Explorer proxy configuration)". The DEFAULT_PROXY type is documented as deprecated on Windows 8.1 and newer, but deprecated is not removed: code written against it, and services that cannot see a user profile, still read only the netsh configuration. You cannot tell from outside which access type a program uses, so the symptom is the guide: a program that follows Settings is reading WinINET or uses the automatic type; one that only follows netsh uses the default type.
Why a service ignores the proxy you set in Settings#
This is the question that sends most people to netsh, and the answer is structural rather than a bug. Microsoft's WinINet vs. WinHTTP comparison has one row that settles it:
| Feature | WinINet | WinHTTP |
|---|---|---|
| Services support. Can be run from a service or a service account | no | yes |
| Session isolation | no | yes |
| Impersonation | no | yes |
The same page says to use WinINet "unless you plan to run within a service or service-like process that requires impersonation and session isolation". So services use WinHTTP, and WinHTTP in its default mode reads the netsh configuration, not Internet Options. Settings, meanwhile, wrote your proxy into your user hive. A service running as LocalSystem, NetworkService or a dedicated account has a different hive, and never sees it.
Microsoft's guidance for a WinHTTP program that wants the browser's settings confirms the boundary: WinHttpGetIEProxyConfigForCurrentUser works only when "The application must be running as a user account", and a service must first load that user's profile with LoadUserProfile. "If loading the user's registry settings is not an option", the documented fallback is the default access type, which is the netsh configuration.
Windows Update is the textbook case. Microsoft states that "The Windows Update client requires the Windows HTTP Services (WinHTTP) to scan for available updates", and that the Automatic Updates service, running as Local System, "can only discover a proxy server" by a proxy "manually configured by using the Netsh command" or by WPAD in DNS or DHCP. For scan URLs "The system proxy is attempted (WinHTTP)" first, and only after "certain proxy, service, or authentication error codes" does it try the user proxy. For URLs not used for detection, such as reporting, the order is reversed: user proxy first, then system. Downloads go through BITS or Delivery Optimization, which are separate components again.
netsh winhttp commands#
Run these from an elevated prompt. The machine scope and the classic set proxy change the configuration for every WinHTTP caller on the box.
netsh winhttp show proxy
netsh winhttp set proxy proxy-server="proxy.corp.example:8080" bypass-list="<local>;*.corp.example;10.*"
netsh winhttp import proxy source=ie
netsh winhttp reset proxyshow proxyprints the current static setting, orDirect access (no proxy server).set proxytakes one server for HTTP and HTTPS, or per-scheme servers ("http=myproxy;https=sproxy:88"is Microsoft's own example), plus a bypass list. The Windows Server reference says the bypass list "should be separated by semicolons".import proxy source=iecopies the Internet Options proxy into WinHTTP. Microsoft's wording is "Importing settings from IE is the only available option", and there is no built-in import from other browsers. Run it as the user whose settings you want. It is a one-off copy: a later change in Settings, or a VPN client rewriting Internet Options, does not follow it into WinHTTP. The classic setting thatshow proxydisplays holds only a server and a bypass list, so a PAC URL has nowhere to go there; check the result withshow proxyandshow advproxyrather than assuming.reset proxy"Resets the WinHTTP proxy setting to DIRECT".
set advproxy replaces set proxy. The Win32 netsh reference now marks both set proxy and show proxy "deprecated. Use set advproxy instead", while the Windows Server command reference still documents both without the warning. advproxy takes JSON, a scope, and can carry a PAC URL and WPAD, which set proxy never could:
netsh winhttp set advproxy setting-scope=machine settings="{\"Proxy\":\"proxy.corp.example:8080\",\"ProxyBypass\":\"<local>;*.corp.example\",\"AutoconfigUrl\":\"http://pac.corp.example/proxy.pac\",\"AutoDetect\":false}"
netsh winhttp show advproxyThe four properties are required: Proxy and ProxyBypass (strings), AutoconfigUrl (string) and AutoDetect (boolean); use empty strings for the ones you do not need. setting-scope is user or machine, and settings-file= reads the same JSON from a file, which avoids the quoting above. Microsoft notes that "SOCKS5 isn't supported". Neither reference gives a minimum Windows build for advproxy, so check that netsh winhttp show advproxy is recognised on your oldest build before scripting it across a fleet.
.NET and PowerShell#
Microsoft's documentation is precise here, and the answers differ by runtime.
.NET Core 3.0 and later, including .NET 8, 9 and 10. HttpClient.DefaultProxy is used "if no proxy is set explicitly". On Windows it "Reads proxy configuration from environment variables or, if those are not defined, from the user's proxy settings". The variables are HTTP_PROXY, HTTPS_PROXY, ALL_PROXY and NO_PROXY. Two details catch people out. The proxy URL may be http, https, socks4, socks4a or socks5, but "cannot include any text after the hostname, IP, or port". And .NET's NO_PROXY rule is narrower than most: "Asterisks are not supported for wildcards; use a leading dot", and NO_PROXY=example.com "will not match www.example.com". That is a different rule from curl and Go, which the no_proxy environment variable compares in detail.
.NET Framework 4.x. The older WebRequest.DefaultWebProxy "reads proxy settings from the app.config file. If there is no config file, the current user's Internet options proxy settings are used." Environment variables are not mentioned, and Microsoft now marks WebRequest and WebClient obsolete for new development.
PowerShell 7.0 and later. The Invoke-WebRequest notes state: "Because of changes in .NET Core 3.1, PowerShell 7.0 and higher use the HttpClient.DefaultProxy property to determine the proxy configuration", followed by the same Windows rule: environment variables first, otherwise the user's proxy settings. -NoProxy, "added in PowerShell 6.0.0", bypasses "the proxy configured in the environment" for one call, and -Proxy with -ProxyUseDefaultCredentials sends the current user's credentials to an explicit proxy.
Windows PowerShell 5.1. The same page says environment variable support began "in PowerShell 7.0", so setting HTTPS_PROXY does not configure 5.1. Windows PowerShell runs on .NET Framework, which takes the user's Internet Options as above.
To see what a PowerShell 7 session resolves for a URL, ask the runtime rather than guessing:
[System.Net.Http.HttpClient]::DefaultProxy.GetProxy('https://www.example.com/')
[System.Net.Http.HttpClient]::DefaultProxy.IsBypassed('https://intranet/')GetProxy returns the proxy URI, or the destination itself when the request will go direct.
Developer tools on Windows#
Most of the tools that fail on a corporate Windows laptop were written for Unix and bring Unix proxy habits with them. The details for each, and the root CA half of the problem, are in corporate proxies and developer tooling; what is specific to Windows is which layer they see.
curl.exe and git. curl's documented proxy sources are -x, its config file and the environment variables (http_proxy, HTTPS_PROXY, ALL_PROXY, NO_PROXY). Internet Options and WinHTTP are not among them. Git's HTTP transport is built on curl, and its http.proxy setting is documented as an override for the proxy "normally configured using the 'http_proxy', 'https_proxy', and 'all_proxy' environment variables".
npm. The proxy and https-proxy settings in .npmrc default to null, and npm's documentation says that if HTTPS_PROXY, https_proxy, HTTP_PROXY or http_proxy is set, "proxy settings will be honored". noproxy defaults to "The value of the NO_PROXY environment variable".
Python, requests and pip. This is the one Unix-style tool that does read the Windows settings, partially. CPython's urllib.request.getproxies() on Windows "Returns settings gathered from the environment, if specified, or the registry", the registry being ProxyEnable and ProxyServer in the user's Internet Settings key. requests calls the same function, and pip vendors requests (2.34.2 in the vendor.txt on pip's main branch). Only the manual proxy is read: a PAC URL or "Automatically detect settings" leaves Python going direct. And it is all or nothing: if any proxy variable is set, the registry is not consulted at all, including its bypass list.
Go. net/http's ProxyFromEnvironment reads HTTP_PROXY, HTTPS_PROXY and NO_PROXY (either case) and nothing else, on every platform. A Go tool on Windows ignores both Internet Options and WinHTTP.
Browsers. Chromium uses the system proxy settings by default and evaluates PAC scripts with its own resolver. Its documentation describes --winhttp-proxy-resolver, which hands PAC evaluation to the Windows libraries, as "NOT a supported mode". DHCP-based WPAD "is only supported for Chrome on Windows and Chrome on Chrome OS".
If the corporate proxy wants NTLM or Kerberos, these tools need more than an address. A local proxy that authenticates as the logged-in user, compared in Cntlm or Px, solves it once for all of them; Px also reads Internet Options when no upstream proxy is named.
Bypass lists: semicolons and <local>#
The same intent has to be written twice in different grammars.
Windows (Internet Options, netsh winhttp, advproxy ProxyBypass) | NO_PROXY (curl, Go, .NET, Python) | |
|---|---|---|
| Separator | Semicolon | Comma |
| Short names | <local>: any host name with no dot in it | No equivalent; list each short name |
| Wildcards | *.corp.example, 10.* | Not portable; .NET rejects * and wants a leading dot |
| Whole domain | *.corp.example | corp.example in curl and Go, .corp.example in .NET |
<local> does not mean "localhost" or "the local network". Microsoft's WinHttpOpen reference says that with <local> "this function bypasses any host name that does not contain a period". CPython's registry parser implements the same rule (if '.' not in host), and Chromium's documentation warns that the name "can easily be confused with the concept of localhost" while the two are "completely orthogonal". So intranet bypasses, intranet.corp.example does not, and neither does 10.1.2.3, because an IP address contains periods. Internal FQDNs and private ranges need their own entries.
Pasting a Windows list into NO_PROXY produces one long entry that matches nothing. Translate it instead:
# Windows bypass list
# <local>;*.corp.example;10.*;127.0.0.1
# NO_PROXY equivalent, portable subset
setx NO_PROXY "localhost,127.0.0.1,corp.example,intranet"Ranges such as 10.* do not survive the translation portably. Check each runtime's reading of a value with the no_proxy tester, or keep ranges in a local proxy's bypass list instead.
Worked example: one laptop, all three layers agreeing#
A domain-joined laptop gets its proxy from Group Policy as a PAC URL in Internet Options: http://pac.corp.example/proxy.pac, which returns PROXY proxy.corp.example:8080 for the internet and DIRECT for *.corp.example. Edge and Chrome work. Windows Update fails to scan, a .NET 8 worker service times out, and pip install hangs.
- Windows Update.
netsh winhttp show proxysaysDirect access. There is no WPAD record, so WinHTTP has nothing. Either publish WPAD, or set the machine configuration.import proxy source=ieis the tempting fix, but Internet Options here holds a PAC URL, not a proxy address, and the classic WinHTTP setting has no field for one. On a build that hasadvproxy, set the PAC URL for the machine scope; otherwise useset proxywith the address the PAC returns and a bypass list of<local>;*.corp.example. - The .NET worker. It runs as LocalSystem, whose user settings are empty, and
netshis not in its documented path. AddHTTPS_PROXY=http://proxy.corp.example:8080andNO_PROXY=.corp.example,localhostto the service's environment (for a Windows service, theEnvironmentmulti-string value under itsHKLM\SYSTEM\CurrentControlSet\Services\<name>key), then restart it. Note the leading dot: that is what .NET needs to match subdomains. - pip. Internet Options has a PAC URL and no manual proxy, so Python's registry reader finds no enabled
ProxyServerand goes direct. SetHTTPS_PROXYfor the user, orpip config set global.proxy http://proxy.corp.example:8080. - Verify each consumer, not the browser.
netsh winhttp show proxy(orshow advproxy) for WinHTTP; the PowerShell 7GetProxycall above for .NET;python -c "import urllib.request; print(urllib.request.getproxies())"for Python;curl -v https://www.example.com/for curl, whoseUses proxy env variableline confirms which variable it took.
The PAC file itself is worth testing once with the PAC file tester, because the static address you copy into netsh and the environment is whatever the PAC returns today, and it will not follow a later change to the PAC.
Failure modes#
A service, scheduled task or agent has no internet, the browser is fine. It runs as a service account and reads WinHTTP or its own empty user settings. Configure netsh winhttp for WinHTTP callers; for .NET and PowerShell 7, set the environment of that service.
netsh winhttp import proxy source=ie reports success, nothing changes. Either the Internet Options it read held a PAC URL rather than a proxy address, or it ran as a different user (an elevated prompt opened with another administrator account) from the one whose settings you meant. show proxy and show advproxy show what WinHTTP actually holds.
pip works at the office and hangs on VPN. The VPN client switched Internet Options from a manual proxy to a PAC or WPAD, and Python reads only the manual proxy.
setx HTTPS_PROXY fixed curl and broke an internal tool. .NET and Python stop reading the user's settings, including its bypass list, as soon as a variable exists. Internal hosts that Internet Options bypassed are now sent to the proxy. Set NO_PROXY in the same change.
407 Proxy Authentication Required from WinHTTP-based services only. A service account cannot authenticate to a proxy that expects the user's NTLM or Kerberos identity. Ask for source-address rules for the machine, or see proxy authentication for what the proxy needs.
Intranet short names are proxied. The bypass list has *.corp.example but not <local>, so http://intranet/ goes to the proxy, which cannot resolve it.
Where this answer stops applying#
The consumer table is built from Microsoft's documentation for WinINET, WinHTTP, Windows Update, .NET HttpClient, WebRequest and PowerShell 7.6, from the CPython, requests, Go, npm, git and curl sources and manuals, and from Chromium's proxy documentation, all read on 13 October 2026. It was not tested on a live corporate network for this page. A program's own proxy setting (.npmrc, git config http.proxy, -Proxy, a proxy set in code) overrides every layer described here. Group Policy can set or lock both the user and the WinHTTP configuration. Third-party HTTP libraries inside a .NET or Python application may not use the defaults described above, so check the library, not just the runtime.
Frequently asked questions#
What is the difference between netsh winhttp and Internet Options proxy settings?#
Internet Options (and Settings > Proxy) is the per-user WinINET configuration used by browsers and desktop apps. netsh winhttp sets the WinHTTP configuration used by services and system components such as Windows Update's scan. They are stored separately, and changing one does not change the other.
Why does my Windows service not use the proxy I set in Settings?#
Settings writes the signed-in user's configuration, and a service runs under its own account, which never sees it. Microsoft's WinINet vs. WinHTTP comparison gives WinINET no services support. Configure WinHTTP with netsh winhttp for WinHTTP-based services, or set HTTPS_PROXY in the service's environment for .NET.
What does netsh winhttp import proxy source=ie do?#
It copies the proxy configured in Internet Options into the WinHTTP configuration. It is a one-time copy and imports from Internet Options only, so a later change in Settings is not carried over. Confirm the result with netsh winhttp show proxy, especially when Internet Options uses a PAC URL rather than a fixed proxy.
Does PowerShell use the system proxy?#
PowerShell 7.0 and later use .NET's HttpClient.DefaultProxy, which on Windows reads environment variables first and otherwise the user's proxy settings. Windows PowerShell 5.1 does not read the environment variables; that support began in PowerShell 7.0.
Does Windows Update use netsh winhttp proxy settings?#
Yes. Microsoft states the Windows Update client uses WinHTTP to scan for updates, tries the WinHTTP system proxy first for scan URLs, and falls back to the user's proxy only after certain errors. Downloads run through BITS or Delivery Optimization.
What does <local> mean in a proxy bypass list?#
It bypasses the proxy for any host name that contains no period, such as intranet. It does not cover localhost by intent, fully qualified internal names, or IP addresses.
Is netsh winhttp set proxy deprecated?#
Microsoft's Win32 netsh reference marks set proxy and show proxy deprecated in favour of set advproxy and show advproxy, which take JSON and a user or machine scope. The Windows Server command reference still documents set proxy without that warning, and it continues to work.
Primary sources#
Every normative claim on this page is checked against the specification or the vendor documentation listed here. Where behaviour is version dependent, the version is named in the text.
- Microsoft: WinINet vs. WinHTTP
- Microsoft: netsh winhttp (Windows Server command reference)
- Microsoft: Netsh.exe commands for WinHTTP (Win32)
- Microsoft: WinHttpOpen function, dwAccessType and pszProxyBypassW
- Microsoft: Setting WinInet Proxy Configurations in WinHTTP
- Microsoft: How the Windows Update client determines which proxy server to use
- Microsoft: HttpClient.DefaultProxy
- Microsoft: WebRequest.DefaultWebProxy
- Microsoft: Invoke-WebRequest (PowerShell 7.6), Notes on proxy configuration
- Chromium: Proxy support in Chrome (net/docs/proxy.md)
- CPython urllib.request: getproxies_registry and proxy_bypass_registry
- curl manual page, ENVIRONMENT
Found something wrong, or behaviour that differs on your version? Report it with the version number and a primary source. Anything substantive is fixed in the page and logged on the corrections page. See editorial standards for how pages are researched, sourced and reviewed.
More in troubleshooting proxies#
- 502 vs 503 vs 504: reading proxy errors correctly
- Access-Control-Allow-Origin multiple values: the fix
- curl through a proxy
- Header and body size limits at the proxy
- nginx 499: what it means, and its ALB, HAProxy, Envoy names
- nginx 502 after Docker restart: fix the stale IP
- nginx worker_connections: the right number for a proxy
- A proxy debugging playbook