The commands on this page were run on curl 8.18 on Linux, 8–9 September 2026. Where output is shown, it is shown as it came back. Commands that need paid proxy credentials (authentication, provider routing) are shown as syntax; we verify them in the provider reviews.
curl is the fastest way to find out whether a proxy works, what the target site sees through it, and how long it takes. This is the reference we use ourselves for the proxy tests on this site. It covers the flags that matter, the mistakes that silently produce wrong results, and how to measure instead of guess.
The one flag: -x
curl -x http://proxy.example.com:8080 https://api.ipify.org
-x (long form --proxy) takes a URL: scheme, host, port. If you leave out the scheme, curl assumes http://. The response is the IP address the target saw, which should be the proxy’s, not yours.
Same thing through the environment, which every tool that uses libcurl also honours:
export https_proxy=http://proxy.example.com:8080
export http_proxy=http://proxy.example.com:8080
curl https://api.ipify.org
Note the two variables. http_proxy is used for http:// targets, https_proxy for https:// targets. Setting only one is the most common reason “the proxy works for some sites and not others”.
Username and password
curl -x http://user:password@proxy.example.com:8080 https://api.ipify.org
or, keeping the credentials out of the URL:
curl -x http://proxy.example.com:8080 -U user:password https://api.ipify.org
-U is --proxy-user. Do not confuse it with -u, which authenticates to the target site.
If your password contains @, : or /, the URL form breaks. Either use -U, or percent-encode the characters (@ → %40).
Residential providers put routing options into the username. A country selection typically looks like customer-USER-cc-US (Oxylabs) or brd-customer-ID-zone-residential-country-us (Bright Data). The exact syntax is in each provider’s dashboard and changes over time; the proxy itself does not care, it is just a string.
HTTP proxy for an HTTPS site: what actually happens
When the target is https://, curl does not send the request to the proxy in the clear. It sends CONNECT target:443 and the proxy opens a tunnel. From then on the traffic is encrypted end to end, and the proxy cannot see or modify the request, including headers.
Two practical consequences:
- A proxy that “adds headers” (
Via,X-Forwarded-For) can only do that on plainhttp://targets. On HTTPS it is blind. This is why anonymity tests are done against anhttp://echo endpoint. - A proxy that is configured to forbid CONNECT will work for
http://and fail forhttps://withReceived HTTP code 403 from proxy after CONNECT. Free proxy lists are full of these.
To see the tunnel negotiation:
curl -v -x http://proxy.example.com:8080 https://api.ipify.org 2>&1 | grep -E 'CONNECT|Proxy|< HTTP'
SOCKS5, and the DNS leak most people do not know about
curl -x socks5://proxy.example.com:1080 https://api.ipify.org
There are two SOCKS5 schemes in curl and they behave differently:
| Scheme | Who resolves the hostname |
|---|---|
socks5:// | your machine, then sends the IP to the proxy |
socks5h:// | the proxy |
With socks5://, the DNS query for api.ipify.org leaves your own network. The target site sees the proxy’s IP, but your DNS resolver, and anyone watching it, sees which sites you visit. For scraping that also means geo-targeted DNS answers come from your location, not the proxy’s. Use socks5h:// unless you specifically want local resolution. The same pair exists for SOCKS4: socks4:// and socks4a://.
Headers that make you look like a browser
A bare curl request announces itself as curl/8.18.0. Most sites block that before looking at the IP. Minimum viable disguise:
curl -x http://proxy:8080 \
-A 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.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 -L \
https://www.example.com/
--compressed asks for gzip/brotli and decodes it, which is what a browser does and what saves you bandwidth on residential proxies that bill per gigabyte. -L follows redirects. Keep -L: several sites (Walmart, for example) block by redirecting to a /blocked page with status 200, and without following the redirect you never see that.
This is exactly the header set our test script sends, and it is enough to get a real page from Amazon and Nike from a datacenter IP. It is not enough for Zillow, Walmart or Indeed, which block the address itself; see what happens without a proxy.
Measure, do not guess: -w
curl can print timings and metadata after the transfer. This is the line we use for every proxy measurement:
curl -s -o /dev/null -x http://proxy:8080 \
-w 'status=%{http_code} connect=%{time_connect}s ttfb=%{time_starttransfer}s total=%{time_total}s bytes=%{size_download} final_url=%{url_effective}\n' \
https://www.amazon.com/s?k=airpods
Output from a Hetzner datacenter IP, no proxy, 8 September 2026:
status=200 connect=0.004s ttfb=0.204s total=0.817s bytes=174390 final_url=https://www.amazon.com/s?k=airpods
The same command from the same IP one day later, after a handful of test requests:
status=503 connect=0.004s ttfb=0.148s total=0.149s bytes=2671 final_url=https://www.amazon.com/s?k=airpods
Same URL, same headers. The second one is Amazon’s “Robot Check” page: 2.6 KB instead of 174 KB, status 503. Nothing in your code changed; the IP’s reputation did. That is what the bytes field is for: a page that is suddenly 65 times smaller is not the page.
What each field tells you:
time_connect: TCP handshake. Through a proxy this is the handshake to the proxy; a slow proxy shows here.time_starttransfer: time to first byte. The target’s processing plus the proxy’s forwarding. With residential proxies this is where the cost shows: two extra hops through a consumer connection.url_effective: where you ended up after redirects. If it is not the URL you asked for, read the page before trusting it.%{exitcode}(curl ≥ 7.75) gives curl’s own error code inside-w, useful in scripts that must tell “site returned 403” from “proxy never answered”.
Timeouts
curl -x http://proxy:8080 --connect-timeout 5 -m 20 https://www.example.com/
--connect-timeout limits the handshake, -m (--max-time) limits the whole transfer. Set both. A dead proxy in a free list otherwise costs you the default, which is effectively forever. In our free-proxy tests, anything that does not connect in 5 seconds does not connect at all.
Rotating through a list
Providers with a rotating gateway give you one host:port and change the exit IP behind it per request. Verify that it actually rotates:
for i in 1 2 3 4 5; do curl -s -x http://user:pass@gateway:7777 https://api.ipify.org; echo; done
Five different addresses means rotation works. Five identical addresses means you are on a sticky session, or the gateway is not doing what the brochure says. Our provider tests report this as rotation N/5.
For your own list of proxies:
while read -r p; do
printf '%-28s ' "$p"
curl -s -o /dev/null -x "$p" --connect-timeout 5 -m 12 -w '%{http_code} %{time_total}s\n' https://api.ipify.org || echo fail
done < proxies.txt
Errors you will see and what they mean
| Message | Meaning |
|---|---|
curl: (7) Failed to connect to api.ipify.org port 443 via 127.0.0.1 after 0 ms: Could not connect to server | proxy is down or the port is wrong; note the target hostname in the message even though it was the proxy that refused |
Received HTTP code 407 from proxy after CONNECT | proxy wants authentication, or rejected your credentials |
Received HTTP code 403 from proxy after CONNECT | proxy forbids HTTPS tunnelling (common on free lists) |
Received HTTP code 502 from proxy after CONNECT | proxy accepted you but could not reach the target |
curl: (28) Operation timed out | no answer within -m; on Akamai-protected sites this can be the block itself |
curl: (92) HTTP/2 stream was not closed cleanly | the site reset the connection mid-stream; Best Buy does this to datacenter IPs |
curl: (56) Recv failure | connection dropped; with a proxy, usually the proxy gave up |
curl: (35) SSL connect error | TLS handshake failed; a proxy doing MITM, or a broken CONNECT |
Things that are not curl’s fault
- Status 200 with the wrong page. Google returns a JavaScript-only shell with status 200 to non-browsers; it contains zero results. Check the body for something that only the real page has (we grep for the
<title>of a real result page). socks5://DNS leak. See above. Usesocks5h://.- Only one of
http_proxy/https_proxyset. Half your requests bypass the proxy. -uinstead of-U. Your proxy password goes to the target site.
The script behind this page
The measurements on this site come from a Node script that shells out to curl with exactly the flags above, once per target per provider, and stores the JSON. It is in the openwebdata repository under tyokalut/proxytesti/. If you want to reproduce a number, that is where it came from.
