Guide

How to Use Proxies With C# and .NET: HttpClient and WebProxy, Tested

C# HttpClient proxy setup, tested on .NET 10: WebProxy on the handler, Credentials after a 407, HTTPS_PROXY not HTTP_PROXY, SOCKS5, rotation, the 407 error.

HProxy Team··Updated September 27, 2026·8 min read
HProxy.Guide

Free proxies won't hold up here.

Shared datacenter IPs get flagged and dropped fast. When it has to hold, gaming, streaming, accounts, you need mobile and residential IPs that read as a real device, from $0.44/GB, pay as you go.

Proxies for Web Scraping→

In .NET, the proxy sits on the message handler under HttpClient, not on HttpClient itself. We tested 26 setups on 27 September 2026 with real C# on .NET 10.0.12, compiled by PowerShell 7, against proxies we ran on our own machine. The target was a made-up host that only those proxies could reach, so each request either used the proxy or failed on the spot. We ran everything twice and got the same result both times.

Three things work differently from what many guides say. Two make the proxy login fail, and one skips the proxy:

  • The login goes in Credentials. A user:pass in the WebProxy address is ignored.
  • A Proxy-Authorization header does not work for HTTPS sites. Worse, it can reach the site itself.
  • HTTP_PROXY alone does not cover https:// addresses. Set HTTPS_PROXY or ALL_PROXY.
Our own console window: the .NET proxy lab of 27 September 2026 on .NET 10.0.12. A WebProxy on the handler went through a CONNECT tunnel. HTTPS_PROXY and ALL_PROXY worked, HTTP_PROXY alone went direct for https. Credentials worked after a 407, a login in the WebProxy address was ignored, a Proxy-Authorization default header failed for https and reached the site through an open proxy. socks5 and socks4a sent the host name, socks4 failed to resolve it. A rotating IWebProxy was asked once per request.
Captured on our own machine on 27 September 2026: Node 22.19 printing the saved results of our second run. The lab password is masked and ports read <port>.

Set the proxy on the handler

Build a WebProxy, put it on the handler, and pass the handler to HttpClient:

using System;
using System.Net;
using System.Net.Http;

var handler = new SocketsHttpHandler
{
    Proxy = new WebProxy("http://203.0.113.7:8080")
    {
        Credentials = new NetworkCredential("user", "pass"),
    },
    UseProxy = true,
    PooledConnectionLifetime = TimeSpan.FromMinutes(2),
};

var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(15) };
Console.WriteLine(await client.GetStringAsync("https://httpbin.org/ip"));

In our lab, an https:// address went through the proxy in a CONNECT tunnel, so the proxy saw only the host name and port. An http:// address went to the proxy as a plain request with the full URL, and the proxy could read and answer it. HttpClientHandler takes the same Proxy and UseProxy settings, because it runs on SocketsHttpHandler since .NET Core 2.1.

Keep that client for the life of your program. The Microsoft guidelines recommend "a static or singleton HttpClient instance with PooledConnectionLifetime set to the desired interval, such as 2 minutes", against both port exhaustion and stale DNS.

The login: Credentials, and only after a 407

We tried every place people put a proxy login:

  • Credentials on the WebProxy: worked. The first CONNECT went without a login, the second with it, after the 407.
  • user:pass@ in the WebProxy address: ignored. The proxy got no login, and HttpClient failed with a 407.
  • user:pass@ in HTTPS_PROXY: worked, again after a 407.
  • DefaultProxyCredentials on the handler: worked for HTTPS_PROXY, and also for our own WebProxy without Credentials.
  • A Proxy-Authorization default header: failed with a 407 for https:// sites. Worked only for http:// sites.

The login follows a 407. HttpClient does not send the login up front. It asks the proxy without it, reads the 407, and asks again with it. That costs one extra round trip per new connection, and it means a proxy log always shows a refused attempt first.

A login in the address is ignored. The .NET runtime takes the proxy login only from Credentials, or else from DefaultProxyCredentials. It never reads it from the WebProxy address. The error then printed our proxy address with the password in it, so that password ends up in your error logs.

A Proxy-Authorization header goes to the wrong place. For an https:// site, HttpClient opens the tunnel first, and your default headers travel inside it to the site. The proxy never sees them, so it answers 407. Through a proxy that needed no login, our lab site received the header with the proxy password.

DefaultProxyCredentials covers more than documented. The Microsoft documentation says it applies "When the default (system) proxy is being used". In the runtime source, it is the fallback for any proxy whose Credentials are not set: _proxy.Credentials ?? settings._defaultProxyCredentials. Our own WebProxy without Credentials used it.

WebProxy.UseDefaultCredentials = true sends the Windows login of the current user instead. That suits company proxies that use Windows sign-in. We did not test it.

Environment variables: HTTPS_PROXY, not HTTP_PROXY

When the handler has no proxy of its own, HttpClient uses HttpClient.DefaultProxy. On Windows, it "reads proxy configuration from environment variables or, if those are not defined, from the user's proxy settings". Our lab machine had no system proxy set.

What was setAn HTTPS request
HTTPS_PROXY.Through the proxy.
ALL_PROXY only.Through the proxy.
HTTP_PROXY only.Went direct. It only covers plain HTTP addresses.
HTTPS_PROXY and NO_PROXY with the host.Went direct.
HTTPS_PROXY, and UseProxy = false on the handler.Went direct.

The runtime source shows the rule: HTTP_PROXY fills the proxy for http://, HTTPS_PROXY the one for https://, and ALL_PROXY fills whichever one is missing. To set a proxy for the whole process in code, assign HttpClient.DefaultProxy = new WebProxy(...). A new client without its own proxy used it in our lab.

SOCKS5, SOCKS4 and SOCKS4a

HttpClient speaks SOCKS since .NET 6. Use the scheme in the address, and the login in Credentials:

var handler = new SocketsHttpHandler
{
    Proxy = new WebProxy("socks5://203.0.113.7:1080")
    {
        Credentials = new NetworkCredential("user", "pass"),
    },
};

In our lab, socks5:// and socks4a:// sent the host name and left the lookup to the proxy. socks4:// looked up the host on our own machine first, because SOCKS4 only carries IPv4 addresses. Our made-up host failed with "Failed to resolve the destination host to an IPv4 address." before the proxy saw anything. Pick socks5 unless the proxy speaks only SOCKS4. socks4 and socks4a have no password login: .NET sends only the user name, from Credentials or from the proxy variable. The difference is explained in our guide to SOCKS5 proxies.

Rotate with one client

HttpClient asks an IWebProxy for the proxy on each request, so one client can rotate:

using System;
using System.Net;
using System.Threading;

sealed class RotatingProxy : IWebProxy
{
    private readonly Uri[] _pool;
    private int _next = -1;
    public RotatingProxy(params string[] pool) => _pool = Array.ConvertAll(pool, u => new Uri(u));
    public ICredentials? Credentials { get; set; }
    // takes the proxies in turn; Interlocked keeps the count right when requests run in parallel
    public Uri GetProxy(Uri destination) => _pool[(int)((uint)Interlocked.Increment(ref _next) % (uint)_pool.Length)];
    public bool IsBypassed(Uri host) => false;
}

In our lab, a client with a proxy like this made 4 requests, called GetProxy 4 times, and took our two proxies in turn. Our free proxy API returns working proxies as plain text, one per line, ready for that pool:

using System.Linq;

string raw = await client.GetStringAsync("https://hproxy.com/api/proxy-list?format=txt&protocol=http&limit=50");
string[] pool = raw.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
                   .Select(p => $"http://{p}")
                   .ToArray();
var handler = new SocketsHttpHandler { Proxy = new RotatingProxy(pool), PooledConnectionLifetime = TimeSpan.FromMinutes(2) };

We compiled this example but did not run it, because our lab never leaves our own machine. Leave out recent=true here. That option adds proxies that went quiet in the last 48 hours, so it makes the list longer, not fresher. Public proxies see everything that is not encrypted, so send nothing private through them. For production, a rotating gateway with a new residential IP on each request removes the list work entirely.

Errors, and how to tell them apart

Since .NET 8, HttpRequestException carries an HttpRequestError. It names the kind of failure in the same words in every language of Windows. That matters because the socket messages come from Windows in its own language:

catch (HttpRequestException e)
{
    Console.WriteLine($"{e.HttpRequestError}: {e.Message}");
    if (e.InnerException is not null) Console.WriteLine(e.InnerException.Message);
}

For both SOCKS failures, e.Message only says "An error occurred while establishing a connection to the proxy tunnel." The cause sits in e.InnerException, which is why the example prints it too. What each error meant in our lab:

  • "The proxy tunnel request to proxy '...' failed with status code '407'." In e.Message. ProxyTunnelError. No login, a wrong one, or one in the wrong place.
  • "SOCKS server did not return a suitable authentication method." In e.InnerException. ProxyTunnelError. The SOCKS5 proxy wanted a login and got none.
  • "Failed to resolve the destination host to an IPv4 address." In e.InnerException. ProxyTunnelError. socks4 could not look up the site on your machine.
  • Connection refused, in the language of your Windows. ConnectionError. Nothing listened on the proxy address and port.

For more on the login side of a 407, see our 407 guide.

Check that the proxy carries the traffic

Compare the address a site sees with and without the proxy:

using var direct = new HttpClient();
using var proxied = new HttpClient(new SocketsHttpHandler { Proxy = new WebProxy("http://203.0.113.7:8080") });

string real = await direct.GetStringAsync("https://httpbin.org/ip");
string viaProxy = await proxied.GetStringAsync("https://httpbin.org/ip");
Console.WriteLine($"{real}\n{viaProxy}");

We compiled this check but did not run it, since it calls a public site. If both lines show the same address, the request never used the proxy. Our proxy checker tests a pasted proxy for status, speed, anonymity, country and network.

Where to go from here

Two habits keep .NET proxy code alive unattended. Reuse one client with a timeout, and check an exit before you trust it. For a live pool to test against, the free proxy list is checked every few minutes.

The cURL guide is the quickest way to test a proxy before you wire it into C#. The Python requests guide is a close sibling, and proxies for web scraping covers choosing a proxy type. When a project moves to production, our paid pools give you addresses that nobody else is using up. The developer docs cover keys, orders, plan generation and the rules that make a retried call safe.

Sources

Frequently asked questions

How do I set a proxy on HttpClient in C#?
Put a WebProxy on the Proxy property of the handler, then pass the handler to HttpClient: new HttpClient(new SocketsHttpHandler { Proxy = new WebProxy("http://host:port") }). An HttpClient instance has no proxy property. In our test on .NET 10, an https:// request then went through the proxy in a CONNECT tunnel, and an http:// request went to the proxy as a plain request.
Where does the proxy username and password go?
In the Credentials property of the WebProxy: new WebProxy("http://host:port") { Credentials = new NetworkCredential("user", "pass") }. In our test, a user:pass written into the WebProxy address was ignored, and a Proxy-Authorization default header failed for https:// sites. HttpClient sends the login only after the proxy answers 407, so the proxy sees one request without it first.
Does HttpClient use HTTP_PROXY and HTTPS_PROXY?
Yes, when no proxy is set on the handler. But HTTP_PROXY only covers http:// addresses. In our test, an https:// request with only HTTP_PROXY set went straight to the site. Set HTTPS_PROXY, or ALL_PROXY for both. NO_PROXY skips hosts, a login in the variable works, and UseProxy = false on the handler ignores all of them.
Does .NET support SOCKS5 proxies?
Yes, since .NET 6. Use new WebProxy("socks5://host:port") and put the login in Credentials. In our test, socks5 and socks4a sent the host name to the proxy, while socks4 resolved it on our own machine first. socks4 and socks4a have no password login: .NET sends only the user name, from Credentials or from the proxy variable.
What does The proxy tunnel request to proxy failed with status code 407 mean?
The proxy refused to open the tunnel because it got no valid login. Check that the login is in WebProxy.Credentials, not in the address and not in a request header, and that the password is right. Since .NET 8, the exception also carries HttpRequestError.ProxyTunnelError, which is the same in every language of Windows.
Should I create a new HttpClient for every proxy?
No. Keep one HttpClient and give its handler your own IWebProxy that returns a different proxy each time. In our test, one client made 4 requests, called GetProxy 4 times and alternated between two proxies. Microsoft recommends one long-lived client with PooledConnectionLifetime set, for example to 2 minutes.

Proxies that don't die mid-job

Residential, ISP, datacenter and mobile, verified by the same engine that runs tens of millions of checks. They read as a real device and hold up under load. Pay as you go, and your balance never expires. $0.44/GB is the 2,000 GB+ rate; a single gigabyte is $0.50/GB, with no minimum order.

129M+ proxy checks run · 100+ countries · HTTP / HTTPS / SOCKS · re-checked every few minutes · no signup

HProxy.

Honest guides and comparisons on proxies, scraping and staying unblocked, from the team that runs the network.

RSS feed