Corporate Proxies and Windows Apps — Sorting Out Proxy Resolution in WinINET, WinHTTP, and .NET
· Updated: · Go Komura · Windows, Proxy, WinHTTP, WinINET, .NET, HttpClient, PAC, WPAD, Networking
Revision history (first version, published Aug 20, 2026)
- First published
Cite this article(DOI (registered archive): 10.5281/zenodo.22170869)
The DOIs below refer to previously archived versions and may not match the current text. Use this page’s URL to reference the current text.
Go Komura (2026). Corporate Proxies and Windows Apps — Sorting Out Proxy Resolution in WinINET, WinHTTP, and .NET. KomuraSoft LLC. https://comcomponent.com/en/blog/windows-proxy-wininet-winhttp-dotnet/
- DOI (registered archive)
- 10.5281/zenodo.22170869
- DOI (last registered version)
- 10.5281/zenodo.22170870
“The browser can open external sites, but only the business app cannot reach the external API.” “It works when run by hand, and fails the moment it becomes a Windows service.” Mismatches like these are common in environments with a corporate proxy.
The starting point of the investigation is which proxy settings that app reads, and under whose account. Windows does not have a single proxy setting. The browser, a service, and .NET’s HttpClient may each be consulting different settings.
In most cases the cause is neither a proxy server outage nor an app bug, but this mismatch between settings families and running accounts. This article is written for IT staff at small and midsize companies and for Windows app developers. It works through the overall picture of the settings, then PAC, authentication, and certificates, and finally the isolation procedure.
| What is wrong, or what you want to know | Where to start reading |
|---|---|
| Only the browser can communicate / behavior does not change after configuring | The three families of settings |
| Works when run by hand, but fails as a service | Running-account differences |
| Only specific URLs fail / the PAC settings are not used | PAC and WPAD |
| Behavior changed after moving from .NET Framework to .NET | .NET resolution order |
| 407 is returned | Proxy authentication |
| Certificate errors appear | TLS inspection |
| Not sure where to start | Five-step isolation |
HttpClient creation patterns and timeout design themselves are covered in “Don’t Wrap HttpClient in a using Block”. The focus of this article is which settings the proxy is resolved from, and where the communication stops after that.
1. The Bottom Line First
There are three points to get straight first.
- Before looking at the settings, identify the app and its running account. Which of WinINET’s per-user settings, WinHTTP’s machine settings, and the environment variables gets read is decided on the app side. The settings an administrator sees on their own screen are not necessarily visible from a service.123
- Investigate with the failing URL, not one that works. PAC returns a proxy or DIRECT per URL. In .NET, too, the route changes with the runtime, environment variables, and an explicit handler setting.456
- Investigate the route, authentication, and certificates separately. 407 is proxy authentication and is distinct from a 401 from the destination. A certificate error caused by TLS inspection is handled by distributing the internal CA and configuring the necessary exclusions, not by disabling validation.783
flowchart TB
accTitle: Information to line up for a proxy investigation
accDescr: Identify the app's HTTP stack and running account, line up the settings visible from that account and the failing URL, then investigate the route, authentication, and certificates
app["HTTP stack and account"] --> settings["Check that account's settings"]
settings --> url["Check the route with the failing URL"]
url --> error["Investigate authentication and certificates separately too"]
Figure 1: Line up “which settings”, “seen from whose account”, and “which URL” before investigating where the failure occurs.
In one sentence: whenever you say “I checked the proxy settings”, be able to say which of the three families you checked, and from which account.
In the diagram a solid line marks a relation that always holds and a dashed line marks a conditional one (the conditions are given per relation on the detail page). The full list of relations (19 in total, with evidence and certainty) and the definitions of the main concepts are collected on the knowledge map detail page (in Japanese). Data: JSON-LD / Turtle
2. Windows Has Three Families of “Proxy Settings”
The paths by which an app on Windows finds the corporate proxy fall broadly into three families.
| Settings family | Where to set it, or the command | Scope | What mainly reads it |
|---|---|---|---|
| (1) WinINET (Internet Options) | Settings app > Network & internet > Proxy, inetcpl.cpl |
Per user (default) | Browsers, interactive desktop apps, .NET Framework by 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 are defined) | HttpClient on .NET (Core and later), curl, cross-platform tools such as Node.js and Python |
“The Windows Proxy Settings” Usually Means (1)
The “Proxy” you see in the Settings app is historically Internet Explorer’s Internet Options, which is WinINET’s configuration. By default it is stored per user.3
(2) is the per-machine default for contexts with no signed-in user, such as services. (3) is mainly the convention of tools with cross-platform origins, and on Windows it is read by .NET (Core and later), curl, and the like.5
The App, Not the Person Who Configured It, Decides Which One Is Read
The source is fixed by the app: (1) for an app that uses WinINET, (2) or an app-specific setting for WinHTTP, and (3) then (1) for .NET (Core and later).
So when “the settings are right but it still cannot connect”, first confirm whether the settings you checked and the settings the app read belong to the same family. Before going around setting all three families to the same value, it is important to identify the entry point of the target app.
Some Configurations Are Per Device Rather Than Per User
Enabling the Group Policy “Make proxy settings per-machine (rather than per-user)” switches (1) to machine scope and applies the same settings to all users. With MDM (Intune and the like), the NetworkProxy CSP configures it per device.3
flowchart TB
accTitle: Changing the scope of the per-user settings
accDescr: WinINET settings are per user by default, but Group Policy can switch them to per-computer settings, and MDM can configure them per device through the NetworkProxy CSP
user["WinINET settings are per user by default"] --> gp["GPO switches them to per computer"]
gp --> all["Same settings applied to all users"]
mdm["NetworkProxy CSP in MDM"] --> device["Configured per device"]
Figure 2: Because some configurations change the scope of (1) to per device, read “per user” as the default state.
3. WinINET and WinHTTP — One for Interactive Apps, One for Services
3.1. Different Roles
WinINET and WinHTTP are both standard Windows HTTP client stacks. They differ, however, in the execution model they assume.
WinINET is for interactive desktop apps. It automatically inherits the user’s Internet Options, proxy, cookies, and credential cache, and can show a credential prompt UI when needed. On the other hand, use in services and service-like processes is not supported.1
WinHTTP is for services and server-side use. It supports running under a service account, thread impersonation, and session isolation. In exchange, it does not share the browser’s settings, cookies, or credentials, and shows no UI.2
Microsoft’s guidance on choosing between them is likewise WinINET unless you are in a service or a service-like process that needs session isolation or impersonation, and WinHTTP for services.1 This does not, however, mean that every app running as a service reads WinHTTP’s machine settings. Services written in .NET are treated separately in section 3.3.
3.2. Basic netsh winhttp Operations
WinHTTP’s machine default proxy is managed with netsh.9
:: Show 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
:: Reset to the default (DIRECT)
netsh winhttp reset proxy
Do Not Confuse Static Settings, Import, and Automatic Configuration
set proxy is a static setting. It does not handle automatic detection, a PAC URL, or proxy authentication.3
import proxy source=ie copies the static settings as they are at the moment you run it. If Internet Options is changed afterward, the WinHTTP side does not follow.
flowchart TB
accTitle: Importing proxy settings is a one-time copy
accDescr: import proxy source=ie only copies the static settings at that moment into WinHTTP, and later changes to Internet Options are not followed automatically
source["Static settings at run time"] --> copy["import proxy source=ie"]
copy --> dest["Copied into WinHTTP"]
source -.-> later["Later changes are not followed automatically"]
Figure 3: import is not a synchronization setting but an operation that takes in the static settings at that moment.
To configure the machine including PAC and WPAD automatic detection, use netsh winhttp set advproxy. The JSON-format advanced settings include Proxy, ProxyBypass, AutoconfigUrl, and AutoDetect.9
3.3. The Most Common Pitfall: Services Do Not Read the User’s IE Settings
The typical case is a developer who moves a tool that was running on their own PC into a Windows service running as LocalSystem.
Even if it communicated during development through the developer’s own per-user settings (1), the settings visible from LocalSystem are something else. If WinHTTP’s machine settings are unconfigured (DIRECT), it tries to connect to the external API directly and times out. The procedure for turning something into a service itself is covered in “How to Build and Operate Windows Services”.
flowchart TB
accTitle: The mismatch when a manually run tool becomes a service
accDescr: When a tool that ran under the developer's per-user settings becomes a LocalSystem service, the visible settings change, and if WinHTTP's default is DIRECT it attempts a direct connection and fails
dev["Run manually as the developer"] --> works["Communicates with your own settings"]
works --> service["Changed to a LocalSystem service"]
service --> changed["Visible settings change"]
changed --> direct["Direct connection if WinHTTP is unconfigured"]
direct --> failure["Timeout at the external API"]
Figure 4: Even on the same machine, changing the running account changes which settings can be seen.
Provide the Settings in the Form That HTTP Stack Reads
For a process that communicates even when no user is signed in, provide machine-scope settings. The way to configure them, however, must match the HTTP stack.
| Target | How to provide the settings |
|---|---|
| Native apps and Windows components that use WinHTTP | Provide the WinHTTP settings through netsh3 |
| Services that use HttpClient on .NET (Core and later) | System environment variables (HTTPS_PROXY and the like), or an explicit HttpClientHandler.Proxy from the app’s own configuration |
HttpClient on .NET (Core and later) does not read WinHTTP’s machine settings. Do not conclude that “it is a service, so configuring netsh is enough.” The detailed priority order is covered in chapter 5.
A Static Setting on a Laptop That Leaves the Office Breaks in the Opposite Direction
If you pin the corporate static proxy on a laptop, the proxy is unreachable outside the office and communication fails. Treat static machine settings as a tool for servers whose network configuration does not change.3
4. PAC and WPAD — What “Automatic Configuration” Consists Of
PAC computes the route, and WPAD finds where the PAC is. Splitting “automatic configuration” into these two shows you where to look.
4.1. The PAC File and FindProxyForURL
PAC (Proxy Auto-Configuration) is a file written in JavaScript (ECMAScript). Its mandatory FindProxyForURL(url, host) function returns, for the given URL and host, either the list of proxies to use or DIRECT, meaning a direct connection.10
function FindProxyForURL(url, host) {
// Internal domain and private addresses connect directly
if (dnsDomainIs(host, ".example.co.jp") ||
isInNet(host, "10.0.0.0", "255.0.0.0")) {
return "DIRECT";
}
// Everything else goes through the proxy. Fall back to the next one if the first is unavailable
return "PROXY proxy1.example.co.jp:8080; PROXY proxy2.example.co.jp:8080; DIRECT";
}
In this example, the internal domain and the specified private addresses connect directly, and everything else goes through the proxy. In the latter case, if the first proxy is unavailable it moves on to the next, and finally falls back to DIRECT.
flowchart TB
accTitle: PAC changes the route by destination
accDescr: The PAC example shown evaluates the URL and host, returns DIRECT for the internal domain and the specified private addresses, and otherwise returns an ordered list of proxies
target["Pass the URL and host"] --> check{"Matches the example's internal conditions?"}
check -->|"Yes"| direct["Return DIRECT"]
check -->|"No"| proxies["Proxy 1, proxy 2, then DIRECT"]
Figure 5: Because the PAC’s answer changes per URL, success with another site does not confirm the route of the failing API.
Two points of caution for investigations follow from this.
Check by passing the failing URL. PAC can return a different answer for each URL. WinHTTP’s automatic proxy feature is also designed to be queried each time with the URL of the request. “Other sites are visible in the browser” is not proof that the failing API takes the same route.4
For traffic missing from the proxy log, also suspect DIRECT and bypasses. DIRECT is an instruction to “go without the proxy”. If internal traffic does not show up in the log, check for a DIRECT result in the PAC or a match in the bypass list.
4.2. Automatic Detection with WPAD
Turning on “Automatically detect settings” uses WPAD (Web Proxy Auto-Discovery) to find where the PAC file is. Typically the PAC URL is handed out over DHCP, or a host named wpad is resolved through DNS and the file is fetched from a URL such as http://wpad/wpad.dat.11
flowchart TB
accTitle: From automatic detection to fetching the PAC
accDescr: In WPAD the location of the PAC file is found through DHCP or DNS, and the PAC is fetched from that location and used to compute routes
auto["Enable automatic detection"] --> find["Find the location through DHCP or DNS"]
find --> pac["Fetch the PAC file"]
pac --> route["Compute the route per destination"]
find -.-> missing["Detection fails if nothing is set up"]
Figure 6: Automatic detection needs the DHCP/DNS setup on the network side.
Turning on automatic detection alone on a network without that setup does nothing, and it adds the wait until detection fails. Choosing “automatic” does not resolve things everywhere.
4.3. Behavior of Clients That Cannot Read a PAC
Even if you distribute a PAC, not every client evaluates it. netsh winhttp set proxy is a static setting, and tools that use the HTTP_PROXY environment-variable convention generally have no place to put a PAC URL either. They take a fixed proxy URL.35
For a native app that uses WinHTTP directly, check how the session is opened.
| How WinHttpOpen is used | Handling of automatic proxy |
|---|---|
WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY on Windows 8.1 or later |
WinHTTP resolves the proxy automatically per request from the system/user settings (including WPAD/PAC)12 |
The traditional WINHTTP_ACCESS_TYPE_DEFAULT_PROXY and the like |
The app itself must call WinHttpGetProxyForUrl and apply the result to the request. DEFAULT_PROXY is deprecated on 8.1 and later1012 |
flowchart TB
accTitle: Checking whether WinHTTP uses the PAC
accDescr: If the WinHTTP session is opened with AUTOMATIC_PROXY the proxy is resolved automatically, but with the traditional way of opening it the app must call the AutoProxy API and apply the result
open["Check the WinHttpOpen parameters"] --> mode{"AUTOMATIC_PROXY?"}
mode -->|"Yes"| automatic["WinHTTP resolves automatically"]
mode -->|"Traditional opening"| api["App calls the AutoProxy API"]
api --> apply["Apply the result to the request"]
Figure 7: Knowing only that an app uses WinHTTP does not tell you that the PAC is used automatically too.
In older implementations, a PAC may exist and still not be used. On a network operated with PAC, also decide what to give clients that cannot read the PAC through static settings or environment variables.
5. Proxy Resolution in .NET — Framework and Core and Later Are Different Things
.NET Framework and .NET (Core and later) determine the default proxy differently. Investigating a .NET 8 app with Framework-era knowledge can lead you to check the wrong settings.
5.1. .NET Framework — Internet Options by Default, Overridden by defaultProxy
.NET Framework’s HttpWebRequest, and the HttpClient built on top of it, use the default proxy unless Proxy is set explicitly. That default is determined by combining the running account’s Internet settings (the WinINET equivalent) with the configuration file, and the configuration file’s settings take priority.6
It is controlled through system.net/defaultProxy in app.config or machine.config.13
<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>
If defaultProxy is empty, the Internet Options settings are used; if proxyaddress and the like are specified, those take priority. From code, WebRequest.DefaultWebProxy replaces the same default.136
flowchart TB
accTitle: The .NET Framework default proxy
accDescr: .NET Framework starts from the running account's Internet settings and gives priority to values specified in the configuration file to determine the default proxy
account["Running account's settings"] --> config["Configuration file takes priority"]
config --> default["Framework default proxy"]
replace["Replaced through DefaultWebProxy"] -.-> default
Figure 8: The Framework default is the settings of the “running account”, which are not necessarily the settings visible on the administrator’s screen.
When running under a service account, the same caution as in section 3.3 applies. What it reads by default is that account’s Internet Options, which are separate from what is visible on the administrator’s desktop, and usually empty.
5.2. .NET (Core and Later) — Environment Variables First, Then the OS User Settings
.NET (Core and later) has the static property HttpClient.DefaultProxy. It is the default used when the handler does not specify a proxy explicitly, and on Windows it is initialized by reading the environment variables and, if they are not defined, the user’s proxy settings, in that order.5
| Environment variable | Meaning |
|---|---|
HTTP_PROXY |
Proxy used for HTTP requests |
HTTPS_PROXY |
Proxy used for HTTPS requests |
ALL_PROXY |
Fallback when the above are not defined |
NO_PROXY |
Comma-separated list of hosts for which no proxy is used |
Separate the Three Proxy-Specifying Variables From NO_PROXY
If any of HTTP_PROXY, HTTPS_PROXY, or ALL_PROXY is defined, it takes priority over the OS settings. A leftover HTTPS_PROXY from testing, or one injected by a CI/CD template, sends traffic along a route different from the settings you saw on screen.
On the other hand, defining only NO_PROXY does not configure a proxy from environment variables. On Windows, the OS user proxy settings continue to be used.
flowchart TB
accTitle: Default proxy initialization in .NET on Windows
accDescr: If any of HTTP_PROXY, HTTPS_PROXY, and ALL_PROXY is defined the environment variables take priority, and if none is defined the Windows user settings are used. NO_PROXY alone does not configure a proxy from environment variables
init["Initialize the default proxy"] --> env{"Any of the 3 proxy variables defined?"}
env -->|"Yes"| useenv["Environment variables take priority"]
env -->|"No"| user["Windows user settings"]
init -.-> no["NO_PROXY alone configures nothing"]
Figure 9: First check whether a proxy is specified through environment variables, and distinguish that from a state with only NO_PROXY.
The “Leading Dot” in NO_PROXY and the Difference on Linux
NO_PROXY does not support wildcards (*). .example.com with a leading dot matches www.example.com but not example.com itself.5
In Linux containers and the like, the default is no proxy if the environment variables are undefined. Because the default behavior differs between Windows and Linux, check it when moving to a container as well.5
5.3. Explicit Configuration — HttpClientHandler.Proxy and UseProxy
On both runtimes, an explicit HttpClientHandler.Proxy has the highest priority. It takes precedence over the OS settings and the configuration file. With UseProxy = false, no proxy is used at all.11
using System.Net;
// Explicitly use a proxy read from the app's own configuration
var handler = new HttpClientHandler
{
Proxy = new WebProxy("http://proxy.example.co.jp:8080")
{
BypassProxyOnLocal = true,
BypassList = new[] { @"^intra\.example\.co\.jp$" },
UseDefaultCredentials = true // For 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 connections to internal APIs)
var directHandler = new HttpClientHandler { UseProxy = false };
var directClient = new HttpClient(directHandler);
flowchart TB
accTitle: Determining the effective proxy from the handler
accDescr: If UseProxy is false the connection is direct, if true the explicit Proxy on the handler is used, and if there is no explicit setting the default proxy is used
use{"Is UseProxy true?"} -->|"No"| direct["Direct connection"]
use -->|"Yes"| explicit{"Proxy set explicitly?"}
explicit -->|"Yes"| proxy["Use the specified proxy"]
explicit -->|"No"| default["Use the default proxy"]
Figure 10: Before investigating the default proxy, check the explicit setting on the handler and UseProxy.
Watch Out for the Automatic Bypass of Local Destinations Too
When there is no explicit setting and the OS settings are followed, flat names without a dot, loopback addresses, and destinations matching the machine’s own domain suffix can be treated as “local” and bypassed.11
When “the behavior differs between a direct IP address and a name” or “it started going through the proxy once we used the FQDN”, check this determination.
The priority order so far is summarized below.
| Priority (high to low) | .NET Framework | .NET (Core and later) |
|---|---|---|
| 1 | Explicit setting such as HttpClientHandler.Proxy |
Same as left |
| 2 | defaultProxy in app.config |
Assignment to HttpClient.DefaultProxy |
| 3 | Running account’s Internet Options | Environment variables (HTTP_PROXY and the like) |
| 4 | — | Windows user proxy settings |
6. Authenticating Proxies — 407 Is a “Proxy” Authentication Error
6.1. Do Not Confuse 407 With 401
Even after reaching the proxy, you get no further without passing authentication. Tell apart who is demanding authentication by the status code and header.7
| Response | Who demands authentication | Header to check |
|---|---|---|
| 407 Proxy Authentication Required | The proxy | Proxy-Authenticate |
| 401 | The destination server | WWW-Authenticate |
On a 407, first check the schemes listed in Proxy-Authenticate. Basic sends a user name and password, whereas Negotiate (Kerberos/NTLM) and the like are challenge/response schemes. In the latter, the password itself does not travel, and authentication completes over several round trips.7
sequenceDiagram
accTitle: Flow of responding to an authenticating proxy
accDescr: The client receives 407 and a notice of the authentication scheme from the proxy, and responds with credentials for that scheme. Challenge-response schemes need several round trips
participant C as Client
participant P as Proxy
C->>P: Request the communication
P-->>C: 407 with Proxy-Authenticate
C->>P: Respond to authentication per the scheme
Note over C,P: Several round trips depending on the scheme
Figure 11: On a 407, check the authentication information sent to the proxy, not to the destination server.
Which authentication scheme things “fall back” to is covered in detail in “NTLM and Kerberos Explained with Diagrams”.
6.2. Passing Credentials in .NET
The place to configure it differs between following the default proxy and specifying a proxy explicitly.
To use the default proxy and pass credentials, use HttpClientHandler.DefaultProxyCredentials. These are the credentials sent to the default proxy when UseProxy = true and Proxy = null.14
using System.Net;
var handler = new HttpClientHandler
{
UseProxy = true, // The default. Combined with a null Proxy, the system default proxy is used
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 specifying the proxy explicitly, give the credentials to the WebProxy. In most client scenarios the recommendation is to use the signed-in user’s default credentials rather than an individual user name and password, which is what WebProxy.UseDefaultCredentials = true does.15
6.3. The Service Account 407 Problem
The “default credentials” are the credentials of the account that runs the process. For an interactive user it authenticates as that user; for a LocalSystem service, as the computer account.
flowchart TB
accTitle: Becoming a service changes who is authenticated
accDescr: With default credentials, an interactive run authenticates as that user and a LocalSystem service as the computer account, so check whether the proxy can authenticate that principal
run{"Running account?"} -->|"Interactive user"| user["That user's credentials"]
run -->|"LocalSystem"| machine["Computer account"]
user --> check["Check whether the proxy can authenticate it"]
machine --> check
Figure 12: Even with “default” left selected in the code, the principal the proxy authenticates changes when the app becomes a service.
A proxy that authenticates users through AD integration may be unable to authenticate a computer account or a local account, and the 407s continue. Conversely, some environments provide authentication exemptions for services by source IP or by account.
For an app that will become a service, therefore, decide at the design stage whether to use a domain service account (gMSA and the like), set up an authentication exemption on the proxy, or provide an internal relay proxy that needs no authentication. Investigating a 407 requires both the app’s configuration and “whether the proxy can authenticate that running account”.
There is also the method of embedding credentials in an environment variable, as in HTTP_PROXY=http://user:pass@proxy:8080.5 Because the plaintext password is exposed in the environment variables, that is, in the process information, it is not recommended for permanent operation.
7. HTTPS and Proxies — CONNECT Tunnels and TLS Inspection
7.1. HTTPS Passes Through the Proxy in a “Tunnel”
To use a proxy for HTTPS, the client first sends CONNECT destination-host:443 to open a TCP tunnel. On success the proxy returns 200, after which the client and the destination server perform the TLS handshake inside the tunnel. If the tunnel does not open, a 407, 502, or similar comes back.16
flowchart TB
accTitle: Until the HTTPS tunnel opens
accDescr: In HTTPS a CONNECT request opens a TCP tunnel, and after 200 is returned the TLS handshake takes place inside the tunnel. If the tunnel does not open, a 407 or 502 or similar is returned
connect["Specify the destination with CONNECT"] --> result{"Tunnel opened?"}
result -->|"Success, 200"| tls["TLS connection inside the tunnel"]
result -->|"Failure"| error["407, 502, and so on"]
Figure 13: Read a failure at the stage of opening the tunnel separately from a failure of the TLS connection that follows.
In this “pass-through” model, the proxy cannot read the encrypted HTTPS content. What appears in the log is only the destination host name and whether the connection succeeded; the URL path is not visible.
7.2. TLS Inspection Proxies and Certificate Errors
In the TLS inspection (SSL decryption, break and inspect) model, the proxy terminates TLS, decrypts and inspects the traffic, and then re-encrypts it. The certificate presented to the client is also replaced with one re-signed by the proxy’s own CA.8
flowchart TB
accTitle: TLS inspection changes the certificate
accDescr: In TLS inspection the proxy terminates TLS to inspect the content and presents the client with a certificate re-signed by its own CA, so trust in that CA is required
proxy["Proxy terminates TLS"] --> inspect["Decrypt, inspect, re-encrypt"]
proxy --> cert["Certificate re-signed by the internal CA"]
cert --> trust["CA trust needed on the client side"]
Figure 14: In the inspection model, the question is not only the destination’s certificate but whether the internal CA can be trusted.
First, Check Which Trust Store Is Used for Validation
This configuration requires the proxy’s CA certificate to be distributed to the trusted roots of every client. Certificate validation errors occur not only on machines that have not received it, but also in runtimes that use their own trust store and do not look at the Windows certificate store.
In .NET this typically shows up as an HttpRequestException with an inner AuthenticationException. Look for a message such as “the remote certificate is invalid”.
The Fix Is Distributing the CA, Not Disabling Validation
The internal CA certificate is normally distributed to the local computer’s “Trusted Root Certification Authorities”. For when to use the user store instead, see “The Windows Certificate Store in Practice”.
Do not work around it by always returning true from ServerCertificateCustomValidationCallback. It remains a vulnerability in which a man-in-the-middle attack cannot be detected when the app is used on an outside network.
Exclude Pinned Traffic From Inspection
Traffic that performs certificate pinning fails the moment the proxy replaces the certificate. For Windows components that validate specific Microsoft certificates and the like, there is no workaround, and an exclusion is required.3
flowchart TB
accTitle: Separating CA distribution from excluding pinned traffic
accDescr: For a certificate error under TLS inspection, check trust in the CA, but for traffic that pins certificates the replacement itself is the cause of failure, so exclude it from inspection
failure["Certificate validation error"] --> pinned{"Certificate pinned?"}
pinned -->|"Yes"| exclude["Exclude from inspection"]
pinned -->|"No"| store["Check the trust store being used"]
store --> ca["Check distribution of the internal CA"]
Figure 15: Distributing the internal CA and avoiding the certificate replacement itself are separate fixes.
Microsoft also recommends excluding traffic to SaaS such as Microsoft 365 from decryption and inspection at the network layer.8 If certificate errors occur only with specific cloud services, suspect the combination of the inspection exclusion list and pinning.
8. Isolation Procedure — Identify the Culprit in Five Steps
In an actual investigation, check the mechanisms covered so far in the following order.
| Step | What to do | What you learn |
|---|---|---|
| (1) Reproduce | Access the failing URL with curl.exe -v or Invoke-WebRequest (if possible on the same machine under the same account) |
Whether the problem is app-specific or environmental |
| (2) Collect settings | Collect all three families: netsh winhttp show proxy, the per-user settings, and the environment variables |
Which family contains what |
| (3) Identify the account | Identify the account the target app runs under (a service, Task Scheduler, or another user) | Which settings and which credentials it runs with |
| (4) Classify the error | Distinguish 407 / 403 / name resolution failure / timeout / certificate error | Whether it is proxy authentication, policy denial, the route, or TLS inspection |
| (5) Proxy log | Check the proxy server’s access log at the relevant time | Whether the proxy was reached at all, and who it authenticated as |
(1) Reproduce: Same URL, but Line Up the Tool’s Settings Family Too
Access the failing URL, if possible from the same machine and the same account. Also watch the differences between tools.
| Tool | What to keep in mind during the investigation |
|---|---|
The curl.exe bundled with Windows |
-x http://proxy:8080 specifies the proxy explicitly. TLS validation normally uses the OS certificate store (Schannel) |
Invoke-WebRequest in Windows PowerShell 5.1 |
Resolves on the .NET Framework side. Internet Options by default |
Invoke-WebRequest in PowerShell 7 |
Resolves on the .NET side. Environment variables take priority |
“curl gets through but the app does not” is a hint that the settings families differ. Do not conclude from success with another tool alone that the app took the same route.
(2) Collect Settings: Record All Three Families Together
In PowerShell, they can be collected as follows. The HKCU of the per-user settings belongs to the account that ran this command.
# (1) Per-user (WinINET) settings -- note that this reads the running account's HKCU
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'
(3) Identify the Account: For a Service, Recheck Under the Same Account
Identify whether the target runs as a service, from Task Scheduler, or as another user. For a service, redo the reproduction of (1) and the collection of (2) under the same account. Success in your own administrator session is no proof of what LocalSystem sees.
flowchart TB
accTitle: Reproduce under the same running account
accDescr: Because collection in the administrator's session is no proof of the settings visible from the service, identify the target's running account and then recheck the reproduction and settings collection under that account
admin["Checked in the administrator's session"] --> identify["Identify the target's running account"]
identify --> same["Reproduce and collect under the same account"]
same --> compare["Compare settings and credentials"]
Figure 16: Confirm that the information gathered in (1) and (2) is what the target account identified in (3) sees.
(4) Classify the Error: Break Down “Cannot Connect”
For a 407, check proxy authentication in chapter 6; for a certificate error, TLS inspection in chapter 7. For a timeout, treat failure to reach the proxy as the first candidate and investigate the route, name resolution, and firewalls. A 403 policy denial is also kept separate from authentication and timeouts.
flowchart TB
accTitle: Deciding where to investigate from the error
accDescr: Split communication failures into 407, 403, timeout or name resolution failure, and certificate error, and investigate authentication, policy denial, the route, and TLS inspection respectively
error["Communication failure"] --> auth["407 means proxy authentication"]
error --> deny["403 means policy denial"]
error --> route["Timeout or name resolution means the route"]
error --> tls["Certificate error means TLS"]
Figure 17: Splitting by status code and exception narrows the settings and logs to check.
The pattern where the cause is an inbound Windows Firewall rule rather than the proxy is covered in “The Windows Firewall and Business Applications”.
(5) Proxy Log: Confirm the Arrival and the Authenticated Account
In the access log at the relevant time, confirm whether the proxy was reached and who it authenticated as. If there is no trace, treat the traffic as never having reached the proxy, and investigate a DIRECT result in the PAC, the bypass list, and environment variables that were left behind.
If necessary, confirm the actual destination with a packet capture. For how to collect one, see “Packet Capture on Windows in Practice — Choosing Among pktmon, netsh trace, and Wireshark”.
9. Design Recommendations — Make the Proxy “Configurable” in the App
How easy an investigation is depends on the app’s settings and logs. For an app delivered into an environment with a corporate proxy, provide the following four things.
Allow an Explicit Proxy and a Direct Connection, Not Only Following the Default
Make “follow the OS settings” the default, and for cases where the PAC cannot be read, the app runs as a service, or the configuration is unusual, allow the proxy URL, the bypass list, and “use no proxy” to be specified from a configuration file. The implementation points are HttpClientHandler.Proxy and UseProxy from section 5.3.11
The default in .NET (Core and later) also includes the environment-variable priority explained in section 5.2. Even when leaving it to the default, read which settings are used according to those rules.
Put the Internal Exceptions in a Form That Fits the Deployment Guide
Document which of a PAC DIRECT result, the bypass list, and NO_PROXY excludes internal traffic to APIs, databases, license servers, and the like. Show, with examples, that NO_PROXY does not support wildcards and what the leading dot means.5
Design Timeouts and Retries Assuming the Proxy Route Too
Waiting out a long default timeout when the proxy is down or authentication is pending freezes both the UI and operations. Separate out a shorter connection timeout, and limit retries to idempotent requests. Details are covered in “Don’t Wrap HttpClient in a using Block”.
Log the Route From the Handler That Was Actually Configured
Logging only HttpClient.DefaultProxy misses the handler’s explicit setting and UseProxy = false, and records a route that does not match. It is important to select the effective settings from the handler used to create the HttpClient, and to apply the destination’s bypass determination as well.
using System.Net.Http;
// handler is the same instance used to create the HttpClient
// UseProxy=false always means direct. An explicit setting is used if present, otherwise DefaultProxy
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} running account {User}",
target, route, Environment.UserName);
flowchart TB
accTitle: Logging the route from the client's configuration
accDescr: Select the effective proxy from the handler used to create the HttpClient, perform the destination's bypass determination and proxy resolution, and log the route and running account
handler["Same handler as at creation"] --> effective["Apply UseProxy and the explicit setting"]
effective --> target["Bypass determination and resolution for the destination"]
target --> log["Log the route and account"]
Figure 18: Log the route from the client’s own configuration, not only from the default proxy.
Logging the routes to the main destinations and the running account at startup makes steps (1) to (3) of chapter 8 easier to follow. When someone says “but it works in the browser”, a design that stands up to investigation is one in which the app can explain its own settings and route.
10. Summary
In a Windows proxy investigation, first line up the settings family, running account, and target URL.
WinINET’s per-user settings, WinHTTP’s machine settings, and environment variables are separate families. Becoming a service changes the visible settings and credentials, and the default order also differs between .NET Framework and .NET (Core and later). netsh winhttp set proxy alone cannot handle PAC, automatic detection, or authentication.
PAC returns a proxy or DIRECT per URL, and WPAD needs the setup on the network side. Even after the route is determined, proxy authentication with 407 and certificate validation under TLS inspection are checked separately. Do not disable certificate validation; distribute the CA and exclude pinned traffic.
Isolation proceeds in the order reproduce, collect the three families of settings, identify the running account, classify the error, then the proxy log. On the app side, provide an explicit proxy and direct connection, exception settings, timeouts, and route logging.
The next time someone asks you about “only the business app cannot connect”, start by confirming this.
Under whose account does that app run, and which of the three families of proxy settings does it read?
This one question is the entry point to the investigation.
Related Articles
- Don’t Wrap HttpClient in a using Block — Practical HTTP Communication in C# Business Apps (Creation Patterns, Timeouts, Retries)
- Packet Capture on Windows in Practice — Choosing Among pktmon, netsh trace, and Wireshark
- The Windows Firewall and Business Applications — Register Inbound Rules From the Installer
- How to Build and Operate Windows Services — From Choosing Between Task Scheduler and Services to Turning a BackgroundService into a Windows Service
- NTLM and Kerberos Explained with Diagrams — Why Authentication Falls Back to NTLM
- The Windows Certificate Store in Practice — User or Computer, Which Should You Use?
Related Consulting Areas
KomuraSoft LLC handles investigations of Windows app communication problems in environments with corporate proxies, authenticating proxies, and TLS inspection, such as “it works on the development machine but cannot communicate on the customer’s network” and “it stopped reaching the external API once it became a service”, as well as consultations on the communication design of business apps that assume a proxy environment (settings, timeouts, and log design). Starting from working out the reproduction steps and how to collect logs is perfectly fine.
- Windows App Development
- Bug Investigation and Root Cause Analysis
- Technical Consulting and Design Review
- Contact
References
-
Microsoft Learn, WinINet vs. WinHTTP. On the guidance to use WinINET unless the process is a service or needs impersonation or session isolation, and the feature comparison table covering the credential cache, credential prompts, service support, impersonation, session isolation, and more. ↩ ↩2 ↩3
-
Microsoft Learn, About WinHTTP. On WinHTTP being an HTTP stack designed for services and server-side use that supports running under a service account and impersonation, while not sharing the browser’s cookies, cache, credentials, or the user’s Internet Options. ↩ ↩2
-
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, device-scope proxy configuration for contexts with no signed-in user (the NetworkProxy CSP and the “Make proxy settings per-machine” policy), and traffic that uses certificate pinning failing under TLS inspection and requiring an exclusion. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9
-
Microsoft Learn, WinHttpGetProxyForUrl function. On the function being an implementation of the WPAD protocol that must be called per URL because the PAC file can return a different proxy for each URL, and supporting both an explicit PAC URL and automatic detection from the network. ↩ ↩2
-
Microsoft Learn, HttpClient.DefaultProxy Property. On Windows reading the HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, and NO_PROXY environment variables first and the user’s proxy settings if they are undefined, Linux initializing with no proxy when the environment variables are absent, NO_PROXY not supporting wildcards and using leading-dot subdomain matching, and the proxy URL being able to contain a user name and password. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8
-
Microsoft Learn, Configuring Internet Applications. On the defaultProxy element defining the default proxy in .NET Framework, an HttpWebRequest without a Proxy property using the default proxy, and the system’s Internet settings being combined with the configuration file’s settings with the configuration file taking priority. ↩ ↩2 ↩3
-
Microsoft Learn, Authentication in WinHTTP. On status code 407 and the Proxy-Authenticate header being returned when proxy authentication is required (server authentication uses 401 and WWW-Authenticate), the difference between Basic authentication and challenge/response schemes such as Kerberos, and the user name and password not traveling over the network in challenge/response schemes. ↩ ↩2 ↩3
-
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, its potential to cause malfunctions and performance degradation in services that assume end-to-end TLS, and the recommendation to exclude Microsoft 365 traffic from decryption and inspection at the network layer. ↩ ↩2 ↩3
-
Microsoft Learn, netsh winhttp. On the syntax of netsh winhttp show/set/import/reset, the proxy-server and bypass-list parameters of set proxy, import proxy source=ie, and the JSON-format advanced proxy settings (Proxy, ProxyBypass, AutoconfigUrl, AutoDetect) of set advproxy. ↩ ↩2
-
Microsoft Learn, WinHTTP AutoProxy Support. On the PAC script containing the FindProxyForURL(url, host) function and computing a list of proxies per request, a special return value indicating that a direct connection is acceptable, and the traditional AutoProxy API not being integrated into the HTTP stack automatically so that the app must call WinHttpGetProxyForUrl itself. ↩ ↩2
-
Microsoft Learn, Make HTTP requests with the HttpClient class. On the two configuration methods HttpClient.DefaultProxy and HttpClientHandler.Proxy, an explicit Proxy taking priority over the configuration file and the local computer’s settings, the typical WPAD configuration in which the PAC file (wpad.dat and the like) is obtained through the DNS name wpad or DHCP, and the local-destination bypass determination based on flat names, loopback, and domain suffix matches. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, WinHttpOpen function. On the meaning of each dwAccessType value, WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY (Windows 8.1 and later) determining the proxy automatically from the system/user proxy settings and handling failover and authentication automatically, and WINHTTP_ACCESS_TYPE_DEFAULT_PROXY being deprecated on 8.1 and later. ↩ ↩2
-
Microsoft Learn, defaultProxy element (network settings). On the enabled and useDefaultCredentials attributes of the system.net/defaultProxy element, its proxy, bypasslist, and module child elements, the system proxy settings being used when the element is empty, and configuring through HttpClient.DefaultProxy when migrating to .NET 6 or later. ↩ ↩2
-
Microsoft Learn, HttpClientHandler.DefaultProxyCredentials Property. On the property setting the credentials used to authenticate to the system default proxy when UseProxy is true and Proxy is null. ↩
-
Microsoft Learn, WebProxy.Credentials Property. On the Credentials property being the credentials sent to the proxy in response to HTTP 407, and the recommendation to set UseDefaultCredentials to true in most client scenarios so that the signed-in user’s default credentials are used. ↩
-
Microsoft Learn, Work with existing on-premises proxy servers. On outbound HTTPS communication being established through a CONNECT request to the proxy, HTTP 200 being returned on success, and responses such as 407 (authentication required) and 502 indicating that the proxy is not allowing the communication, so that the isolation should proceed with the proxy team. ↩
Related Articles
Recent articles sharing the same tags. Deepen your understanding with closely related topics.
The Network Works but Windows Says "No internet" — Isolating NCSI, DNS, Proxy, and VPN on Windows
Why Windows says "No internet" while the network works, starting from the NCSI verdict. Isolate DNS, proxy, VPN, and captive portals with...
Don't Wrap HttpClient in a using Block — Practical HTTP Communication in C# Business Apps (Creation Patterns, Timeouts, Retries)
Creating a new C# HttpClient with using on every request exhausts sockets, but making it static means it stops following DNS changes. Thi...
The Order of Name Resolution on Windows — hosts, the DNS Cache, LLMNR/mDNS, and DoH
Whether hosts, the DNS cache, the DNS server, or LLMNR/mDNS answered decides why some PCs fail. Learn the Windows name resolution order, ...
Time Travel Debugging — Recording and Rewinding the Bugs That Never Reproduce in Long-Running Apps
A once-a-month bug leaves only its result in a crash dump. Record and rewind execution with WinDbg Time Travel Debugging (TTD): TTD.exe, ...
Why Arguments Break — The Rules of Windows Command-Line Arguments
Windows passes CreateProcess a single string that the receiver splits. Covers the CommandLineToArgvW, CRT, and .NET rules, ArgumentList, ...
Related Topics
These topic pages place the article in a broader service and decision context.
Windows Technical Topics
Topic hub for KomuraSoft LLC's Windows development, investigation, and legacy-asset articles.
Bug Investigation & Long-Run Failures
Topic page for intermittent failures, communication diagnosis, long-run crashes, and failure-path test foundations.
Where This Topic Connects
This article connects naturally to the following service pages.
Windows App Development
We support Windows desktop applications that involve resident processing, device integration, operational logging, and maintainable structure.
Frequently Asked Questions
Common questions about the topic of this article.
- Why can the browser connect while only the business app cannot get through the corporate proxy?
- The browser reads WinINET's per-user proxy settings, but the business app does not necessarily read the same settings. An app that runs 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 account the app runs under, then check the proxy settings visible from that account both with netsh winhttp show proxy and in the user settings. If you can reproduce the problem with curl.exe or a similar tool under the same account on the same machine, it is a mismatch between settings families rather than an app-specific problem.
- Why does the app's traffic not change after I run netsh winhttp set proxy?
- netsh winhttp sets WinHTTP's machine default. It does not affect browsers and interactive apps that read WinINET, or HttpClient on .NET (Core and later), 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 have to confirm which HTTP stack the target app uses and which settings family it resolves the proxy from.
- Which proxy settings does a .NET app read?
- .NET Framework uses the Internet Options (WinINET-equivalent) settings of the running account by default, and the system.net/defaultProxy element in app.config can override them. HttpClient on .NET (Core and later) reads environment variables such as HTTP_PROXY, HTTPS_PROXY, and NO_PROXY first, and falls back to the Windows user proxy settings if none is defined. In both cases, an explicit HttpClientHandler.Proxy takes top priority. Because the default resolution order differs between Framework and Core and later, proxy behavior has to be rechecked 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, which is separate from an authentication error from the destination server (401). First check the authentication scheme the proxy requires (Negotiate, NTLM, or Basic) in the Proxy-Authenticate header, and in .NET pass credentials through HttpClientHandler.DefaultProxyCredentials or WebProxy.UseDefaultCredentials. In an app that runs under a service account, the "default credentials" are that service account's, so the typical symptom is that it works for an interactive user and returns 407 as soon as it runs as a service. Also check the proxy-side log for who the request was authenticated as.
- A TLS inspection proxy causes certificate errors. Is it acceptable to disable certificate validation?
- Disabling it is not recommended. A TLS inspection proxy decrypts the traffic and presents the client with a certificate re-signed by its own CA, so validation fails unless that CA certificate is 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 stays there permanently.