netsh winhttp set proxy: Windows Proxy From the CLI
Use netsh winhttp set proxy, reg.exe and HTTP_PROXY to script a Windows proxy on servers and fleets, and see which layer each app reads and what auth it allows.
netsh winhttp set proxy is the command most admins reach for when a Windows server has to go through a proxy, and it is the one most often blamed when half the box still connects directly. Windows has three proxy layers that don't read each other: WinINET (the per-user Settings proxy), WinHTTP (what services and netsh winhttp control) and environment variables (what curl, Python and PowerShell 7 read first). This guide gives you a scriptable command for each layer, the import that copies one into another, what each layer can and can't do for proxy authentication, and a rollout pattern for many machines.
Which Windows Proxy Layer Does Your Program Read?
Each layer is a separate store with its own writer and its own readers. Changing one never changes the other two, with a single exception: the one-way import covered later.
| Layer | Where it lives | Command-line writer | Who reads it |
|---|---|---|---|
| WinINET | `HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings` (per user), or HKLM when per-machine policy is on | `reg add`, `Set-ItemProperty` | Settings app, Edge and Chrome on default config, Windows PowerShell 5.1, BITS jobs on default settings, Python urllib fallback |
| WinHTTP | `WinHttpSettings` value under `HKLM\...\Internet Settings\Connections` | `netsh winhttp set proxy` / `set advproxy` | Services running as SYSTEM, Windows Update scans, the `WinHttp.WinHttpRequest.5.1` COM object |
| Environment | Process environment, persisted in the user or system environment block | `set`, `setx`, `$env:`, `[Environment]::SetEnvironmentVariable` | curl, Python requests/urllib, .NET `HttpClient` (so PowerShell 7), git, npm, pip |
Two rules explain almost every "but I set the proxy" ticket.
First, the reader decides, not the admin. Microsoft's netsh winhttp reference states that the WinHTTP proxy "affects all applications including Windows services which use WinHTTP" and is independent of WinINET browser settings. A Python script never looks at WinHTTP at all.
Second, environment variables win where they're honoured. The .NET HttpClient.DefaultProxy documentation says that on Windows it "reads proxy configuration from environment variables or, if those are not defined, from the user's proxy settings." Python's urllib.request.getproxies() does the same thing: environment first, then the Windows registry's Internet Settings. So a stale HTTPS_PROXY in the system environment silently overrides the Settings proxy for every Python and PowerShell 7 process on the machine.
Windows Update sits awkwardly across two layers. Microsoft's support article on how the Windows Update client picks a proxy (updated 2026-08-25) says update scans try the system proxy (WinHTTP) first and fall back to the signed-in user's WinINET proxy on certain proxy or authentication errors, while non-scan URLs such as reporting try the user proxy first.
If you only need a one-off desktop setup with screenshots of the Settings page, our datacenter proxy setup guide for Windows covers that path. This post stays at the prompt.
Audit What a Machine Is Using Before You Change It
Read all three layers first. On a box someone else configured, at least one usually holds a value nobody remembers setting.
:: WinHTTP, both views
netsh winhttp show proxy
netsh winhttp show advproxy
:: WinINET for the current user
reg query "HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings" /v ProxyEnable
reg query "HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings" /v ProxyServer
:: Environment variables visible to this shell
set | findstr /i "_proxy"
Run both WinHTTP views, because they can disagree. On the Windows Server 2022 machine (build 20348) we used while writing this, netsh winhttp show proxy printed Direct access (no proxy server). while netsh winhttp show advproxy printed a JSON object whose "Proxy" value matched the current user's WinINET ProxyServer string, even though ProxyEnable in that same key was 0. That's one machine, not a documented rule, but the lesson holds: an address in the advproxy JSON is not proof that services are being proxied. Check the enable flags too.
The same audit in PowerShell, with machine-level environment included so you catch values a service would inherit:
$inet = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings'
Get-ItemProperty $inet | Select-Object ProxyEnable, ProxyServer, ProxyOverride, AutoConfigURL
foreach ($scope in 'Process', 'User', 'Machine') {
foreach ($name in 'HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY', 'NO_PROXY') {
$v = [Environment]::GetEnvironmentVariable($name, $scope)
if ($v) { '{0,-8} {1,-12} {2}' -f $scope, $name, ($v -replace '//[^@/]+@', '//***@') }
}
}
netsh winhttp show proxy
The -replace masks any user:pass@ so the audit output is safe to paste into a ticket.
Scraping at scale? Skip the blocks.
Fast, unblockable datacentre proxies with unlimited bandwidth.
Layer 1: WinINET From the Command Line
The Settings page writes three values. You can write them directly with reg.exe, which works from a plain Command Prompt, a login script, or a remote shell.
set KEY="HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings"
reg add %KEY% /v ProxyEnable /t REG_DWORD /d 1 /f
reg add %KEY% /v ProxyServer /t REG_SZ /d "gateway.sparkproxy.io:11000" /f
reg add %KEY% /v ProxyOverride /t REG_SZ /d "<local>;10.*;*.intranet.sparkproxy.io" /f
ProxyOverride is semicolon-separated. means "any host name without a dot", the same token netsh documents as bypassing "all short name hosts".
Watch the hive. HKCU belongs to whichever account runs the command. Push this through a remote tool that executes as SYSTEM and you've configured SYSTEM's profile, which no human user ever sees. For a setting every account shares, Microsoft's Teams Rooms deployment guide (updated 2026-06-29) documents switching WinINET to per-machine mode and writing the values under HKLM:
# Run as Administrator. Per-machine WinINET, as documented for Teams Rooms devices.
$policy = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\CurrentVersion\Internet Settings'
$inet = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings'
New-Item -Path $policy -Force | Out-Null
Set-ItemProperty -Path $policy -Name 'ProxySettingsPerUser' -Type DWord -Value 0
Set-ItemProperty -Path $inet -Name 'ProxyEnable' -Type DWord -Value 1
Set-ItemProperty -Path $inet -Name 'ProxyServer' -Type String -Value 'gateway.sparkproxy.io:11000'
The Group Policy equivalent is Computer Configuration > Administrative Templates > Windows Components > Internet Explorer > Make proxy settings per-machine (rather than per-user). Once it's on, users can't override the device proxy with their own. Microsoft's guide reboots afterwards so long-running apps pick up the change. At minimum, restart anything that was already running.
Layer 2: netsh winhttp set proxy and set advproxy
WinHTTP is the layer for services, scheduled tasks running as SYSTEM, and anything with no interactive user. Open an Administrator prompt, because the machine-wide setting is stored under HKLM.
The classic form is still the shortest:
netsh winhttp set proxy proxy-server="gateway.sparkproxy.io:11000" bypass-list="<local>;*.intranet.sparkproxy.io"
Microsoft's Win32 netsh reference (page updated 2025-04-15) marks set proxy and show proxy as deprecated in favour of set advproxy and show advproxy. Microsoft's own Windows Update proxy article still uses set proxy in its steps, so expect to meet both in the wild. On our Server 2022 build 20348 box, netsh winhttp set advproxy /? returns the full usage text, so the newer command is present there.
set advproxy takes a scope and a JSON object with four required properties: Proxy, ProxyBypass, AutoconfigUrl and AutoDetect.
netsh winhttp set advproxy setting-scope=machine settings="{\"Proxy\":\"gateway.sparkproxy.io:11000\",\"ProxyBypass\":\"<local>\",\"AutoconfigUrl\":\"\",\"AutoDetect\":false}"
Escaped quotes inside a quoted argument are fragile, and PowerShell's native-argument quoting rules make them worse. The documented settings-file= parameter avoids the problem entirely, so in scripts write the JSON to disk first:
# Run as Administrator
$json = @{
Proxy = 'gateway.sparkproxy.io:11000'
ProxyBypass = '<local>;*.intranet.sparkproxy.io'
AutoconfigUrl = ''
AutoDetect = $false
} | ConvertTo-Json -Compress
$file = Join-Path $env:TEMP 'winhttp-proxy.json'
[IO.File]::WriteAllText($file, $json) # UTF-8 without a BOM
netsh winhttp set advproxy setting-scope=machine settings-file="$file"
netsh winhttp show advproxy
Remove-Item $file
The Proxy string follows the WINHTTP_PROXY_INFO format, so per-scheme values work too, for example http=gateway.sparkproxy.io:11000;https=gateway.sparkproxy.io:11000. One hard limit from the same reference: "SOCKS5 isn't supported." SparkProxy's SOCKS5 port (13000) has to be consumed at the environment-variable layer or inside the application, not through WinHTTP.
setting-scope=user exists as well. For servers, machine is the one you want, because the service accounts doing the work don't have a user session to read from.
Copy the Settings Proxy Into WinHTTP With import
If WinINET is already correct, one command copies it across:
netsh winhttp import proxy source=ie
netsh winhttp show proxy
Microsoft documents IE as "the only available option" for the source. Three things to know before you rely on it:
- It's a snapshot. Change the Settings proxy tomorrow and WinHTTP keeps yesterday's value until you import again.
- It imports from the account running the command. Run it as the admin whose WinINET holds the proxy, not from a SYSTEM-context deployment tool, or you'll import SYSTEM's empty settings and end up with
Direct access. - The Delivery Optimization proxy guidance (updated 2026-05-21) notes the imported configuration has the same limits as
set proxy: no auto-detection, no PAC URL and no proxy authentication.
In a provisioning script, setting WinHTTP explicitly with set advproxy is more predictable than importing, because the script then doesn't depend on who happens to be logged on.
Layer 3: HTTP_PROXY, HTTPS_PROXY and NO_PROXY
Environment variables are the only layer that can carry credentials and a SOCKS scheme, and the only one that cross-platform tools read consistently.
For the current Command Prompt only:
set HTTP_PROXY=http://USERNAME:PASSWORD@gateway.sparkproxy.io:11000
set HTTPS_PROXY=http://USERNAME:PASSWORD@gateway.sparkproxy.io:11000
set NO_PROXY=localhost,127.0.0.1,.intranet.sparkproxy.io
The scheme on HTTPS_PROXY is http:// on purpose. It describes how the client talks to the proxy (a plain HTTP CONNECT), not the target site.
To persist them, setx writes to the user environment, and setx /m writes to the system environment. Microsoft's setx reference gives two gotchas worth repeating: values are "available in future command windows only, not in the current command window," and there's "a limit of 1024 characters," with longer content cropped. A long NO_PROXY list can hit that. The .NET call has no such limit:
# Run as Administrator for 'Machine'. New processes see it; running ones do not.
[Environment]::SetEnvironmentVariable('HTTPS_PROXY', 'http://gateway.sparkproxy.io:11000', 'Machine')
[Environment]::SetEnvironmentVariable('HTTP_PROXY', 'http://gateway.sparkproxy.io:11000', 'Machine')
[Environment]::SetEnvironmentVariable('NO_PROXY', 'localhost,127.0.0.1,.intranet.sparkproxy.io', 'Machine')
The machine-level example has no credentials in it deliberately. Any local user can read the system environment, so pair a machine-wide variable with IP whitelisting (next section) and keep user:pass URLs to per-process or per-user scope.
A Windows-specific curl detail we measured. curl's documentation says http_proxy is "only accepted in its lower case version because of the CGI protocol," and that accepting uppercase "has been the source for many security problems." On Windows, environment variable names are case-insensitive, so that rule doesn't bite. With curl 8.21.0 on our Server 2022 box, setting uppercase HTTP_PROXY to a dead address made curl -v print Uses proxy env variable http_proxy == 'http://127.0.0.1:9' and fail with exit code 7. Scripts written on Linux that set only uppercase names behave differently once copied to Windows.
A PowerShell detail we also measured. Microsoft's Invoke-WebRequest reference says environment-variable proxy support arrived in PowerShell 7.0. Windows PowerShell 5.1 doesn't have it. On the same machine (PowerShell 5.1.20348.5756), with HTTPS_PROXY pointing at a dead port, Invoke-WebRequest https://www.sparkproxy.io/ still returned status 200: it ignored the variable and went direct. If your fleet runs scheduled tasks under powershell.exe rather than pwsh.exe, environment variables won't proxy them. Pass -Proxy explicitly, as shown in our PowerShell scraping walkthrough.
Python reads environment variables first and falls back to WinINET. This short script prints what Python will actually use on a given host, which settles arguments faster than reading registry keys by hand:
import os
import subprocess
import urllib.request
import winreg
def wininet_proxy():
key_path = r"Software\Microsoft\Windows\CurrentVersion\Internet Settings"
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, key_path) as key:
try:
enabled, _ = winreg.QueryValueEx(key, "ProxyEnable")
server, _ = winreg.QueryValueEx(key, "ProxyServer")
except FileNotFoundError:
return None
return server if enabled else None
def masked(url):
if "@" in url:
scheme, rest = url.split("://", 1)
return scheme + "://***@" + rest.split("@", 1)[1]
return url
env_hits = {k: v for k, v in os.environ.items() if k.lower().endswith("_proxy")}
print("environment:", {k: masked(v) for k, v in env_hits.items()} or "none")
print("WinINET (HKCU):", wininet_proxy() or "disabled")
print("Python will use:", {k: masked(v) for k, v in urllib.request.getproxies().items()} or "direct")
winhttp = subprocess.run(["netsh", "winhttp", "show", "proxy"],
capture_output=True, text=True).stdout
print("WinHTTP:", " ".join(winhttp.split()))
Proxy Authentication Limits, Layer by Layer
This is where scripted setups usually break. A residential or datacenter proxy that wants a username and password returns 407 Proxy Authentication Required, and whether anything answers that challenge depends on the layer.
| Layer | Can the config hold user:pass? | What answers a 407 | Practical choice with SparkProxy |
|---|---|---|---|
| WinINET | No. `ProxyServer` holds `host:port` only | The app, by prompting or using its own credential handling | IP whitelist |
| WinHTTP (`netsh`) | No | Only an app that calls `WinHttpSetCredentials`; Windows default credentials go only to NTLM/Negotiate | IP whitelist |
| Environment | Yes, `http://user:pass@host:port` | The tool, from the URL (curl, Python, .NET) | user:pass or whitelist |
The WinHTTP row comes straight from Microsoft. The Delivery Optimization guide says netsh winhttp set proxy "offers no auto-detection of the proxy, no support for explicit PAC URLs, and no authentication to the proxy." Microsoft's "Authentication in WinHTTP" page explains why: credentials set with WinHttpSetCredentials are "only used for one request" and WinHTTP "does not cache the credentials," so each application must handle the 407 itself. The automatic logon policy that sends your Windows login "only applies to the NTLM and Negotiate authentication schemes." A commercial proxy that issues a Basic challenge gets nothing from a service that never asked for credentials.
So for WinINET and WinHTTP, the fix isn't a cleverer command. It's removing the challenge. SparkProxy supports both IP whitelist and user:pass authentication: add the server's public egress IP to the whitelist in the dashboard and the gateway stops asking. Our explainers on how proxy authentication works and IP whitelisting for proxies cover the trade-offs. Find the egress IP from the server itself:
(Invoke-RestMethod -Uri 'https://api.ipify.org?format=json' -NoProxy).ip
-NoProxy needs PowerShell 6.0 or later. On 5.1, run it before any proxy is configured.
A proxy cannot fix everything here. If the 407 persists after whitelisting, check that the server's egress IP is actually the one you whitelisted (NAT gateways and cloud load balancers often differ from the interface address). If you get a TLS error instead of a 407, the proxy isn't the cause at all: a corporate TLS-inspection appliance upstream is replacing certificates, and Microsoft notes that Delivery Optimization "won't accept a substitute certificate" for its pinned endpoints.
Push the Proxy to Many Machines at Once
For a fleet, the reliable pattern is: set WinHTTP explicitly at machine scope, set per-machine WinINET if desktop apps matter, set machine environment variables only where Python or PowerShell 7 jobs run, then verify from each host. PowerShell remoting does it in one pass:
$servers = Get-Content .\servers.txt
$proxy = 'gateway.sparkproxy.io:11000'
$bypass = '<local>;*.intranet.sparkproxy.io'
Invoke-Command -ComputerName $servers -ArgumentList $proxy, $bypass -ScriptBlock {
param($proxy, $bypass)
$json = @{ Proxy = $proxy; ProxyBypass = $bypass; AutoconfigUrl = ''; AutoDetect = $false } |
ConvertTo-Json -Compress
$file = Join-Path $env:TEMP 'winhttp-proxy.json'
[IO.File]::WriteAllText($file, $json)
netsh winhttp set advproxy setting-scope=machine settings-file="$file" | Out-Null
Remove-Item $file
[Environment]::SetEnvironmentVariable('HTTPS_PROXY', "http://$proxy", 'Machine')
[Environment]::SetEnvironmentVariable('NO_PROXY', 'localhost,127.0.0.1,.intranet.sparkproxy.io', 'Machine')
[pscustomobject]@{ Host = $env:COMPUTERNAME; WinHTTP = (netsh winhttp show advproxy | Out-String).Trim() }
} | Format-Table -AutoSize
A few decisions shape this script:
- Which port. On the SparkProxy gateway, port 11000 is HTTP/HTTPS with Random Rotation and port 11002 gives Sticky Sessions, where the exit IP holds for 5 minutes. Windows Update and package mirrors don't care which IP they come from, so 11000 is fine. A job that logs into a site and pages through results needs 11002, as explained in what a sticky session proxy is.
- Whitelisting first. Add every server's egress IP before pushing, or the whole fleet starts returning 407 at once.
- Policy versus script. The Group Policy setting above covers per-machine WinINET. For WinHTTP, run the
netshstep from a computer startup script or your configuration tool, which runs it as SYSTEM with the rights a machine-scope write needs. - Cost. SparkProxy plans are packs starting from $75/month (USD, read from sparkproxy.io on 2026-10-09) with unlimited bandwidth, so the billing unit is the plan and its concurrency, not gigabytes. A fleet pulling Windows updates through the gateway doesn't run up a traffic bill. A 24-hour free trial with no credit card is enough to test the rollout on two or three hosts first.
Prove Each Layer Exits Through the Proxy
"The command succeeded" only proves the store was written. Check each layer by making a request that uses it, and compare the returned IP with the server's direct egress IP.
:: Environment layer: curl reads the variables
curl -s https://api.ipify.org
:: Explicit proxy, bypassing whatever the environment says
curl -s -x http://gateway.sparkproxy.io:11000 https://api.ipify.org
:: SOCKS5 through the same gateway, DNS resolved at the proxy
curl -s -x socks5h://USERNAME:PASSWORD@gateway.sparkproxy.io:13000 https://api.ipify.org
For WinHTTP, use a client that reads the netsh setting. Microsoft documents that the WinHttp.WinHttpRequest.5.1 COM object's default proxy mode "should be obtained from the registry," meaning the store netsh winhttp writes, so it makes a clean probe from PowerShell:
$req = New-Object -ComObject WinHttp.WinHttpRequest.5.1
$req.Open('GET', 'https://api.ipify.org', $false)
$req.Send()
"WinHTTP exit IP: $($req.ResponseText)"
Don't use bitsadmin for this check. Microsoft's BITS reference says a job's default proxy mode uses "the proxy and proxy bypass list settings defined by each user," which is WinINET, so a BITS test from your own prompt tells you about the wrong layer.
For WinINET under Windows PowerShell 5.1, which on our test box followed the user's proxy settings and ignored the environment:
powershell.exe -NoProfile -Command "(Invoke-WebRequest https://api.ipify.org -UseBasicParsing).Content"
Run the same three checks against port 11000 a few times. With Random Rotation, the IP should change between requests. Against 11002, it should stay the same within the session window. If one layer shows your server's own IP while the others show proxy IPs, you've found the layer that wasn't set, and the audit block above tells you which store to fix.
Roll Back Without Leaving Strays
Undo all three layers, or the next admin inherits a half-proxied machine:
:: WinHTTP back to DIRECT (Administrator prompt)
netsh winhttp reset proxy
:: WinINET for the current user
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings" /v ProxyEnable /t REG_DWORD /d 0 /f
:: Environment: setx cannot delete, so use reg delete on the stored values
reg delete "HKCU\Environment" /v HTTPS_PROXY /f
reg delete "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v HTTPS_PROXY /f
Microsoft's setx reference confirms "You can't use this command to remove values added to the local or system environments," which is why the delete goes through reg. In PowerShell, [Environment]::SetEnvironmentVariable('HTTPS_PROXY', $null, 'Machine') removes it. If you enabled per-machine WinINET, set ProxyEnable to 0 under the HKLM key too and remove the ProxySettingsPerUser policy value if the machine should go back to per-user mode. Then run the audit block once more and confirm all three layers read direct.
Frequently asked questions
FAQ
No. It writes the WinHTTP store, which Microsoft describes as independent of WinINET browser settings. Browsers on default configuration follow the WinINET (Settings) proxy, so set that separately with reg add or per-machine policy.
No. Microsoft states the command offers "no authentication to the proxy," and WinHTTP does not cache credentials between requests. Use IP whitelisting for anything that relies on WinHTTP or WinINET, and keep user:pass URLs for environment variables.
curl reads its -x flag and the *_proxy environment variables, not the WinINET or WinHTTP stores. Set HTTPS_PROXY (case doesn't matter on Windows, as we measured with curl 8.21.0) or pass -x on each call.
Yes for setting-scope=machine and the classic set proxy, because the machine-wide WinHTTP setting is stored under HKLM. Run the prompt or remoting session as an administrator, and confirm afterwards with netsh winhttp show advproxy.
Microsoft's Win32 netsh reference marks set proxy and show proxy as deprecated and points to set advproxy and show advproxy. The older form is still widely used, including in Microsoft's own Windows Update troubleshooting article, but new scripts should use set advproxy with a settings file.
No. The netsh reference says "SOCKS5 isn't supported" for set advproxy. Use SOCKS5 at the environment layer instead, for example ALL_PROXY=socks5://... for .NET tools or curl -x socks5h:// against SparkProxy's port 13000.
Get 20% off your first month
Premium datacentre proxies with unlimited bandwidth. Use the code at checkout.
Save up to 15% more on quarterly, half-yearly and yearly plans
Related articles
Java Proxy Settings: Fix JVM 407 and Ignored Proxies
Java proxy settings that work: which JVM properties HttpClient reads, why Basic auth gets 407 on HTTPS, and the startup flag that fixes it, with a check each.
Sticky Session IP Changing Mid-Flow? Causes and Fixes
Sticky session IP changing mid-login or checkout? Check leaks, session ID format, the 5-minute window, parallel connections and exit churn in order.
httpx proxies Argument Removed? Fix ProxyError and 407
The httpx proxies argument is gone in 0.28. Fix the TypeError, then ProxyError 407, SOCKS ImportError and RemoteProtocolError, with verbatim errors and code.
