C# HttpClient proxy: 407 with the right login
WebProxy accepts an address with user:pass@ in it and then doesn't send that login. The proxy answers 407 and the password looks wrong when it isn't.
In short
Put the login on the proxy object, not into its address: new WebProxy("http://via.proxies.vision:9000") with Credentials = new NetworkCredential(user, pass). HttpClientHandler.Credentials is the website’s login and doesn’t reach the proxy either.
For an https:// URL, HttpClient asks the proxy to open a tunnel with CONNECT. A proxy that needs a login answers 407 Proxy Authentication Required, and HttpClient tries again with whatever credentials it has for the proxy. If it has none, the 407 is final:
System.Net.Http.HttpRequestException: The proxy tunnel request to proxy 'http://via.proxies.vision:9000/' failed with status code '407'.The credentials for the proxy come from exactly one place: WebProxy.Credentials (or HttpClientHandler.DefaultProxyCredentials). Two lookalikes don’t count. A user:pass@ written into the proxy address is kept in the address and ignored for authentication. And HttpClientHandler.Credentials answers a website that asks for a login, not the proxy. We ran each variant with the same, correct login:
The environment variable is the exception. A plain new HttpClient() on Linux or macOS reads HTTPS_PROXY, and there a login inside the URL is used: HTTPS_PROXY=http://USER:PASS@via.proxies.vision:9000 returned 200 in our test. So code that worked from the environment can break the moment the same string moves into a WebProxy.
Every message, reproduced
HttpClient on .NET 10.0.13 against via.proxies.vision. .NET 8 printed the same 407 message.
| What we set | Result |
|---|---|
| Correct login on WebProxy.Credentials | 200 |
| No credentials, or the login only in the address | HttpRequestException: The proxy tunnel request … failed with status code '407'. |
| Wrong password | … failed with status code '407'. |
| Wrong password, http:// URL | no exception: StatusCode 407, the proxy's error page as the body |
Username -country-usa | … failed with status code '403'. |
| Chicago with country de | … failed with status code '465'. |
socks5://…:11000 | 200 |
socks5h://…:11000 | NotSupportedException: Only the 'http', 'https', 'socks4', 'socks4a' and 'socks5' schemes are allowed for proxies. |
| socks5://, wrong password | HttpRequestException … inner SocksException: Failed to authenticate with the SOCKS server. |
https:// as the proxy scheme | The SSL connection could not be established … Cannot determine the frame size or a corrupted frame was received. |
| http:// to the SOCKS port | HttpIOException: The response ended prematurely. (ResponseEnded) |
UseProxy = false | 200, from our own IP |
403: a value in the username
Targeting goes into the proxy username. Our gateway refuses values it can’t read, such as a three-letter country code or a session label on the rotating port. A key it doesn’t know is ignored without an error.
465: no exit
The login and the username are fine, but no residential exit matches every filter right now. Drop the narrowest one, usually the city. The same three codes in other clients: Java, curl.
Tell proxy errors apart in code
Since .NET 8 a refused tunnel carries HttpRequestError.ProxyTunnelError, and for a 407 e.StatusCode is ProxyAuthenticationRequired. Catch on that instead of parsing the message. For http:// URLs there is no tunnel: the proxy’s 407 comes back as an ordinary response, so check StatusCode before you read the body.
SOCKS5 without the h
.NET accepts socks5:// and rejects socks5h://. That isn’t a gap: with a hostname that doesn’t exist, .NET’s socks5 got SOCKS server failed to connect to the destination. Received error code 0x01, so the name went to the proxy and was resolved there.
On our port 9000 every new connection gets a new exit. HttpClient, though, keeps its connection to the proxy open and sends the next request to the same host through the same tunnel. We made four requests with one client, then three with a new client each:
Choose on purpose
Don’t create a client per request to force rotation in a busy app; that exhausts sockets. If you need one IP for a while, put a session into the username and use port 10000: USER-country-de-session-job1-lifetime-10. If you need a new IP per request, a short PooledConnectionLifetime on SocketsHttpHandler limits how long a tunnel is reused: with 2 seconds, four requests three seconds apart came from four IPs.
A setup that works
A .NET console app (dotnet new console), Program.cs replaced with this. Ran unchanged with dotnet run on .NET 10 and 8.
using System.Net;
var user = Environment.GetEnvironmentVariable("PROXY_USER") + "-country-de";
var pass = Environment.GetEnvironmentVariable("PROXY_PASS");
var handler = new SocketsHttpHandler
{
// The login belongs on the WebProxy. A user:pass@ inside the proxy address is ignored.
Proxy = new WebProxy("http://via.proxies.vision:9000")
{
Credentials = new NetworkCredential(user, pass),
},
UseProxy = true,
};
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(30) };
try
{
Console.WriteLine(await client.GetStringAsync("https://ipinfo.io/json"));
}
catch (HttpRequestException e) when (e.HttpRequestError == HttpRequestError.ProxyTunnelError)
{
// 407: the login. 403: a value in the username the gateway can't read. 465: no exit matches.
Console.Error.WriteLine(e.Message);
Environment.Exit(1);
}{
"ip": "217.252.154.93",
"city": "Aschaffenburg",
"region": "Bavaria",
"country": "DE",
…For SOCKS5, change the address to socks5://via.proxies.vision:11000 and keep the credentials. The same login works on both ports.
Why does HttpClient get 407 from the proxy when the login is correct?
How do I set proxy credentials for HttpClient in C#?
Does HttpClient read HTTPS_PROXY?
Does HttpClient support SOCKS5 proxies?
Why do all my requests through a rotating proxy use the same IP?
Keep reading
Node.js fetch With a Proxy: undici ProxyAgent, NODE_USE_ENV_PROXY and Errors
Node's fetch ignores HTTPS_PROXY. Three ways to proxy it, the undici 8 'invalid onRequestStart method' trap, and where the proxy's 407 hides.
6 min read · #node.js#errors
wget Proxy Authentication: https_proxy, 407 and an Ignored HTTPS_PROXY
wget through a proxy with a login: which variables it reads (not HTTPS_PROXY), --proxy-user, what 'Proxy tunneling failed' means, and BusyBox wget.
5 min read · #command-line#authentication
Java Proxy Authentication: 407 With the Right Password (disabledSchemes)
Why Java's HttpClient and HttpURLConnection get 407 from a proxy even with the correct login, and the one property that fixes it.
5 min read · #errors#authentication
Residential proxies, pay per GB
HTTP and SOCKS5. Country, city and sticky sessions in the username. No subscription.