Corporate Proxies and Windows Apps — Sorting Out Proxy Resolution in WinINET, WinHTTP, and .NET

· · Windows, Proxy, WinHTTP, WinINET, .NET, HttpClient, PAC, WPAD, Network

“The browser can open external sites, but only the business app cannot reach the external API.” “It works on the development machine, but times out on the customer network.” “It communicates when I run it by hand, and fails as soon as I turn it into a Windows service.” — When you run a business app in an environment with a corporate proxy, this kind of consultation is among the most common.

In most cases the cause is neither a proxy-server outage nor an app bug. Windows has several separate families of what people call “proxy settings”, and which settings who reads differs by app (by the HTTP stack it uses) and by running account — that mismatch. The settings the browser reads, the settings a service reads, and the settings .NET HttpClient reads can each be a different thing. Once that structure is in your head, isolating “it works in the browser, but…” becomes surprisingly fast.

This article is aimed at IT staff at small and midsize companies and at Windows app developers. It ties together, in a single picture, the three families of proxy settings — WinINET, WinHTTP, and environment variables — PAC and WPAD autoconfiguration, the difference in proxy resolution between .NET Framework and .NET (Core and later), authenticating proxies (407), TLS inspection, and the practical isolation procedure. HttpClient creation patterns and timeout design itself are covered in “Don’t Wrap HttpClient in a using Block”, so this article concentrates on proxy resolution.

1. The Bottom Line First

  • Windows proxy settings are not one thing; there are at least three families. (1) WinINET per-user settings (the “Proxy” page in the Settings app = the old Internet Options), (2) WinHTTP machine settings (netsh winhttp), and (3) the HTTP_PROXY / HTTPS_PROXY environment variables. Which one is read is decided on the app side.12
  • The “Proxy” you see in the Settings app is WinINET’s per-user settings. Browsers and interactive apps read them; Windows services do not. WinINET is not supported for use in a service; service use is WinHTTP’s job.13
  • The most common cause of “it works by hand but not as a service” is a difference of running account. LocalSystem and a service account cannot see the per-user proxy an administrator configured on their own screen.34
  • netsh winhttp set proxy is a static setting; it does not handle PAC, automatic detection, or proxy authentication. If you want to configure PAC or WPAD per machine, you need the netsh winhttp set advproxy side.42
  • PAC results change per URL. The PAC file’s FindProxyForURL function takes a URL and a host and returns a list of proxies or a direct connection (DIRECT). “That site works, but only this API does not” can be a PAC branch.56
  • HttpClient on .NET (Core and later) initializes the default proxy in the order environment variables → Windows user proxy settings. If any of HTTP_PROXY, HTTPS_PROXY, or ALL_PROXY is defined, it takes priority over the OS settings, so the accident of “someone left an environment variable behind” can happen.7
  • .NET Framework’s default is the running account’s Internet Options, and you can override it with defaultProxy in app.config. Configuration-file settings take priority over system settings.89
  • 407 is a proxy-authentication error; it is a different thing from 401 (server authentication). Schemes include Negotiate, NTLM, and Basic, and in .NET you pass credentials with DefaultProxyCredentials or WebProxy.UseDefaultCredentials. Watch the fact that under a service account the contents of the “default credentials” change.101112
  • A TLS-inspection proxy only holds together as a set with distribution of the internal CA certificate. Machines and runtimes that have not received it get a certificate-validation error. Resolve it by distributing to the certificate store, not by disabling validation in the app.134

In one sentence: whenever you say “I checked the proxy settings”, always be able to say which of the three families you checked, and from which account — that is this article’s subject.

2. Windows Has Three Families of “Proxy Settings”

First, the overall map. The paths a Windows app uses to find a corporate proxy fall into these three families.

Settings family Where you set it / the command Scope What mainly reads it
(1) WinINET (Internet Options) Settings → Network & internet → Proxy, inetcpl.cpl Per user (default) Browsers, interactive desktop apps, .NET Framework default
(2) WinHTTP (machine settings) netsh winhttp set proxy / set advproxy Machine Windows services, some OS components
(3) Environment variables HTTP_PROXY / HTTPS_PROXY / ALL_PROXY / NO_PROXY Process (inherited depending on where they were defined) HttpClient on .NET (Core and later), curl, cross-platform tools such as Node.js and Python

(1) is what people generally recognize as “the Windows proxy settings”; the substance is WinINET configuration. Historically it is Internet Explorer’s Internet Options, and by default it is stored per user.4

(2) is the per-machine default for contexts such as a service where “there is no signed-in user”. (3) is mainly the convention of tools that come from the cross-platform world; on Windows, .NET (Core and later) and curl and the like also read it.7

The important point is that which family is read is decided on the app side, not on the settings side. If the app uses WinINET internally it reads (1); if WinHTTP, (2) (or an app-specific override); if .NET (Core and later), (3) then (1). So it is usually not “the proxy settings are correct but it still cannot connect”; the reality is “the family the app is reading was a different family from the one you checked”.

Three families of Windows proxy settingsWinINET is per-user Settings and Internet Options, WinHTTP is the machine default via netsh, and environment variables are process-scoped. Which family is read is decided by the app, not by the settings sideWhich family?WinINET per-user settingsWinHTTP machine settingsHTTP_PROXY and friendsBrowsers and desktop appsServices and some OS parts.NET Core+ and curl

Figure 1: Three families sit side by side. The app chooses which one it reads.

If you enable the Group Policy “Make proxy settings per-machine (rather than per-user)”, you can switch (1) to per-machine and apply the same settings to every user. With MDM (Intune and similar) you can configure it per device with the NetworkProxy CSP.4

3. WinINET and WinHTTP — For Interactive Apps and for Services

3.1. The Difference in Roles

WinINET and WinHTTP are both Windows inbox HTTP client stacks, but they assume different uses.

  • WinINET: Aimed at interactive desktop apps. It automatically inherits the user’s Internet Options (proxy, cookies, credential cache) and can even show a credential-entry UI if needed. Use in a service or a service-like process is not supported.1
  • WinHTTP: Aimed at services and the server side. It supports running under a service account, thread impersonation, and session isolation; in exchange it does not share the user’s browser settings, cookies, or credentials. It also shows no UI.3

Microsoft’s own guidance is equally clear: “use WinINET unless you are running inside a service, or in a service-like process that needs session isolation and impersonation” — put the other way, if it is a service, use WinHTTP.1

WinINET for interactive apps, WinHTTP for servicesWinINET inherits the signed-in user's Internet Options and is not supported in a service. WinHTTP runs under a service account with no UI and does not share the user's browser settingsYesService or service-likeInteractive desktop app?WinINETWinHTTPReads the user's Internet OptionsMachine settings, no UI

Figure 2: Interactive apps use WinINET. A service uses WinHTTP.

3.2. Basic netsh winhttp Operations

WinHTTP’s machine-default proxy is operated with netsh.2

:: Display the current WinHTTP proxy settings
netsh winhttp show proxy

:: Set a static proxy (with a bypass list)
netsh winhttp set proxy proxy-server="proxy.example.co.jp:8080" bypass-list="*.example.co.jp;<local>"

:: Import the Internet Options (WinINET) settings
netsh winhttp import proxy source=ie

:: Return to the default (DIRECT)
netsh winhttp reset proxy

Two constraints to keep in mind here.

  1. netsh winhttp set proxy is a static setting. It handles neither proxy auto-detection, nor specifying a PAC URL, nor proxy authentication.4
  2. import proxy source=ie copies the static settings at that moment only; it does not follow later changes on the Internet Options side. When you need a per-machine configuration that includes PAC or auto-detection, configure the JSON-form detailed settings (Proxy, ProxyBypass, AutoconfigUrl, AutoDetect) with netsh winhttp set advproxy.2

3.3. The Most Common Pitfall: A Service Does Not Read the User’s IE Settings

The pattern you see most often on site, in time order, looks like this.

  1. A developer runs the tool on their own PC → their per-user proxy settings (1) take effect and it works
  2. In production it is left resident as a Windows service (How to Build and Operate Windows Services) under LocalSystem
  3. The settings visible from LocalSystem are a different thing (per-user settings are invisible, and the WinHTTP machine settings are unconfigured = DIRECT) → it tries a direct connection to the external API and times out

It is not “it does not work even though it is the same machine”; even on the same machine, a different running account means a different set of proxy settings is visible. For a process that communicates even when no user is signed in, the correct approach is to prepare per-machine settings in the form that process’s HTTP stack actually reads. For a native app or Windows component that uses WinHTTP, the netsh WinHTTP settings take effect.4 HttpClient on .NET (Core and later), on the other hand, does not read WinHTTP’s machine settings (see Chapter 5), so for a .NET service you set a system environment variable (HTTPS_PROXY and similar) or specify HttpClientHandler.Proxy explicitly from app settings.

The accident also happens in the other direction. If you bake a static proxy into a laptop that moves between the corporate network and the outside with netsh winhttp set proxy, that proxy is unreachable outside the company and communication dies completely. Treat a machine-static setting as a means aimed at servers whose network configuration does not change.4

Why a service does not see the user's IE settingsA developer run reads per-user WinINET settings and works. As LocalSystem those settings are invisible. A native WinHTTP app then follows unconfigured machine settings (DIRECT). A .NET Core+ service still uses environment variables or an explicit handler.Proxy and does not switch to netsh winhttpWinHTTP.NET Core+Run by hand as the userWinINET per-user settings applyWindows service as LocalSystemPer-user settings are invisibleWhich HTTP stack?WinHTTP unconfigured = DIRECTEnv vars or handler.ProxyExternal API times out

Figure 3: The same machine, a different account, a different set of visible proxy settings.

4. PAC and WPAD — What “Automatic Configuration” Actually Is

4.1. PAC Files and FindProxyForURL

A PAC (Proxy Auto-Configuration) file is JavaScript (ECMAScript) that computes “which proxy to use for this URL”, and it always contains a function named FindProxyForURL(url, host). The function returns a list of proxies that should be used, or a special return value (DIRECT) meaning it is fine to connect directly without a proxy.5

function FindProxyForURL(url, host) {
    // Internal domains and private addresses go direct
    if (dnsDomainIs(host, ".example.co.jp") ||
        isInNet(host, "10.0.0.0", "255.0.0.0")) {
        return "DIRECT";
    }
    // Everything else goes through a proxy. Fall back to the next if the first is unavailable
    return "PROXY proxy1.example.co.jp:8080; PROXY proxy2.example.co.jp:8080; DIRECT";
}

Two practical consequences follow.

  • Proxy resolution has to be done per URL. Because PAC can return a different proxy or a direct connection depending on the URL (host), WinHTTP’s automatic-proxy feature is also designed to pass the request URL and query each time.6 “The browser can see a different site” is not proof that the problem API takes the same path.
  • DIRECT is an instruction to “go without a proxy”. If traffic that should be internal never appears in the proxy log, first suspect that PAC returned DIRECT (or that it matched a bypass list).

4.2. Automatic Detection via WPAD

Turn on “Automatically detect settings” and the machine looks for the PAC file’s location with the WPAD (Web Proxy Auto-Discovery) protocol. In a typical configuration, DHCP hands out a PAC URL, or DNS is used to look up a host named wpad and the PAC is downloaded from a URL such as http://wpad/wpad.dat.14

In other words, “automatic detection” is not magic; it is a mechanism that only works on a network that has already set up a WPAD arrangement in DHCP/DNS. Turning on automatic detection alone on a network with no such arrangement only adds wait time for a detection failure.

PAC resolves a proxy per URL, WPAD only finds the PACFindProxyForURL takes a URL and host and returns a proxy list or DIRECT. WPAD only locates the PAC via DHCP or DNS. A client that cannot evaluate PAC falls back to a static proxy or environment variablesproxy listDIRECTRequest URLFindProxyForURLGo through a proxyConnect without a proxyWPAD via DHCP or DNSClient cannot evaluate PACStatic settings or env vars

Figure 4: PAC decides per URL. WPAD only finds the PAC file.

4.3. How Clients That Cannot Evaluate PAC Behave

Not every client can evaluate PAC.

  • The static settings of netsh winhttp set proxy do not evaluate PAC.4
  • Tools that use the HTTP_PROXY environment-variable style can, as a rule, only write a fixed proxy URL (there is nowhere to write a PAC URL).7
  • For a native app that uses WinHTTP directly, it depends on how the session is opened. An app opened with WinHttpOpen on Windows 8.1 and later specifying WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY has WinHTTP automatically resolve the system/user proxy settings (including WPAD/PAC) per request.15 If it is opened with the older WINHTTP_ACCESS_TYPE_DEFAULT_PROXY (deprecated from 8.1 onward) or similar, automatic proxy is not integrated into the HTTP stack, and the app has to call WinHttpGetProxyForUrl itself and apply the result to the request. In other words, on an older implementation, PAC can be present and still unused.5

“The browser goes to the right proxy via PAC, but the business app does not read PAC and tries a direct connection and fails” — this is another staple mismatch. On a PAC-operated network you need to decide a fallback — static settings or environment variables — for clients that cannot read PAC.

5. .NET Proxy Resolution — Framework and Core and Later Are Different Things

Which proxy settings a .NET app reads differs by default between .NET Framework and .NET (Core and later). Confuse the two and you will investigate a .NET 8 app with Framework-era knowledge and miss.

5.1. .NET Framework — Default Is Internet Options, Overridden with defaultProxy

On .NET Framework, HttpWebRequest and the HttpClient that sits on top of it use the default proxy unless you specify Proxy explicitly. The default proxy is decided by a combination of the system’s Internet settings (the running account’s WinINET settings) and the configuration file, and the configuration-file settings take priority.8

You can control this default with the system.net/defaultProxy element in app.config (or machine.config).9

<configuration>
  <system.net>
    <!-- useDefaultCredentials: whether to send default credentials to an authenticating proxy -->
    <defaultProxy enabled="true" useDefaultCredentials="true">
      <proxy usesystemdefault="true"
             proxyaddress="http://proxy.example.co.jp:8080"
             bypassonlocal="true" />
      <bypasslist>
        <add address="[a-z]+\.example\.co\.jp$" />
      </bypasslist>
    </defaultProxy>
  </system.net>
</configuration>

Leave the defaultProxy element empty and the system (Internet Options) settings are used; write proxyaddress and similar and those take priority. From the program you can replace the same default with WebRequest.DefaultWebProxy.98

The Chapter 3.3 pitfall applies here too. Because the default is “the running account’s Internet Options”, a .NET Framework app running under a service account reads a different (usually empty) set of settings from the ones visible on the administrator’s desktop.

5.2. .NET (Core and Later) — Environment Variables First, Then the OS User Settings

HttpClient on .NET (Core and later) has a static property HttpClient.DefaultProxy. Unless a handler specifies a proxy explicitly, every HttpClient instance uses it. The initialization rule on Windows is “read environment variables, and if they are not defined, read the user proxy settings”.7

The environment variables used are as follows.7

Environment variable Meaning
HTTP_PROXY Proxy used for HTTP requests
HTTPS_PROXY Proxy used for HTTPS requests
ALL_PROXY Fallback when the above are undefined
NO_PROXY Comma-separated list of hosts that should not use a proxy

Three things to watch.

  • If any of HTTP_PROXY, HTTPS_PROXY, or ALL_PROXY is defined, it takes priority over the OS-side proxy settings. Defining only NO_PROXY does not configure a proxy from environment variables, and on Windows the OS user proxy settings continue to be used. “Invisible settings” such as leaving HTTPS_PROXY as a system environment variable after an old experiment, or a CI/CD template injecting it, are a breeding ground for accidents.
  • NO_PROXY does not support wildcards (*). To match a subdomain, put a leading dot (.example.com matches www.example.com but not example.com itself).7
  • On non-Windows (Linux containers and similar), if the environment variables are undefined it is initialized with no proxy. The default behaviour of the same app changing between Windows and Linux is something to confirm at container-migration time.7

5.3. Explicit Specification — HttpClientHandler.Proxy and UseProxy

On either runtime, the highest priority is an explicit specification on the handler. Specifying HttpClientHandler.Proxy takes priority over OS settings and the configuration file, and UseProxy = false uses no proxy at all.14

using System.Net;

// Use a proxy read from app settings explicitly
var handler = new HttpClientHandler
{
    Proxy = new WebProxy("http://proxy.example.co.jp:8080")
    {
        BypassProxyOnLocal = true,
        BypassList = new[] { @"^intra\.example\.co\.jp$" },
        UseDefaultCredentials = true // On an authenticating proxy, respond with the running account's credentials
    },
    UseProxy = true
};
var client = new HttpClient(handler);

// A client that never uses a proxy (for direct internal APIs)
var directHandler = new HttpClientHandler { UseProxy = false };
var directClient = new HttpClient(directHandler);

When there is no explicit specification and the OS settings are followed, automatic bypass of local destinations has rules. A flat name with no dot, a loopback address, a destination that matches the machine’s own domain suffix, and similar can be treated as “local”.14 Phenomena such as “the behaviour changes if I specify an IP address” or “it suddenly started going through the proxy when I used an FQDN” can be caused by this judgement.

The priority order is as follows.

Priority (high → low) .NET Framework .NET (Core and later)
1 Explicit specification such as HttpClientHandler.Proxy Same
2 defaultProxy in app.config Assignment to HttpClient.DefaultProxy
3 The running account’s Internet Options Environment variables (HTTP_PROXY and others)
4 Windows user proxy settings
Default proxy resolution in Framework versus Core and laterAn explicit HttpClientHandler.Proxy always wins. Framework then uses app.config defaultProxy and the running account's Internet Options. Core and later uses an assignment to HttpClient.DefaultProxy, then environment variables, then Windows user proxy settingsFrameworkCore and laterExplicit handler.ProxyThat proxy is usedNo explicit ProxyWhich runtime?app.config defaultProxyRunning account Internet OptionsHttpClient.DefaultProxyHTTP_PROXY and friendsWindows user proxy settings

Figure 5: Explicit specification always wins. The default path differs by runtime.

6. Authenticating Proxies — 407 Is the Proxy’s Authentication Error

6.1. Do Not Confuse 407 with 401

When you try to go through a proxy that demands authentication, the proxy returns status code 407 (Proxy Authentication Required) and a Proxy-Authenticate header listing the available schemes. That is a different thing from the destination server’s authentication demand (401 and WWW-Authenticate); the party you hand credentials to and the place you configure them are both different.10

407 is the proxy, 401 is the destination server407 and Proxy-Authenticate come from the proxy. 401 and WWW-Authenticate come from the destination server. The credentials and the place you configure them differThe proxyThe destinationOutbound requestWho demands auth?407 + Proxy-Authenticate401 + WWW-AuthenticateDefaultProxyCredentials

Figure 6: 407 is proxy authentication. 401 is server authentication.

Schemes include Basic, which sends the username and password as-is, and challenge/response schemes such as Negotiate (Kerberos/NTLM). In a challenge/response scheme the password itself does not travel the network, and authentication completes over several exchanges.10 The mechanism of which scheme it “falls back” to is covered in more detail in “NTLM and Kerberos Explained with Diagrams”.

6.2. How to Pass Credentials in .NET

When you want to use the default proxy that comes from the OS settings and only get authentication through, use HttpClientHandler.DefaultProxyCredentials. These are the credentials sent to that default proxy when UseProxy = true and Proxy = null (= the system-default proxy).11

using System.Net;

var handler = new HttpClientHandler
{
    UseProxy = true,   // The default. Combined with a null Proxy, this uses the system-default proxy
    Proxy = null,
    // Respond to 407 with the credentials of the running account (signed-in user or service account)
    DefaultProxyCredentials = CredentialCache.DefaultCredentials
};
var client = new HttpClient(handler);

When you specify the proxy explicitly, put the credentials on the WebProxy side. In many client scenarios the recommendation is to use the signed-in user’s default credentials rather than an individual username and password, and WebProxy.UseDefaultCredentials = true is that.12

6.3. The Service-Account 407 Problem

The running account matters here too. “Default credentials” means the credentials of the account that is running that process. Run it as an interactive user and authentication to the proxy is as that user; run it as a LocalSystem service and it is as the computer account.

  • If the proxy is authenticating users through Active Directory, it cannot authenticate a computer account or a local account, and 407 continues as soon as you turn the app into a service
  • Conversely, some environments have an authentication exemption on the proxy side for services (by source IP or by account)

So a 407 investigation does not close on “the app’s settings” alone; it is a set with a design check on the infrastructure side: can the proxy authenticate the running account. For an app you will turn into a service, you should decide at design time one of: run it under a domain service account (gMSA and similar), put an authentication exemption on the proxy side, or stand up an internal relay proxy that does not require authentication.

There is also a style that embeds credentials in an environment variable, as in HTTP_PROXY=http://user:pass@proxy:80807, but a cleartext password is then exposed in an environment variable (= process information), so it is not recommended for standing operation.

7. HTTPS and Proxies — CONNECT Tunnels and TLS Inspection

7.1. HTTPS Goes Through a Proxy as a “Tunnel”

When you use a proxy for HTTPS, the client first sends the proxy a CONNECT destination-host:443 request, and the proxy opens a TCP tunnel. On success the proxy returns 200, and after that the client and the destination server perform the TLS handshake inside that tunnel. If the tunnel does not open, the proxy returns 407 (authentication required), 502, or similar.16

In this model the proxy cannot read the contents of the tunnel (encrypted HTTPS). What remains in the proxy log is the destination host name and whether the connection succeeded; the URL path is not visible — that is the behaviour of a “pass-through” proxy.

HTTPS through a proxy is a CONNECT tunnelThe client sends CONNECT to the proxy, the proxy opens a TCP tunnel and returns 200, then the client and destination perform the TLS handshake inside the tunnel. The proxy log sees the host, not the URL pathCONNECT host:443200 and a TCP tunnelTLS inside the tunnelClientProxyDestinationLog: host and success only

Figure 7: A pass-through proxy sees the host, not the encrypted path.

7.2. TLS-Inspection Proxies and Certificate Errors

Security-product proxies, on the other hand, include a TLS-inspection (SSL decryption, break and inspect) type that terminates TLS, inspects the contents, and re-encrypts before forwarding. In this scheme the server certificate presented to the client is not the real one; it is replaced by a certificate re-signed by the proxy’s own CA.13

The premise that makes this configuration hold is therefore “the proxy’s CA certificate has been distributed to every client’s trusted roots”. On a machine that has not received it, or in a runtime that does not look at the Windows certificate store (tools with their own trust store), you get a certificate-validation error. In .NET it typically surfaces as an HttpRequestException wrapping an AuthenticationException (a message of the “the remote certificate is invalid” kind).

The principles of the fix are as follows.

  • Distribute the internal CA certificate to the local computer’s “Trusted Root Certification Authorities” store. The split between the user store and the computer store is covered in “The Windows Certificate Store in Practice”.
  • Do not disable certificate validation in code. A workaround that always returns true from ServerCertificateCustomValidationCallback becomes a vulnerable app that cannot detect a man-in-the-middle the moment it goes onto an outside network.
  • Certificate-pinned traffic cannot be inspected in the first place. Connections that verify a specific Microsoft certificate, as some Windows components do, fail the moment the proxy swaps the certificate, and there is no workaround other than an exclusion.4 For traffic destined for SaaS such as Microsoft 365, Microsoft itself recommends excluding it from network-layer decryption and inspection.13

A symptom of “every internal site is visible, but only a particular cloud service produces a certificate error in the app” should first make you suspect the combination of the TLS-inspection exclusion list and pinning.

A TLS-inspection proxy re-signs the certificateThe proxy terminates TLS, inspects the contents, and presents a certificate re-signed by its own CA. Validation holds only if that CA is in the trusted roots. Do not disable validation in codeCA in trusted rootsCA missingReal server certificateTLS-inspection proxyRe-signed by the proxy CAClient validationSucceedsCertificate errorDistribute the CA to the store

Figure 8: Inspection works only as a set with distributing the internal CA.

8. The Isolation Procedure — Five Steps to Identify the Culprit

Investigate “cannot connect” mechanically in this order.

Step What you do What you learn
(1) Reproduce Access the problem URL with curl.exe -v or Invoke-WebRequest (preferably on the same machine, under the same account) Whether it is an app-specific problem or an environment problem
(2) Collect settings Collect the three families: netsh winhttp show proxy, the per-user settings, and environment variables What is in which family
(3) Identify the account Identify the target app’s running account (a service, Task Scheduler, another user) Which settings and which credentials it is running under
(4) Classify the error Distinguish 407 / 403 / name-resolution failure / timeout / certificate error Isolate proxy authentication, policy denial, path, and TLS inspection
(5) Proxy log Check the matching time in the proxy server’s access log Whether it reached the proxy at all, and who it authenticated as

You can collect (2) in one go with PowerShell.

# (1) Per-user (WinINET) settings — note that this reads HKCU of the running account
Get-ItemProperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings' |
    Select-Object ProxyEnable, ProxyServer, ProxyOverride, AutoConfigURL

# (2) Machine (WinHTTP) settings
netsh winhttp show proxy

# (3) Environment variables
Get-ChildItem env: | Where-Object Name -match 'proxy'

A few practical tips.

  • In the reproduction test in (1), be aware of which settings family the tool reads. Windows-inbox curl.exe can specify a proxy explicitly with -x http://proxy:8080, and for TLS validation it normally uses the OS certificate store (Schannel). Windows PowerShell 5.1’s Invoke-WebRequest follows the .NET Framework side (Internet Options by default); PowerShell 7 follows the .NET side (environment variables first). “curl works but the app does not” is itself a hint of a mismatch among configuration families.
  • If the target in (3) is a service, re-check (1) and (2) under the same account as the service. A check in the administrator’s own session is not proof of what LocalSystem sees.
  • In the error classification in (4), take Chapter 6 (authentication) as the first candidate for 407, Chapter 7 (TLS inspection) for a certificate error, and “it is not reaching the proxy” (path, name resolution, firewall) for a timeout. The pattern where the cause is a Windows Firewall inbound rule rather than the proxy is covered in “The Windows Firewall and Business Applications”.
  • If you get as far as (5) and there is still no trace in the proxy log, the traffic never reached the proxy. Suspect PAC’s DIRECT decision, a bypass list, or a leftover environment variable, and if needed confirm the actual destination with a packet capture (“Packet Capture on Windows in Practice — Choosing Among pktmon, netsh trace, and Wireshark”).
Five steps to isolate a proxy failureReproduce under the same account, collect the three families of settings, identify the running account, classify the error, then check the proxy logReproduce with curlCollect three familiesIdentify the accountClassify the errorCheck the proxy log407: authenticationCert error: inspectionTimeout: never reached

Figure 9: Walk the five steps in order. The error class picks the next chapter.

9. A Design Recommendation — Make the App One You Can “Configure the Proxy On”

Turn the investigation procedure around and it becomes a design guideline on the app side. For a Windows app you will deliver into an environment with a corporate proxy, the following is recommended.

  1. Make the proxy configurable from app settings. The default is “follow the OS settings”. In most environments the default is enough; only in the exceptional environments — PAC cannot be read, it runs as a service, a special proxy configuration — do you make it possible to specify a proxy URL, a bypass list, and “do not use a proxy” from a settings file. HttpClientHandler.Proxy / UseProxy in Section 5.3 is the implementation point.14
  2. Write down how internal destinations (APIs, databases, license servers, and similar) are treated as proxy exceptions. Put in a form you can write into the deployment procedure whether they are excluded by PAC DIRECT, a bypass list, or NO_PROXY. NO_PROXY matching rules (no wildcards, what a leading dot means) are widely misunderstood, so attach examples.7
  3. Design timeouts and retries on the assumption of going through a proxy. If the proxy is down or stuck on authentication, an implementation that waits on a long default timeout freezes both the UI and operations. Separate a shorter connect timeout, and limit retries to idempotent requests (design details are in “Don’t Wrap HttpClient in a using Block”).
  4. Log “which proxy was used”. Make the app itself able to answer the first question of a failure investigation.

A log as in (4) is already effective if it only records the resolution result. The point is to derive the path from the settings (the handler) you actually used to configure the client. If you log HttpClient.DefaultProxy directly, you will record a value that disagrees with the actual path when the handler specifies Proxy explicitly or sets UseProxy = false.

using System.Net.Http;

// handler is the same instance used to create the HttpClient
// UseProxy=false is always direct. An explicit specification wins; otherwise DefaultProxy is used
var effectiveProxy = handler.UseProxy
    ? handler.Proxy ?? HttpClient.DefaultProxy
    : null;
var target = new Uri("https://api.example.com/v1/orders");
var route = effectiveProxy is null || effectiveProxy.IsBypassed(target)
    ? "DIRECT"
    : effectiveProxy.GetProxy(target)?.ToString() ?? "DIRECT";
logger.LogInformation("HTTP send {Target} route {Route} account {User}",
    target, route, Environment.UserName);

If at startup you once record “path” and “running account” for the main destinations, steps (1) through (3) of Chapter 8 finish just by reading the log. When you are told “it works in the browser, but…”, being able to say from the app side “I used this setting, and this path” is the condition of an app that is strong against proxy trouble.

Make the proxy configurable and log the pathDefault to following the OS settings, allow an explicit proxy URL or bypass or no-proxy from app settings, and log the route actually used together with the running accountPAC unread / service / specialUsual caseDefault: follow OS settingsExceptional environment?Set URL, bypass, or no proxyUse the OS defaultLog route and account

Figure 10: Configure when you must. Always log which path was used.

10. Summary

  • Windows proxy settings split into three families — WinINET per-user settings, WinHTTP machine settings, and environment variables — and which one is read is decided by the app (its HTTP stack) and the running account.
  • WinINET is for interactive apps and is not supported for use in a service; service use is WinHTTP’s job (netsh winhttp). For “it works by hand but not as a service”, first suspect a difference of running account.
  • netsh winhttp set proxy is a static setting and does not handle PAC, automatic detection, or authentication. On a PAC-operated network you need to decide how clients that cannot read PAC will be treated.
  • PAC’s FindProxyForURL returns a proxy or DIRECT per URL. WPAD only works on a network that has a DHCP/DNS arrangement.
  • .NET Framework’s default is the running account’s Internet Options (overridable with defaultProxy); .NET (Core and later) is environment variables then user proxy settings. An explicit specification (HttpClientHandler.Proxy) is always highest priority.
  • 407 is a proxy-authentication error; in an app running under a service account the typical cause is that the “default credentials” become a different person.
  • A TLS-inspection proxy presupposes distribution of the internal CA certificate, and the correct answer to a certificate error is distribution to the certificate store, not disabling validation. Pinned traffic needs an exclusion.
  • Isolate mechanically in the order “reproduce → collect the three families of settings → identify the running account → classify the error → proxy log”. On the app side, a design that “can configure the proxy, and logs the path it used” is the best prevention.

The next time you are consulted with “only the business app cannot connect”, ask this first.

Under whose account is that app running, and which of the three families of proxy settings does it read?

That one question changes the entrance to the investigation a great deal.

KomuraSoft LLC handles investigation of Windows-app communication trouble on corporate-proxy, authenticating-proxy, and TLS-inspection environments — “it works on the development machine but cannot communicate on the customer network”, “after we turned it into a service it could no longer reach the external API” — and consulting on business-app communication design that assumes a proxy environment (settings items, timeouts, log design). It is fine to start from organizing the reproduction steps and how to collect logs.

References

  1. Microsoft Learn, WinINet vs. WinHTTP. On the guidance to use WinINET unless you are in a service or a process that needs impersonation and session isolation, and on the feature-comparison table covering credential cache, credential prompts, service support, impersonation, session isolation, and similar.  2 3 4

  2. Microsoft Learn, netsh winhttp. On the syntax of netsh winhttp show/set/import/reset; set proxy’s proxy-server and bypass-list; import proxy source=ie; and detailed proxy settings in JSON form (Proxy, ProxyBypass, AutoconfigUrl, AutoDetect) via set advproxy.  2 3 4

  3. Microsoft Learn, About WinHTTP. On WinHTTP being an HTTP stack designed for service and server-side use, supporting execution under a service account and impersonation, and not sharing the browser’s cookies, cache, credentials, or the user’s Internet Options.  2 3

  4. Microsoft Learn, Using a proxy with Delivery Optimization. On netsh winhttp set proxy being a static setting that does not support automatic detection, a PAC URL, or proxy authentication; per-device proxy configuration for contexts with no signed-in user (NetworkProxy CSP, the “Make proxy settings per-machine” policy); and certificate-pinned traffic failing under TLS inspection and needing an exclusion.  2 3 4 5 6 7 8 9 10

  5. Microsoft Learn, WinHTTP AutoProxy Support. On a PAC script containing a FindProxyForURL(url, host) function that computes a list of proxies per request and indicating a direct connection with a special return value, and on the older AutoProxy API not automatically integrating automatic proxy into the HTTP stack so that the app has to call WinHttpGetProxyForUrl.  2 3

  6. Microsoft Learn, WinHttpGetProxyForUrl function. On it being an implementation of the WPAD protocol, needing to be called per URL because a PAC file can return a different proxy per URL, and supporting both an explicit PAC URL and automatic detection from the network.  2

  7. Microsoft Learn, HttpClient.DefaultProxy Property. On Windows reading the HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, and NO_PROXY environment variables first and, if they are undefined, the user proxy settings; Linux initializing with no proxy if the environment variables are absent; NO_PROXY not supporting wildcards and using a leading-dot subdomain match; and a proxy URL being able to include a username and password.  2 3 4 5 6 7 8 9

  8. Microsoft Learn, Configuring Internet Applications. On the defaultProxy element defining the default proxy on .NET Framework; an HttpWebRequest with no Proxy property using the default proxy; and system Internet settings and configuration-file settings being combined with the configuration-file side taking priority.  2 3

  9. Microsoft Learn, defaultProxy element (network settings). On the enabled and useDefaultCredentials attributes of the system.net/defaultProxy element, the proxy, bypasslist, and module child elements, the system’s proxy settings being used if the element is empty, and configuring with HttpClient.DefaultProxy when migrating to .NET 6 and later.  2 3

  10. Microsoft Learn, Authentication in WinHTTP. On status code 407 and a Proxy-Authenticate header being returned when proxy authentication is required (server authentication is 401 and WWW-Authenticate); the difference between Basic authentication and challenge/response schemes such as Kerberos; and a challenge/response scheme meaning the username and password do not travel the network.  2 3

  11. Microsoft Learn, HttpClientHandler.DefaultProxyCredentials Property. On the property that sets the credentials used to authenticate to the default proxy when UseProxy is true and Proxy is null so that the system-default proxy is used.  2

  12. Microsoft Learn, WebProxy.Credentials Property. On the Credentials property being the credentials sent to the proxy in response to HTTP 407, and on the recommendation in many client scenarios to set UseDefaultCredentials to true so that the signed-in user’s default credentials are used.  2

  13. Microsoft Learn, Understanding implications when using network intermediation to decrypt or manipulate Microsoft 365 traffic at the network layer. On TLS inspection (SSL decryption) being a configuration in which a proxy or firewall decrypts, inspects, and re-encrypts TLS; it being able to cause malfunction and performance degradation in services that assume end-to-end TLS; and the recommendation to exclude Microsoft 365-destined traffic from network-layer decryption and inspection.  2 3

  14. Microsoft Learn, Make HTTP requests with the HttpClient class. On the two configuration methods HttpClient.DefaultProxy and HttpClientHandler.Proxy; a Proxy specification taking priority over the configuration file and the local computer’s settings; the typical WPAD configuration of obtaining a PAC file (wpad.dat and similar) through the DNS name wpad or DHCP; and the local-destination bypass judgement by flat name, loopback, and domain-suffix match.  2 3 4

  15. Microsoft Learn, WinHttpOpen function. On the meaning of each dwAccessType value. WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY (Windows 8.1 and later) automatically deciding the proxy from system/user proxy settings and also handling failover and authentication automatically, and WINHTTP_ACCESS_TYPE_DEFAULT_PROXY being deprecated from 8.1 onward. 

  16. Microsoft Learn, Work with existing on-premises proxy servers. On outbound HTTPS being established with a CONNECT request to the proxy; success returning HTTP 200; and responses such as 407 (authentication required) or 502 indicating that the proxy is not permitting the communication, so you should proceed to isolation with the proxy-side team. 

Recent articles sharing the same tags. Deepen your understanding with closely related topics.

These topic pages place the article in a broader service and decision context.

This article connects naturally to the following service pages.

Frequently Asked Questions

Common questions about the topic of this article.

The browser can connect, but only the business app cannot get through the corporate proxy. Why?
The browser reads WinINET's per-user proxy settings, but a business app does not necessarily read the same settings. An app running as a Windows service, or under another account, consults the settings visible from that account, WinHTTP's machine settings, or environment variables. First identify the running account, and check the proxy settings visible from that account both with netsh winhttp show proxy and in the user settings. If you can reproduce with curl.exe or similar under the same account on the same machine, you can treat it as a mismatch among configuration families rather than an app-specific problem.
I set netsh winhttp set proxy, but the app's traffic did not change. Why?
What netsh winhttp sets is WinHTTP's machine default. It does not affect browsers or interactive apps that read WinINET, or .NET (Core and later) HttpClient, which prefers environment variables. netsh winhttp set proxy is also a static setting; it does not handle PAC autoconfiguration, automatic detection, or proxy authentication. You first need to confirm which HTTP stack the target app uses and which configuration family it resolves the proxy from.
Which proxy settings does a .NET app read?
.NET Framework by default uses the running account's Internet Options (WinINET-equivalent) settings, and you can override them with the system.net/defaultProxy element in app.config. HttpClient on .NET (Core and later) reads environment variables such as HTTP_PROXY, HTTPS_PROXY, and NO_PROXY first, and if they are not defined falls back to the Windows user proxy settings. In both cases, an explicit HttpClientHandler.Proxy takes priority. The default resolution order therefore differs between Framework and Core and later, so you need to re-check proxy behaviour when you migrate.
What should I check when 407 Proxy Authentication Required is returned?
407 is a sign that the proxy itself is demanding authentication; it is a different thing from a destination-server authentication error (401). First confirm the authentication scheme the proxy is asking for (Negotiate, NTLM, Basic) from the Proxy-Authenticate header, and in .NET pass credentials with HttpClientHandler.DefaultProxyCredentials or WebProxy.UseDefaultCredentials. In an app running under a service account, the "default credentials" become that service account's, so the typical incident is that it works for an interactive user and then 407s as soon as you turn it into a service. Also check the proxy-side log for who it authenticated as.
A TLS-inspection proxy produces certificate errors. May I disable certificate validation?
Disabling it is not recommended. A TLS-inspection proxy decrypts the traffic and then presents the client with a certificate re-signed by its own CA, so validation fails if that CA certificate is not in the trusted roots. The correct fix is to distribute the internal CA certificate to the Windows certificate store (usually the local computer's Trusted Root Certification Authorities). Disabling validation in code means a man-in-the-middle attack cannot be detected when the app is used on an outside network, and the vulnerability remains.

Author Profile

Profile page for the article author.

Go Komura

Representative of KomuraSoft LLC

Focused on Windows software development, technical consulting, and investigations into failures that are difficult to reproduce.

Back to the Blog