Documentation Python quickstart Blog Free tools hello@quanticdata.ioLog in

curl Proxy: Setup, Auth, SOCKS5 and Errors

curl sends a request through a proxy to the target server

To use a proxy with curl, pass -x (or --proxy) with the scheme, host and port, and -U user:pass when the proxy needs credentials. Everything else that goes wrong with curl and proxies is authentication, the SOCKS scheme, HTTPS tunnelling, or an exit code you have not read yet.

The curl proxy command in one line

curl takes the proxy as an argument, not as configuration. This is the whole thing:

curl -x http://198.51.100.10:8080 https://example.com/

-x and --proxy are the same option. The value is a URL: scheme://host:port. Two defaults surprise people. If you leave out the scheme, curl assumes an HTTP proxy. If you leave out the port, curl uses 1080, which is the SOCKS port and almost never what your HTTP proxy listens on, so always write the port.

The proxy applies to every URL in that command, and only that command. Nothing is saved. To confirm the request really left through the proxy, ask an echo endpoint what IP it saw:

curl -x http://198.51.100.10:8080 https://api.ipify.org?format=json

If the JSON shows the proxy exit IP rather than your own address, the route works. If you want the same check without writing a command, the ../../tools/proxy-tester/ tool runs it in the browser and reports the exit IP, country and latency.

curl proxy authentication

Most commercial proxies authenticate by username and password. curl accepts credentials in two places, and they are not equivalent.

curl -x http://gate.example.com:8080 -U myuser:mypass https://example.com/
curl -x http://myuser:mypass@gate.example.com:8080 https://example.com/

-U is short for --proxy-user. Inline credentials in the proxy URL do the same job, with one operational difference: the full command line, password included, is visible to anyone who can list processes on the machine and lands in your shell history. On a shared box or a CI runner, prefer -U with the value read from an environment variable, or put the proxy line in a config file with restrictive permissions.

Special characters have to be percent-encoded when the credentials sit inside the URL. A password containing @ or : will split the URL in the wrong place and produce a name-resolution error rather than an auth error, which sends people hunting for the wrong bug.

When the proxy rejects the credentials it answers with HTTP 407, not 401. curl will print the status if you add -v. The full diagnosis of that status, including why a working username suddenly starts failing, is in ../how-to-fix-407-proxy-authentication-required/.

SOCKS5 with curl, and the one character that matters

curl speaks SOCKS4, SOCKS4a, SOCKS5 and SOCKS5 with hostname resolution at the proxy. You pick with the scheme:

curl -x socks5://198.51.100.10:1080 https://example.com/
curl -x socks5h://198.51.100.10:1080 https://example.com/

With socks5, curl resolves the target hostname locally and sends the resulting IP to the proxy. With socks5h, the hostname travels to the proxy and is resolved there. The extra h is the difference between your local resolver seeing every domain you visit and the proxy seeing it. For scraping and for geo-targeted requests, socks5h is nearly always the one you want, because local resolution can also hand you an IP from the wrong region before the proxy ever gets involved. The long-form flags --socks5 and --socks5-hostname do the same thing.

SOCKS5 carries any TCP traffic, not only HTTP, which is why it is the scheme of choice for tools that are not browsers. Our ../../socks5-proxies/ run on the same pools as the HTTP endpoints, so the choice is a protocol decision rather than a product one.

Setting the proxy once instead of on every command

Three mechanisms exist, in increasing order of stickiness.

Environment variables. curl reads http_proxy, https_proxy, all_proxy and no_proxy. One quirk is worth memorising: http_proxy is honoured in lowercase only, while the others are read in either case. That rule exists because CGI environments turn an incoming Proxy: header into an uppercase variable, and curl refuses to be redirected by a remote header.

export https_proxy=http://gate.example.com:8080
export no_proxy=localhost,127.0.0.1,.internal.example.com
curl https://example.com/

A config file. curl reads .curlrc from your home directory on every invocation. A line such as proxy = "http://gate.example.com:8080" makes the proxy the default for every command, including commands run by scripts you forgot about. Run curl -q to ignore the file for one call.

An alias. A shell alias is the least surprising option because it only fires when you type that name, and it leaves plain curl untouched for everything else.

Whichever you pick, add the exceptions. --noproxy on the command line, or no_proxy in the environment, keeps localhost and internal hostnames off the proxy. Forgetting that is the usual cause of a health check that starts timing out the day someone exports a global proxy.

HTTPS through a proxy: CONNECT, and the certificate question

When the target URL is HTTPS and the proxy is an HTTP proxy, curl does not send your request to the proxy in the clear. It issues a CONNECT request, the proxy opens a raw tunnel to the target on port 443, and the TLS handshake happens end to end inside that tunnel. The proxy sees the hostname and the byte counts, not the content. -v shows the CONNECT line and the handshake that follows it.

Two related flags get confused constantly:

  • -k (--insecure) stops curl verifying the target server's certificate.
  • --proxy-insecure stops curl verifying the certificate of an HTTPS proxy, meaning a proxy you speak TLS to.

If a certificate error appears only when the proxy is enabled, the proxy is almost certainly intercepting TLS and presenting its own certificate, which is normal for a corporate gateway and abnormal for a commercial scraping proxy. The correct fix is to trust that gateway's root certificate with --cacert or by installing it in the system store, not to reach for -k. Disabling verification hides a real man-in-the-middle from you as effectively as it hides the corporate one.

Reading the failure: curl exit codes with a proxy

curl's exit code names the hop that broke. Reading it saves an hour of guessing, because several proxy failures produce identical-looking messages on screen.

ExitNameWhat it means with a proxy set
5COULDNT_RESOLVE_PROXYThe proxy hostname does not resolve. A typo, a dead gateway name, or a malformed credential string that broke the URL.
6COULDNT_RESOLVE_HOSTThe target hostname does not resolve locally, which with socks5 is your resolver failing, not the proxy's.
7COULDNT_CONNECTThe proxy host resolves but refuses the TCP connection: wrong port, firewall, or an IP whitelist you are not on.
28OPERATION_TIMEDOUTThe connection opened and then stalled. Common on residential exits when the target is slow or the exit dies mid-transfer.
35SSL_CONNECT_ERRORThe TLS handshake failed inside the tunnel, often an intercepting proxy or a TLS version mismatch.
56RECV_ERRORThe connection dropped while receiving. With a proxy this frequently means the CONNECT tunnel was closed by the gateway.
97PROXYThe proxy handshake itself failed, which SOCKS proxies return when they reject the request outright.

Add -v before you theorise, and use --write-out to make a script's output machine-readable:

curl -x http://gate.example.com:8080 -o /dev/null -s \
  -w 'status=%{http_code} time=%{time_total}\n' https://example.com/

Browser-side and library-side versions of the same failures are worth recognising too, since the same proxy misconfiguration shows up as ../err-tunnel-connection-failed/ in Chrome and as a ProxyError traceback in Python.

What curl actually gets back, measured

curl retrieves HTML. It does not run JavaScript, so whether the data you want is in the response depends entirely on how the target renders. On 9 September 2026 we ran the ../../seo-audit/ API over six well-known pages from a US exit. Each URL was fetched twice, once as a plain HTTP client with no JavaScript and once fully rendered, and we counted the words each view produced.

PageWords without JSWords renderedShare visible to curl
Hacker News front page589589100%
Vercel home279279100%
Notion home22522899%
Walmart search, "laptop"1,0094,27324%
Airbnb search, Rome129127shell only, both views
Zara US home05570%

Three of the six hand curl the complete page. One gives it a quarter. One returns an empty document that only becomes a page after scripts run. The Airbnb result is the interesting case: neither view contains the listings, because the search results live in a hydration JSON blob rather than in rendered text, so a browser would not have helped either. Reading that blob is cheaper and more reliable than rendering.

The practical rule: try curl first, look at the body, and only escalate when the data is genuinely missing. Escalation is not free, which is why our ../../web-scraping-api/ prices the plain fetch at $0.0002 per page and the JavaScript-rendered fetch at $0.001, five times more. Paying that on every URL when a fifth of your targets need it is the most common avoidable cost in a scraping budget.

Rotating IPs from a curl loop

A single proxy IP running a loop of requests is the easiest pattern to block. Rotating pools solve this at the gateway: you keep one endpoint in your command and the exit IP changes per request, or stays fixed for a session when you ask it to.

for i in $(seq 1 5); do
  curl -s -x http://gate.example.com:8080 -U myuser:mypass \
    https://api.ipify.org?format=json
  echo
done

Run that against a rotating endpoint and you should see five different addresses. Against a sticky session you will see one, which is what you want when a target ties a cart or a login to an IP. Country selection is usually a flag inside the username rather than a separate host, so a curl command targeting Germany differs from one targeting Brazil by a few characters. Our ../../rotating-proxies/ start at $0.50/GB and ../../residential-proxies/ at $0.80/GB on the Basic line, with country targeting included rather than sold as an add-on.

Two habits keep a curl loop honest. Add --retry 3 --retry-connrefused so a single dead exit does not kill the run, and add a delay between requests. A shell loop can hammer a small site harder than a browser ever would, and the block you earn that way is not the proxy's fault.

When to stop using curl

curl is the right tool for testing a proxy, reproducing a failing request, and pulling pages that render server-side. It stops being the right tool at three points: when the response needs JavaScript to become the page, when the target answers with a challenge instead of HTML, and when you need hundreds of URLs a minute with retries and structured output rather than a wall of markup.

At that point the choice is not curl versus a proxy, it is a raw fetch versus a managed one. A scraping API keeps the same request shape but adds rendering, retries and parsing behind one call, and bills per successful result. If you are still choosing the network underneath, ../residential-vs-datacenter-proxies/ covers which pool matches which target, and ../how-to-use-a-proxy-with-python-requests/ shows the same setup in Python once the shell prototype works. To move a working command into code, the ../../tools/curl-converter/ tool turns a curl line into Python, Node or Go.

Sources & further reading

FAQ

Quick answers on curl proxy.

Something else? Ask us →

How do I set a proxy in curl?

Pass -x or --proxy with a full proxy URL: curl -x http://host:port https://example.com/. Include the scheme and the port. Without a scheme curl assumes an HTTP proxy, and without a port it uses 1080, which is rarely correct for an HTTP gateway.

How do I pass a username and password to a curl proxy?

Use -U user:pass (short for --proxy-user), or embed the credentials in the proxy URL as http://user:pass@host:port. The -U form is safer on shared machines because the inline form leaves the password in your shell history and in the process list. Percent-encode any @ or : in the password.

What is the difference between socks5 and socks5h in curl?

With socks5:// curl resolves the target hostname on your machine and sends the IP to the proxy. With socks5h:// the hostname is sent to the proxy and resolved there. Use socks5h when you do not want your local resolver to see the target, or when local resolution would return an IP for the wrong region.

Why does curl fail with a certificate error only when the proxy is on?

The proxy is intercepting TLS and presenting its own certificate, which is standard for corporate gateways. Trust that gateway root with --cacert or install it in the system store. Using -k works but disables verification for every certificate, including a genuine attack. --proxy-insecure is a different flag: it skips verification of an HTTPS proxy, not of the target.

How do I make curl ignore the proxy for some hosts?

Use --noproxy on the command line, or set the no_proxy environment variable to a comma-separated list such as localhost,127.0.0.1,.internal.example.com. Do this whenever you set a global proxy, otherwise internal health checks and localhost calls start routing through the gateway and timing out.

Can curl scrape a JavaScript site through a proxy?

No. curl returns the HTML the server sends and never executes scripts. In our 9 September 2026 test of six pages, three returned their complete text to a plain HTTP client, one returned about a quarter of it, and one returned an empty document. Check the body first, and only pay for rendering on the targets that actually need it.

Test the command against a real pool

Every account gets $2 of API usage free each month, and failed requests are never billed. Point a curl command at a rotating residential endpoint and read the exit IP back before you commit to anything.

Related reading