Back to blog

Guide

How to Use cURL With a Proxy: Flags, Auth, SOCKS5 and Debugging

Every way to route cURL through a proxy: the -x flag, authentication, SOCKS5 versus socks5h, environment variables, .curlrc defaults, and how to debug 407s and connection failures.

Routing cURL through a proxy is a single flag in the simple case and a handful of details in the real ones. The short answer: curl -x http://proxy.example.com:8080 https://example.com. Everything below covers authentication, SOCKS, DNS handling, persistent defaults and the errors that actually come up.

How to Use cURL With a Proxy

The basic flag

-x and --proxy are the same option.

# HTTP proxy
curl -x http://proxy.example.com:8080 https://example.com
 
# Long form, identical behaviour
curl --proxy http://proxy.example.com:8080 https://example.com
 
# Scheme omitted — cURL assumes http://
curl -x proxy.example.com:8080 https://example.com

Note that the proxy scheme and the target scheme are independent. An HTTP proxy can serve an HTTPS target: cURL issues a CONNECT request and tunnels the TLS session through it, so your request stays encrypted end to end and the proxy only sees the hostname.

Authentication

Use -U (or --proxy-user):

curl -x http://proxy.example.com:8080 -U myuser:mypassword https://example.com

Credentials can also be embedded in the proxy URL:

curl -x http://myuser:mypassword@proxy.example.com:8080 https://example.com

Both work. Prefer the flag, and prefer neither for anything sensitive: credentials on the command line land in your shell history and are visible in the process list to any other user on the machine. Safer options:

# Prompt for the password instead of storing it
curl -x http://proxy.example.com:8080 -U myuser https://example.com
 
# Read from an environment variable
curl -x http://proxy.example.com:8080 -U "$PROXY_USER:$PROXY_PASS" https://example.com

For a persistent setup, put the credentials in .curlrc (covered below) and restrict its permissions with chmod 600.

If your provider supports IP allowlisting instead of credentials, that avoids the problem entirely — see proxy authentication methods for the trade-offs.

Authentication schemes

cURL negotiates Basic by default. To force a scheme:

curl --proxy-basic  -x http://proxy:8080 -U user:pass https://example.com
curl --proxy-digest -x http://proxy:8080 -U user:pass https://example.com
curl --proxy-ntlm   -x http://proxy:8080 -U user:pass https://example.com
curl --proxy-anyauth -x http://proxy:8080 -U user:pass https://example.com

--proxy-ntlm is the one you reach for behind a corporate Windows proxy.

SOCKS proxies, and the socks5h detail

cURL supports SOCKS4, SOCKS4a, SOCKS5 and SOCKS5 with proxy-side DNS:

curl -x socks4://proxy.example.com:1080  https://example.com
curl -x socks4a://proxy.example.com:1080 https://example.com
curl -x socks5://proxy.example.com:1080  https://example.com
curl -x socks5h://proxy.example.com:1080 https://example.com

There are also dedicated flags — --socks5, --socks4 — but the -x scheme form is more consistent.

The difference between socks5 and socks5h matters and is easy to miss:

SchemeWho resolves DNSConsequence
socks5://cURL, locallyYour resolver sees every hostname you request
socks5h://The proxyHostnames resolve on the proxy's network

Use socks5h:// in almost every case. Two reasons: your DNS queries stop leaking outside the tunnel, which otherwise reveals exactly which hosts you are visiting to your local resolver; and it works for hostnames that only resolve from the proxy's network — internal addresses, or names with region-specific DNS answers.

For SOCKS5 with credentials:

curl -x socks5h://user:pass@proxy.example.com:1080 https://example.com

If you need to chain a SOCKS proxy in front of an HTTP proxy, --preproxy does that:

curl --preproxy socks5h://socks.example.com:1080 \
     -x http://http-proxy.example.com:8080 \
     https://example.com

Environment variables

cURL reads proxy settings from the environment, which is convenient for scripts:

export http_proxy="http://proxy.example.com:8080"
export https_proxy="http://proxy.example.com:8080"
export all_proxy="socks5h://proxy.example.com:1080"
export no_proxy="localhost,127.0.0.1,.internal.example.com"
 
curl https://example.com   # uses https_proxy automatically
  • http_proxy — for HTTP targets
  • https_proxy — for HTTPS targets
  • all_proxy — fallback for any protocol
  • no_proxy — comma-separated list of hosts to bypass

One security detail worth knowing: cURL deliberately ignores the uppercase HTTP_PROXY when running in a CGI environment, because a client-supplied Proxy: header would otherwise be able to set it. Stick to the lowercase forms and you avoid the whole class of surprise.

To ignore the environment for one request:

curl --noproxy "*" https://example.com

Persistent defaults with .curlrc

For a proxy you always want, create ~/.curlrc:

proxy = http://proxy.example.com:8080
proxy-user = myuser:mypassword

cURL reads this on every invocation. Lock it down, since it holds credentials:

chmod 600 ~/.curlrc

To skip it for one request, -q disables reading the file entirely:

curl -q https://example.com

Verifying the proxy is actually used

The reliable check is to ask what address the far end sees:

# Direct
curl https://api.ipify.org
 
# Through the proxy — should return a different address
curl -x http://proxy.example.com:8080 https://api.ipify.org

If both return the same address, the proxy is not being applied. -v shows what cURL is really doing:

curl -v -x http://proxy.example.com:8080 https://example.com

In verbose output look for the CONNECT line and the proxy's response. HTTP/1.1 200 Connection established means the tunnel opened. Anything else is where the failure is.

Rotating IPs across requests

Most providers rotate at the gateway, so the same endpoint returns a different exit IP per connection:

for i in $(seq 1 5); do
  curl -s -x http://user:pass@gateway.example.com:8000 https://api.ipify.org
  echo
done

If you get five different addresses, rotation is working. For sticky sessions, providers typically encode a session ID in the username — check your provider's format. Proxy rotation strategies covers when to rotate per request versus holding a session.

Useful extras

# Timeouts — always set these in scripts
curl -x http://proxy:8080 --connect-timeout 10 --max-time 30 https://example.com
 
# Retry transient failures with backoff
curl -x http://proxy:8080 --retry 3 --retry-delay 2 https://example.com
 
# Follow redirects
curl -x http://proxy:8080 -L https://example.com
 
# Custom user agent
curl -x http://proxy:8080 -A "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" https://example.com
 
# Save output, show only HTTP status
curl -x http://proxy:8080 -o page.html -w "%{http_code}\n" -s https://example.com
 
# Tunnel a non-HTTPS request through CONNECT
curl -p -x http://proxy:8080 http://example.com

If your proxy terminates TLS with its own certificate, point cURL at the CA rather than disabling verification:

curl --proxy-cacert /path/to/proxy-ca.pem -x https://proxy:8443 https://example.com

--proxy-insecure skips verification of the proxy's certificate and -k skips verification of the target's. Both are debugging tools. Neither belongs in anything that runs unattended, because they remove exactly the protection that stops traffic interception.

Debugging errors

HTTP 407 Proxy Authentication Required — credentials wrong or not sent. Verify them, try forcing a scheme with --proxy-basic or --proxy-ntlm, and if your provider uses IP allowlisting confirm your current address is listed.

curl: (5) Could not resolve proxy — the proxy hostname is wrong or your DNS cannot resolve it. Check for typos; try the proxy's IP directly.

curl: (7) Failed to connect to ... port — the host resolves but nothing accepts the connection. Wrong port, proxy down, or a local firewall blocking outbound traffic on that port.

curl: (56) Recv failure or (52) Empty reply — connection established then dropped. Often the proxy rejecting the target, or an upstream timeout. Run with -v to see how far it got.

curl: (35) SSL connect error — TLS failure. If the proxy intercepts TLS, supply its CA with --proxy-cacert.

Works direct, 403 through the proxy — the target is blocking the proxy's IP, not your request. This is the normal failure mode for datacenter ranges on protected sites; residential IPs are the usual fix. Why proxies get blocked covers the detection side.

Intermittent failures at volume — you are being rate-limited. Slow the pace and widen the IP pool; rotation alone does not reduce aggregate load.

Exit codes are worth learning if you script this: 5 is proxy resolution, 6 is host resolution, 7 is connection, 28 is timeout, 35 is TLS. echo $? after a failed call tells you which category you are in without reading verbose output.

A working script pattern

#!/usr/bin/env bash
set -euo pipefail
 
PROXY="http://gateway.example.com:8000"
: "${PROXY_USER:?set PROXY_USER}"
: "${PROXY_PASS:?set PROXY_PASS}"
 
fetch() {
  curl -sS \
    -x "$PROXY" \
    -U "$PROXY_USER:$PROXY_PASS" \
    --connect-timeout 10 \
    --max-time 30 \
    --retry 3 --retry-delay 2 \
    -w "%{http_code} %{time_total}s\n" \
    -o "$2" \
    "$1"
}
 
fetch "https://example.com/page" "output.html"

Credentials come from the environment rather than the command line, timeouts are explicit, retries handle transient proxy failures, and -w reports status and timing so you can see degradation before it becomes an outage.

Which proxies work with cURL

Any standards-compliant HTTP, HTTPS or SOCKS5 proxy. What varies is whether the target accepts them.

For scripted collection, the practical requirements are: SOCKS5 support if you want proxy-side DNS via socks5h, credential or IP-allowlist authentication that fits your environment, generous concurrency if you are parallelising, and enough IP diversity that repeated requests do not stack on one address.

FlameProxies supports HTTP(S) and SOCKS5 across 80M-plus residential IPs in 180-plus countries, starting at $0.50/GB and dropping to $0.45/GB above 1TB, with unlimited concurrent sessions. Current rates are on the pricing page.

For the broader picture on choosing between residential and datacenter for scripted work, best proxies for web scraping covers the trade-offs.

Frequently asked questions

How do I use a proxy with cURL?
Pass the proxy with the -x flag, or its long form --proxy, followed by the scheme, host and port. For example: curl -x http://proxy.example.com:8080 https://example.com. For credentials, add -U username:password, or embed them in the proxy URL.
What is the difference between socks5 and socks5h in cURL?
With socks5:// cURL resolves the hostname locally and sends the resulting IP address to the proxy. With socks5h:// the proxy performs the DNS resolution. Use socks5h:// in almost all cases — it stops your DNS queries leaking outside the tunnel and it works when the target hostname only resolves from the proxy's network.
How do I pass proxy credentials in cURL?
Two ways. Use -U user:password as a separate flag, which is the cleaner option, or embed them in the URL as -x http://user:password@proxy:8080. Both work, but embedded credentials end up in your shell history and in the process list where other users on the machine can read them, so the flag is safer.
Why does cURL return 407 with a proxy?
HTTP 407 Proxy Authentication Required means the proxy rejected your credentials, or none were sent. Check the username and password, confirm you are using the right authentication scheme, and if your provider uses IP allowlisting instead of credentials, verify your current public address is on the allowlist.
Does cURL respect http_proxy environment variables?
Yes. cURL reads http_proxy, https_proxy, all_proxy and no_proxy. Note one security detail: cURL deliberately ignores the uppercase HTTP_PROXY when running in a CGI context, because a client-supplied Proxy header would otherwise set it. The lowercase forms are the reliable choice.
How do I make cURL always use a proxy?
Add a proxy line to your .curlrc file — proxy = http://proxy.example.com:8080 — in your home directory. cURL reads it on every invocation. Use the --no-proxy flag or -q to skip it for a single request when you need to bypass.