Using Proxies with cURL: The Core Flags
If you have ever seen a 403 Forbidden on a perfectly valid cURL request, the problem is rarely your headers — it is your IP. Using proxies with cURL is the fastest way to change that, and cURL ships with first-class support for both HTTP and SOCKS5 tunnels. The two flags you will reach for 90% of the time are -x (or its long form --proxy) and --socks5-hostname.
For an HTTP proxy, the syntax is short:
curl -x http://gate.proxyhat.com:8080 https://httpbin.org/ipFor SOCKS5, you have two variants and the difference matters. --socks5 resolves DNS locally and sends the resolved IP to the proxy; --socks5-hostname (equivalently socks5h://) sends the hostname to the proxy and lets it resolve DNS. The second form is what you almost always want, because it prevents your local resolver from leaking the target domain to your ISP or DNS provider.
# Resolves DNS locally — can leak the hostname
curl --socks5 gate.proxyhat.com:1080 https://httpbin.org/ip
# Resolves DNS at the proxy — preferred
curl --socks5-hostname gate.proxyhat.com:1080 https://httpbin.org/ip
# Equivalent URL form
curl -x socks5h://gate.proxyhat.com:1080 https://httpbin.org/ipThe socks5h:// scheme is documented in the official cURL manual and is the same convention used by Python's requests and Go's golang.org/x/net/proxy. Treat socks5:// as a footgun: it works, but it can expose which domains you are querying even when the traffic itself is tunneled.
Authentication and Geo-Targeting in the Username
ProxyHat encodes credentials, country, city, and session stickiness directly in the proxy username. This is convenient because cURL's --proxy-user flag accepts any string, and URL-embedded auth works everywhere — including inside HTTP_PROXY environment variables.
Basic authenticated request:
curl -x http://gate.proxyhat.com:8080 \
--proxy-user 'USERNAME:PASSWORD' \
https://httpbin.org/ipGeo-targeted request — exit from a US IP in New York:
curl -x http://gate.proxyhat.com:8080 \
--proxy-user 'user-country-US-city-newyork:pass' \
https://httpbin.org/ipSticky session — keep the same exit IP for a multi-step login flow:
curl -x http://gate.proxyhat.com:8080 \
--proxy-user 'user-country-US-session-abc123:pass' \
https://example.com/loginBecause the session ID is just part of the username, you can generate a fresh sticky identity per job with uuidgen or openssl rand -hex 8. The ProxyHat SDK wraps these same endpoints, so anything you prototype in cURL translates directly to a Python or Node.js worker later.
The Environment-Variable Workflow
Hardcoding -x on every invocation gets tedious fast. cURL honors the standard proxy environment variables, which means you can set them once per shell session and forget the flag entirely.
| Variable | Used for | Example |
|---|---|---|
HTTP_PROXY | Plain HTTP requests | http://gate.proxyhat.com:8080 |
HTTPS_PROXY | HTTPS requests (most modern traffic) | http://gate.proxyhat.com:8080 |
ALL_PROXY | Fallback for any scheme cURL does not match above | socks5h://gate.proxyhat.com:1080 |
NO_PROXY | Hosts that should bypass the proxy | localhost,127.0.0.1,.internal.corp |
Reusable shell setup:
export HTTP_PROXY="http://user-country-US:pass@gate.proxyhat.com:8080"
export HTTPS_PROXY="http://user-country-US:pass@gate.proxyhat.com:8080"
export ALL_PROXY="socks5h://user-country-US:pass@gate.proxyhat.com:1080"
export NO_PROXY="localhost,127.0.0.1,.internal.corp"
# Now any cURL call is proxied automatically
curl https://httpbin.org/ipIf you want this to persist per-project without polluting your global profile, drop it into a .env file and source it in your script. For ad-hoc use, a ~/.curlrc file is even cleaner — cURL reads it on every invocation:
# ~/.curlrc
proxy = "http://user-country-US:pass@gate.proxyhat.com:8080"
compressed
silent
show-error
connect-timeout = 15
max-time = 60
retry = 3
retry-all-errorsYou can also keep multiple config files and switch with -K:
curl -K ~/.curlrc.residential https://httpbin.org/ip
curl -K ~/.curlrc.datacenter https://httpbin.org/ipThis is the pattern the ProxyHat team recommends for CI pipelines: one config per region, selected by the job runner. See the ProxyHat docs for the full list of username flags.
Residential vs Datacenter: Why It Matters for Hard Targets
Not all proxies are equal. Datacenter IPs come from hosting providers (AWS, Hetzner, DigitalOcean) and are trivially fingerprintable — many anti-bot vendors publish ASN blocklists that flag them automatically. Residential proxies come from real ISP-assigned ranges, so they look like ordinary consumer traffic.
Practical consequences:
- SERP scraping: Google and Bing return CAPTCHAs on datacenter IPs after as few as 5–10 automated requests; residential IPs typically sustain hundreds per IP before any challenge.
- E-commerce price monitoring: sites like Amazon and Walmart block entire datacenter ASNs; residential exit IPs see the same prices a real shopper would.
- Ticketing and sneaker drops: queue systems fingerprint IP reputation; residential reduces the risk of instant queue rejection.
- Ad verification: you need to see the creatives a real user in a specific city sees, which requires geo + residential.
For deeper use-case context, see web scraping and SERP tracking on the ProxyHat site.
Worked Example: Rotating IPs in a Bash Loop
Here is a production-shaped pattern — a list of URLs, a per-request session ID, retries on any error, and timing diagnostics via -w:
#!/usr/bin/env bash
set -euo pipefail
URLS=("https://example.com/page1" "https://example.com/page2" "https://example.com/page3")
GATE="http://gate.proxyhat.com:8080"
FMT="http_code=%{http_code} time_total=%{time_total}s remote_ip=%{remote_ip}\n"
for url in "${URLS[@]}"; do
session="$(openssl rand -hex 8)"
curl --proxy "$GATE" \
--proxy-user "user-country-US-session-${session}:pass" \
--retry 5 \
--retry-all-errors \
--retry-delay 2 \
--connect-timeout 15 \
--max-time 60 \
--compressed \
-s -w "$FMT" \
-o /dev/null \
"$url"
doneKey things this script gets right:
--retry-all-errorsretries on HTTP 4xx and 5xx, not just transport errors — essential when a target returns transient 429s.--retry-delay 2backs off 2 seconds between attempts, giving the target room to cool down.-wemitshttp_code,time_total, andremote_ipso you can pipe to a CSV and spot slow exits or repeated IPs.- A fresh
session-*per URL forces ProxyHat to assign a new exit IP each iteration.
If you want sticky behavior within a single logical job (e.g., a multi-page crawl that must look like one user), reuse the same session string across all URLs in that job.
Production Tips: TLS, Headers, and Parallelism
Pin TLS 1.3
Modern targets often reject TLS 1.0–1.1 handshakes and some fingerprint JA3 on older stacks. Pinning TLS 1.3 gives you a clean, modern handshake and avoids surprise downgrades:
curl --tlsv1.3 \
-x http://gate.proxyhat.com:8080 \
--proxy-user 'user-country-DE-city-berlin:pass' \
https://example.comSet a Realistic User-Agent
The default cURL User-Agent (curl/8.x.x) is an instant tell. Override it with a current browser string:
curl -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36' \
-H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' \
-H 'Accept-Language: en-US,en;q=0.9' \
--compressed \
-x http://gate.proxyhat.com:8080 \
--proxy-user 'user-country-US:pass' \
https://example.com--compressed tells cURL to advertise Accept-Encoding: gzip, deflate, br, which both reduces bandwidth and makes your request look like a real browser. Brotli support has been in cURL since 7.57.0 (2017), so there is no reason to skip it.
Parallelism with xargs -P
For embarrassingly parallel workloads, xargs -P is the simplest way to fan out cURL across cores:
cat urls.txt | xargs -n 1 -P 20 -I {} bash -c '
session="$(openssl rand -hex 8)"
curl -x http://gate.proxyhat.com:8080 \
--proxy-user "user-country-US-session-${session}:pass" \
--retry 3 --retry-all-errors \
--connect-timeout 15 --max-time 60 \
--compressed -s -w "%{http_code} %{time_total}s {}\n" \
-o "/tmp/$(echo {} | md5sum | cut -c1-12).html" \
"{}"
'-P 20 runs 20 concurrent cURL processes. Tune this against your ProxyHat plan's concurrency limit — pushing past it just queues requests locally and inflates time_total. A good starting point is 20–50 concurrent sessions for residential pools; datacenter pools can usually handle 100+.
Parallelism with curl --parallel
cURL 7.66.0+ supports native parallel transfers, which is more efficient than spawning processes:
curl --parallel \
--parallel-immediate \
--parallel-max 20 \
--config ~/.curlrc \
-o "page_#1.html" https://example.com/1 \
-o "page_#2.html" https://example.com/2 \
-o "page_#3.html" https://example.com/3The #1, #2 placeholders expand to the transfer index, so each URL gets its own output file. Combine with ~/.curlrc for shared proxy and retry settings.
Legal and Ethical Notes
Proxies are a tool, not a license. In the United States, accessing data behind authentication or in violation of a site's Terms of Service can implicate the Computer Fraud and Abuse Act, although courts have narrowed its reach in cases like Van Buren v. United States (2021). In the EU, the GDPR governs any personal data you collect, including IPs of individuals.
Practical guardrails:
- Prefer an official API when one exists — it is cheaper, faster, and legally cleaner than scraping.
- Respect
robots.txtand rate-limit yourself below human browsing speed for sensitive targets. - Do not scrape behind login walls without explicit permission.
- Do not store personal data you do not need; anonymize or aggregate early.
- For commercial resale of scraped data, check the target's ToS and any database-rights regime (e.g., EU sui generis database right).
If your target offers a paid API, the break-even is often surprisingly low. A developer spending 4 hours debugging a scraper against a moving anti-bot target has already spent more than a typical $50–$200/month API subscription.
Key Takeaways
- Use
-x http://gate.proxyhat.com:8080for HTTP and--socks5-hostname gate.proxyhat.com:1080(orsocks5h://) for SOCKS5 to avoid DNS leaks.- Encode country, city, and session in the ProxyHat username:
user-country-US-city-newyork-session-abc123:pass.- Prefer
HTTPS_PROXYandALL_PROXYenv vars, or a~/.curlrcselected with-K, over per-command-xflags.- Residential proxies beat datacenter IPs on hard targets because they come from real ISP ranges and avoid ASN blocklists.
- Always set
--retry,--retry-all-errors,--tlsv1.3, a realistic User-Agent, and--compressedin production scripts.- Fan out with
xargs -Porcurl --parallel, but stay within your plan's concurrency limit.- When a target offers an official API, use it — scraping is a fallback, not a default.
Ready to put this into practice? Grab your credentials from the ProxyHat dashboard, check available exit locations, and start with the residential pool. Everything you prototype in cURL today will work unchanged inside the ProxyHat SDK tomorrow.






