How To Use a Proxy With HttpClient in C#: From Setup to Production
If your C# application sends many requests from the same IP, the target will block it – 403 errors, CAPTCHAs, or rate limits. This is common in web scraping, price monitoring, and data collection. A proxy server routes requests through a different IP, so the target doesn't see yours. This guide covers HttpClient proxy setup from basics to production: authentication, SSL handling, IP rotation, and IHttpClientFactory patterns on .NET 8+.
Lukas Mikelionis
Last updated: Mar 04, 2026
8 min read

TL;DR
- Create a WebProxy with your proxy address, attach it to HttpClientHandler, and pass the handler to HttpClient
- For authenticated proxies, set NetworkCredential on the WebProxy
- In production, use IHttpClientFactory with SocketsHttpHandler to avoid socket exhaustion and recycle connections properly
- For IP rotation, use a backconnect proxy (single endpoint, automatic IP assignment per connection) instead of managing proxy lists manually
How proxy routing works
A proxy server sits between your application and the target. Instead of connecting directly, the request flows like this:
The target site sees the proxy's IP address, not yours. The tradeoff is latency – every request takes an extra network hop through the proxy. In our tests, proxied requests averaged roughly 2–3x the response time of direct requests. Residential proxies are slower than datacenter proxies because traffic routes through real ISP connections, and the overhead varies with proxy location and target server. For most scraping and data collection, this is acceptable because the alternative is getting blocked entirely.
Setting this up in C# involves 3 core components:
- WebProxy – holds the proxy server address, port, and optional credentials
- HttpClientHandler – the handler that tells HttpClient how to send requests (including through which proxy)
- HttpClient – the client itself, which receives the configured handler
If you're migrating from the older WebClient API, the concept is the same, but the implementation differs. WebClient is deprecated – HttpClient with WebProxy is the modern replacement.
Setting up your .NET project
You'll need the .NET 8 SDK installed. Check with:
If the command isn't recognized or returns a version below 8.0, download the .NET 8 SDK from dotnet.microsoft.com. Run the installer, restart your terminal, and run dotnet --version again to confirm.
Create a new console project:
System.Net.Http is the namespace that contains HttpClient and its supporting types – it's the core library for making HTTP requests in .NET. It's included by default in .NET 8 projects, so no using statement is needed for it. You will need to add using System.Net; explicitly when working with WebProxy and NetworkCredential, but no NuGet packages are required yet.
The production section later uses the Microsoft.Extensions.Http.Polly package for retry policies, but it isn't needed until then.
Making a basic HTTP request without a proxy
Before adding a proxy, this is what a normal request looks like. It goes directly from your machine to the target. Add this to your Program.cs file
Run it with dotnet run in your project folder.
Output:
That origin value is your actual IP address. httpbin.org is a free HTTP testing service – its /ip endpoint returns the IP address it sees from the incoming request. You can also use ip.decodo.com/ip for the same purpose. The rest of this guide uses httpbin.org to verify that proxy routing works.
Configuring HttpClient to use a proxy
Now that you understand what a proxy does and the three components involved, here's how to wire them together. The minimal setup:
The origin in the response should now show the proxy's IP, not yours – once you replace the placeholder with a working proxy address.
Proxy URL formats:
Format
Example
When to use
HTTP proxy
http://host:8080
Most common. Works for both HTTP and HTTPS target URLs
HTTPS proxy
https://host:8080
Encrypts the connection between your app and the proxy itself
Common proxy ports are 8080, 3128, and 8888.
Skipping the proxy for local addresses
If you're running services locally and don't want local traffic to go through the proxy:
You can also define a custom bypass list for specific domains:
This is useful in corporate environments where internal services shouldn't go through the external proxy.
Adding proxy authentication with credentials
Most paid proxy services, including Decodo's residential proxies, require authentication. The standard approach is to use NetworkCredential:
If the credentials are wrong, you'll get a 407 Proxy Authentication Required response. See common proxy error codes if you run into authentication issues.
Don't hardcode credentials
Never hardcode proxy credentials in source code. Use environment variables or a secrets manager:
To set these environment variables before running your application:
On Windows (PowerShell):
On Linux/macOS (terminal):
These are session-scoped – they last until you close the terminal. For a more permanent setup in development, add them to your project's launchSettings.json under environmentVariables.
For production applications, consider Azure Key Vault, AWS Secrets Manager, or HashiCorp Vault.
Alternative – IP whitelisting
Some proxy providers support IP whitelisting as an alternative to username/password authentication. You whitelist your server's IP in the provider's dashboard, and the proxy accepts connections from that IP without credentials. This simplifies the code (no NetworkCredential needed) but only works when your application runs from a fixed IP address.
Fetching a real webpage through the proxy
The examples so far have all used httpbin.org/ip to verify routing. A real-world request is different – you're fetching an actual webpage, not just checking your IP:
The main difference from the httpbin.org test is the User-Agent header. Most real websites expect a browser-like user agent, and some will block requests that send a generic or missing one.
Dealing with SSL certificate errors
SSL certificate errors are a common problem when first using proxies with HttpClient. You'll typically see something like this:
Why this happens
It depends on how the proxy handles HTTPS traffic:
- CONNECT tunneling – the proxy opens a raw TCP tunnel to the target. Your app negotiates encryption (the TLS handshake) directly with the target server, end-to-end. SSL validation works normally. Most residential proxy providers use this method.
- TLS-intercepting proxy – the proxy decrypts the traffic itself, inspects or modifies it, then re-encrypts it before forwarding to the target. The certificate your app sees belongs to the proxy, not the target domain. This is what triggers SSL errors.
How to handle it
For trusted proxy providers that use TLS interception, you can bypass certificate validation for the proxy connection:
Important:
- Only disable SSL validation for proxy providers you trust. Never do this with free or unknown proxies – you're allowing man-in-the-middle attacks.
- If your proxy uses CONNECT tunneling (most reputable providers do), you shouldn't need to disable validation at all. The TLS connection goes end-to-end between your app and the target.
- In production, log every bypassed validation so you can keep track of what's being skipped.
For details, see the SSL proxy guide.
Rotating proxies to avoid detection and blocks
Using a single proxy IP for all your requests makes you easy to detect. Anti-bot systems will eventually identify and block that IP.
There are 3 rotation strategies. Which one to use depends on your scale and budget.
1. Manual rotation from a proxy list
Maintain a pool of proxy addresses and cycle through them:
This works for small-scale use, but you're managing the proxy list, health checking, and rotation logic yourself. It also creates a new HttpClient per request, which at scale causes socket exhaustion (your machine runs out of available network ports).
2. Provider-managed rotation (backconnect proxies)
This is the recommended approach for most use cases. You connect to a single gateway endpoint, and the provider assigns a different IP from their pool on each new connection. Decodo's rotating proxy network, for example, has 115M+ residential IPs across 195+ locations, which is large enough to avoid IP repetition in most scenarios.
There's an important detail here: backconnect proxies assign a new IP when a new TCP connection reaches the gateway. Since HttpClient keeps connections open and reuses them across requests, multiple requests through the same connection will share the same IP. To force new connections (and new IPs), use SocketsHttpHandler with a short PooledConnectionLifetime:
In testing, this produced 5 unique IPs across 5 sequential requests – each connection recycled and the gateway assigned a fresh address. Each request gets a new connection (and therefore a new IP) because the pooled connection lifetime expires before the next request. Note: this works for sequential requests with a delay between them. For concurrent requests, multiple requests may share a connection – use separate HttpClient instances or the session-based approach below if you need distinct IPs in parallel. The proxy provider handles IP assignment, health checking, and switching to backup IPs when one goes down – all behind the scenes.
3. Session-based (sticky) rotation
Sometimes you need the same IP across multiple requests, for example, when a login flow spans several calls. Most providers support sticky sessions through a session parameter in the username:
The format depends on your provider. With Decodo, the session username format is user-USERNAME-session-SESSIONID-sessionduration-MINUTES. The sessionduration parameter controls how long the IP stays assigned (1–1440 minutes).
Comparing the 3 strategies
This is how the 3 approaches compare on the factors that matter most in practice:
Manual rotation
Backconnect (provider-managed)
Session-based (sticky)
Setup effort
High – maintain proxy list, health checks, rotation logic
Low – single endpoint, no rotation code
Low – session ID in username
IP diversity
Limited by your pool size
Millions of IPs (115M+ with Decodo)
One IP per session
Best for
Small-scale, specific proxy requirements
Most use cases, large-scale scraping
Login flows, multi-step interactions
Socket safety
Risk of exhaustion (new client per request)
Safe with SocketsHttpHandler
Safe (single client reuse)
Get residential proxies
Claim your 3-day free trial of residential proxies and explore full features with unrestricted access.
HttpClient proxy patterns for production
If you're building a quick script, the patterns above are sufficient. The rest of this section is for production applications that need to handle thousands of requests without leaking resources or failing silently.
Don't create a new HttpClient per request
This is the most common mistake with HttpClient in .NET, and it has caused outages in production services running behind proxies. Every new HttpClient(handler) creates a new handler and connection pool. When you dispose clients rapidly, the underlying sockets linger in a TIME_WAIT state (120 seconds on Windows, 60 seconds on Linux), which means your machine runs out of available ports. That is socket exhaustion, and you'll start seeing SocketException errors.
Use IHttpClientFactory
IHttpClientFactory manages HttpClient lifetimes, recycles handlers, and picks up DNS changes automatically. Here's a full production setup with proxy configuration:
The reason for using SocketsHttpHandler is that it gives direct control over how connections are reused and recycled (PooledConnectionLifetime, PooledConnectionIdleTimeout) and how many can run at the same time. On .NET 6+, it's the default handler internally anyway.
PooledConnectionLifetime forces connections to be recycled periodically, which picks up DNS changes. Without it, if the proxy provider changes their DNS records, your app will not notice until it restarts.
Making concurrent requests through a proxy
When you need to fetch multiple URLs in parallel, which is common in price monitoring and data collection, use Task.WhenAll with a shared proxy-configured client:
This works well with IHttpClientFactory. The MaxConnectionsPerServer setting on SocketsHttpHandler controls how many simultaneous connections go to the proxy endpoint.
Add retry logic with Polly
Proxy connections fail – networks are unreliable, and proxy servers go down. Polly is a .NET resilience library that handles retries, timeouts, and circuit breakers. Install the package:
Then configure retries:
This retries failed requests up to 3 times with exponential backoff – each retry waits longer than the previous one (2s, 4s, 8s) to give the proxy or target time to recover. It handles transient HTTP errors (5xx, 408) and also retries on 407 Proxy Authentication Required, which can occur when a proxy gateway is under load.
Handle proxy-specific errors
To handle errors properly, catch specific exceptions instead of generic ones:
Use environment variables for proxy configuration
You can also configure proxy settings through environment variables instead of code. On .NET 6+, HttpClient reads HTTP_PROXY, HTTPS_PROXY, and NO_PROXY on all platforms – Windows, Linux, and macOS. Credentials go directly in the proxy URL:
This works well for Docker and Kubernetes setups where you set environment variables in Docker Compose or Kubernetes manifests without changing any code. In a Dockerfile or Kubernetes secret, set HTTP_PROXY and HTTPS_PROXY to your proxy URL – your C# code doesn't need any proxy-specific logic.
You can also set a global default for all HttpClient instances in code using the static HttpClient.DefaultProxy property. Environment variables are usually the better choice because they don't require code changes across environments.
Choosing the right C# HttpClient proxy approach
Setting up a C# HttpClient proxy starts with 3 components: a WebProxy, a handler, and the client. Moving to production means getting the details right: credential management, SSL handling, rotation strategies, and proper connection management with IHttpClientFactory.
To summarize the recommended approach for each scenario:
- Quick scripts and prototyping – WebProxy + HttpClientHandler is sufficient.
- Production applications – use IHttpClientFactory with SocketsHttpHandler. Add Polly for retries. Store credentials in environment variables or a secrets manager.
- IP rotation – use a backconnect proxy service like Decodo instead of building your own rotation logic. The provider handles IP rotation, dead proxy replacement, and session management automatically.
- Browser automation – if you're using Selenium alongside HttpClient, the Decodo Selenium repository has ready-made C# examples with proxy configuration.
For a broader look at C# web scraping beyond proxy configuration – HTML parsing, JavaScript-rendered pages, and scaling – see the C# web scraping guide.
If you don't have a proxy provider yet, Decodo offers a free trial. Follow the residential proxy quickstart to get your credentials from the dashboard, then paste them into any code example in this guide. Decodo also has setup guides for 50+ tools if you need proxy configuration beyond HttpClient.
C# HttpClient proxy quick reference
This table summarizes every pattern covered in this guide:
Task
Pattern
Basic proxy
new WebProxy("http://host:port") → HttpClientHandler → HttpClient
Authentication
WebProxy.Credentials = new NetworkCredential(user, pass)
Bypass local
WebProxy.BypassProxyOnLocal = true
Rotate IPs (backconnect)
SocketsHttpHandler + PooledConnectionLifetime = TimeSpan.FromSeconds(1)
Sticky session
Username format: user-NAME-session-ID-sessionduration-MIN
Production setup
IHttpClientFactory + ConfigurePrimaryHttpMessageHandler
Retry on failure
HttpPolicyExtensions.HandleTransientHttpError().WaitAndRetryAsync(3, ...)
Env var proxy
Set HTTP_PROXY / HTTPS_PROXY = http://user:pass@host:port
ConclusionÂ
Proxies handle the IP side of the problem. But targets also check request timing and user agents – if every request hits at the same interval with the same headers, the proxy IP doesn't help much. Add random delays between requests, rotate user agents, and make sure failed requests get retried instead of dropped. The patterns in this guide cover the proxy setup. The rest depends on what you're scraping and at what scale.
About the author

Lukas Mikelionis
Senior Account Manager
Lukas is a seasoned enterprise sales professional with extensive experience in the SaaS industry. Throughout his career, he has built strong relationships with Fortune 500 technology companies, developing a deep understanding of complex enterprise needs and strategic account management.
Connect with Lukas via LinkedIn.
All information on Decodo Blog is provided on an as is basis and for informational purposes only. We make no representation and disclaim all liability with respect to your use of any information contained on Decodo Blog or any third-party websites that may belinked therein.