How to Use a Proxy with cURL (2026 Guide)
By Marcus Reiner · 2026-05-23 · 11 min read · Engineering
cURL + proxy in 30 seconds, or curl-impersonate for true browser fingerprint mimicry. Here's both.
The short answer: one flag and one credential string
To send a curl request through a proxy you need exactly two things: the -x (or --proxy) flag with your gateway host and port, and a username:password pair passed either inline or with -U. Everything else in this guide is about making that connection reliable, debuggable and safe to put in production.
The canonical form looks like this: curl -x http://USER:[email protected]:7000 https://httpbin.org/ip. If the response shows an IP that is not your own, the proxy is working. If it hangs, returns 407, or shows your real IP, the causes are almost always one of five things covered in the troubleshooting section below.
We test curl-based proxy setups against every provider we review, and IPRoyal remains the easiest to get running from a cold start: pay-as-you-go credit, a gateway that accepts standard username:password auth, and no dashboard gymnastics before your first successful request.
Basic HTTP and HTTPS proxy commands
curl treats the proxy protocol and the target protocol separately. The -x value describes how to reach the proxy; the URL describes what you are fetching through it. A plain HTTP proxy can still tunnel HTTPS traffic via CONNECT, which is why http:// in the proxy string with an https:// target is normal and correct.
Use --proxy-insecure only when your provider terminates TLS with a self-signed certificate on the proxy hop itself. It does not weaken the TLS session to the target site, but it should still be a deliberate choice rather than a reflex when something fails.
- Rotating residential gateway: curl -x http://user:[email protected]:7000 https://httpbin.org/ip
- Explicit credential flag: curl -x http://gate.example.com:7000 -U user:pass https://example.com
- SOCKS5 with remote DNS: curl -x socks5h://user:[email protected]:1080 https://example.com
- Verbose handshake trace: curl -v -x http://user:[email protected]:7000 https://httpbin.org/ip
- Timing breakdown: curl -w "connect:%{time_connect} total:%{time_total}\n" -o /dev/null -s -x http://user:pass@gate:7000 https://example.com
socks5 vs socks5h: the difference that breaks scrapers
With socks5://, curl resolves the hostname locally and sends an IP address to the proxy. With socks5h://, the hostname is sent to the proxy and resolved there. For proxy work you almost always want socks5h, because local DNS resolution leaks your real location to your resolver and can send you to a CDN edge node near you rather than near the exit IP.
This single character explains a large share of the cases where a geo-targeted proxy returns content for the wrong country. If your German residential IP keeps returning US pricing, check the scheme before you blame the pool.
Sticky sessions and rotation from the command line
Rotating gateways give you a new exit IP on every request by default. That is correct for stateless work like fetching product pages, and wrong for anything with session state: a login, a paginated dashboard, a multi-step checkout.
Providers expose stickiness through the username string rather than a separate host. Common patterns are user-session-abc123, user-sessid-abc123 or user-sticky-1 with a TTL between 1 and 30 minutes depending on the vendor. Generate the session token yourself so you can reuse it across the requests that belong to one logical flow, then discard it.
Country and city targeting works the same way: user-country-de-city-berlin. Because it is all inside the username, curl needs no extra flags, which makes the pattern trivial to script.
- One IP for a whole flow: -x http://user-session-run42:pass@gate:7000
- Per-request rotation: omit the session token entirely
- Country pinning: -x http://user-country-de:pass@gate:7000
- City pinning: -x http://user-country-us-city-newyork:pass@gate:7000
- Never hardcode session tokens in a loop - reuse per flow, then rotate
Keeping credentials out of your shell history
Inline credentials end up in ~/.bash_history, in ps output while the command runs, and in CI logs. For anything beyond a one-off test, move them into a .netrc file or environment variables.
Create ~/.netrc with a machine entry for your gateway host, chmod 600 it, then run curl --netrc -x http://gate.example.com:7000 https://example.com. curl reads the credentials without them ever appearing on the command line. Alternatively export http_proxy and https_proxy so every curl call in the shell uses the proxy with no flags at all - useful for quick debugging sessions, dangerous if you forget you set it.
In CI, use the platform secret store and inject at run time. Rotating a leaked proxy credential is cheap; explaining a month of unexpected bandwidth on someone else's scraping run is not.
Reading curl output when something fails
Verbose mode is the whole diagnostic toolkit. curl -v prints the CONNECT request to the proxy, the proxy response line, the TLS handshake with the target, and the final HTTP status. The layer that fails tells you where the problem is: a failed CONNECT is a proxy or credential issue, a failed handshake is TLS or fingerprinting, a 403 after a successful handshake is the target site blocking the exit IP.
Add -w with timing variables to separate slow proxies from slow targets. If time_connect is 1.8s and time_total is 2.0s, the proxy hop is your bottleneck and a closer gateway region will fix it. If time_connect is 90ms and time_total is 6s, the target is slow or you are being tarpitted.
Common curl proxy errors and what actually causes them
Most failures fall into a handful of buckets, and the fix is usually configuration rather than provider quality. Work through them in order before opening a support ticket.
- 407 Proxy Authentication Required - wrong credentials, or your IP is not on the allowlist when the account is set to IP-based auth
- Connection refused - wrong port; many providers use different ports for rotating vs sticky vs datacenter pools
- Empty reply from server - you sent an HTTPS request to an HTTP-only port, or the gateway dropped the CONNECT
- SSL certificate problem - a transparent MITM proxy in your own network, not the provider; test from a different network before adding -k
- Returns your real IP - shell variables not expanded (single quotes around the -x value), or an ignored proxy because the target matched no_proxy
- 403 from the target - the exit IP is blocked; rotate, switch to residential, or add a realistic user agent
Making requests look human
A clean residential IP with a default curl fingerprint still gets blocked, because curl announces itself in the User-Agent and presents a TLS fingerprint no browser produces. At minimum set a current browser User-Agent, an Accept-Language header, and Accept-Encoding, and let curl follow redirects with -L.
For targets behind Cloudflare, DataDome or PerimeterX, header hygiene is not enough - the JA3 TLS hash gives you away regardless of headers. Either move to a build of curl compiled against a browser TLS profile, or use a managed unblocking endpoint where the vendor maintains the fingerprint for you. That is the trade we describe in our Cloudflare bypass guide, and it is why teams with hard targets end up on a scraping API instead of raw proxies.
A production-ready wrapper script
Once the single command works, wrap it. A useful wrapper does four things: reads credentials from the environment, generates a session token per logical flow, retries on 429 and 5xx with exponential backoff, and logs the exit IP alongside the status so you can correlate failures with specific subnets.
Cap retries at three and add jitter. Hammering a target that just rate-limited you is the fastest way to escalate from a soft 429 to a hard IP ban, and on a rotating pool that damage is shared with every other customer using those IPs.
- Read PROXY_USER and PROXY_PASS from the environment, never from the script body
- One session token per flow, new token per flow
- Retry 429/500/502/503/504 up to 3 times with jitter
- Log %{http_code}, %{time_total} and the resolved exit IP for every request
- Fail loudly on 407 rather than retrying - it is never transient
Cost reality check
curl is bandwidth-billed like everything else. A residential GB in 2026 runs roughly $1.75 at the budget end to $8-15 at the enterprise end, and 1 GB covers approximately 5,000-10,000 lightweight HTML fetches or 1,500-3,000 image-heavy product pages.
Two habits cut spend materially: request compression (Accept-Encoding: gzip, which curl handles with --compressed) and skipping assets you do not parse. If you only need prices, do not download images. Teams routinely halve their bill with those two changes before touching provider pricing.
For learning and low-volume work, pay-as-you-go beats a subscription. IPRoyal and Webshare both let you start with a few dollars of credit, which is the right way to validate a target before committing to a plan.
Where to go from here
Once curl works end to end, port the same credential and session pattern into your language of choice - the gateway semantics are identical in Python requests, Node fetch and Go. Our proxy authentication explainer covers the IP-allowlist alternative to username:password, and our proxy error codes reference maps every status you will meet in production to a specific fix.
If your target sits behind serious bot management, read the JavaScript rendering guide next: at that point the question stops being which proxy and starts being which rendering layer.
Frequently Asked Questions
How do I use curl with a proxy that needs a username and password?
Pass them inline as curl -x http://user:pass@host:port https://example.com, or keep them off the command line with -U user:pass or a chmod 600 ~/.netrc file plus --netrc.
Why does curl still show my real IP?
Usually the -x value was single-quoted so shell variables never expanded, the target matched your no_proxy list, or the request failed and curl fell back to a direct connection. Run curl -v and confirm a CONNECT line appears.
What is the difference between socks5 and socks5h in curl?
socks5 resolves DNS locally and leaks your real location to your resolver; socks5h resolves at the proxy. Use socks5h for any geo-targeted work.
How do I keep the same IP across several curl requests?
Add a session token to the username, for example user-session-abc123, and reuse that exact username for every request in the flow. Most providers hold the IP for 1 to 30 minutes.
What does 407 Proxy Authentication Required mean?
The proxy rejected your credentials. Either the username or password is wrong, or your account uses IP allowlisting and the machine making the request is not on the list. It is never a transient error, so do not retry it.
Can curl bypass Cloudflare with a good proxy?
Not on its own. A clean residential IP helps, but curl's TLS fingerprint is identifiable. You need a browser-matched TLS build or a managed unblocking endpoint for protected targets.
Which proxy provider is easiest to test with curl?
IPRoyal for pay-as-you-go credit and standard gateway auth, Webshare if you want a free tier to confirm your command syntax before spending anything.