Skip to content
ProxyForge

curl proxy cheat sheet: flags, SOCKS5, auth and debugging

ProxyForge engineeringUpdated 8 min read

To send a request through a curl proxy, pass the proxy URL with -x: curl -x "http://USERNAME:[email protected]:PORT" https://api.ipify.org. The scheme picks the proxy type (http://, https://, socks5:// or socks5h://), credentials can sit in the URL or go in --proxy-user, and curl also reads https_proxy, ALL_PROXY and NO_PROXY from the environment. -v shows the CONNECT exchange when something fails.

This page is a reference. Every flag below was checked against curl --help all and the manual, and each behavior described was run against a local authenticating HTTP proxy, an HTTPS proxy with a self-signed certificate and a SOCKS5 proxy, using curl 8.5.0 with OpenSSL. The examples read the connection string from $PROXY_URL, shaped like http://USERNAME:[email protected]:PORT; the dashboard generates the exact string for the country and session mode you choose.

curl proxy flags at a glance

Flag What it does
-x, --proxy [scheme://]host[:port] Use this proxy. Overrides proxy environment variables.
-U, --proxy-user user:password Proxy credentials, as an alternative to putting them in the URL.
--noproxy list Comma-separated hosts that bypass the proxy; '*' bypasses it for everything.
-p, --proxytunnel Tunnel through an HTTP proxy with CONNECT, even for http:// URLs.
--socks5 host[:port] SOCKS5 proxy, target name resolved locally.
--socks5-hostname host[:port] SOCKS5 proxy, target name resolved by the proxy.
--proxy-cacert file CA certificate to verify an HTTPS proxy against.
--proxy-insecure Skip verification of an HTTPS proxy's certificate. For testing only.
--proxy-header "Name: value" Send a header to the proxy only, not to the target.
--connect-timeout seconds Limit the time to establish the connection.
-m, --max-time seconds Limit the whole operation.
--retry n Retry transient failures up to n times.
-v, --verbose Print the connection, CONNECT and TLS steps.
-w, --write-out format Print variables such as timings and status codes after the transfer.

If the proxy string has no port, curl assumes 1080 for every scheme, not 80 or 8080. A missing port is a common cause of "Failed to connect" when copying a gateway address by hand.

Proxy schemes: http, https, socks5 and socks5h

The scheme in front of the proxy host decides how curl talks to the proxy itself:

curl -x "http://USERNAME:[email protected]:PORT"   https://api.ipify.org
curl -x "https://USERNAME:[email protected]:PORT"       https://api.ipify.org
curl -x "socks5://USERNAME:[email protected]:PORT"  https://api.ipify.org
curl -x "socks5h://USERNAME:[email protected]:PORT" https://api.ipify.org
  • http:// (or no scheme) is a plain HTTP proxy. For an https:// target, curl opens a CONNECT tunnel and negotiates TLS with the target through it, so the proxy sees the hostname and port but not the request. For an http:// target, curl sends the full request to the proxy.
  • https:// is a proxy you reach over TLS. The connection to the proxy is encrypted before anything else is sent, including the Proxy-Authorization header. This is not the same as proxying HTTPS traffic, which every HTTP proxy does through CONNECT.
  • socks5:// is SOCKS5 with the target hostname resolved on your machine. The proxy receives an IP address.
  • socks5h:// is SOCKS5 with the hostname sent to the proxy, which resolves it at the exit. In our test proxy's log, socks5:// arrived as an IPv4 address and socks5h:// as a domain name.

Prefer socks5h:// when you use SOCKS at all. Local resolution sends every lookup through your own resolver, and a CDN that answers by resolver location may hand you an address near your server rather than near the exit, which undermines geo-targeted results. For most HTTP work there is no advantage to SOCKS5 over an HTTP proxy; SOCKS5 vs HTTP proxy covers when it is worth it.

Forcing a tunnel with -p

With an HTTP proxy, curl already tunnels https:// targets. -p makes it tunnel http:// targets too, sending CONNECT host:80 instead of a proxied GET:

curl -p -x "$PROXY_URL" http://api.ipify.org

Use it when a gateway only accepts CONNECT, or when you want the proxy to see the same kind of traffic for both schemes.

Credentials without leaking them

Credentials in the proxy URL are URL-decoded by curl, so a password containing @ or : must be written as %40 or %3A. --proxy-user takes them unencoded:

curl -x "http://gateway.proxyforge.io:PORT" -U "USERNAME:PASSWORD" https://api.ipify.org

Both forms put the secret on the command line, where it lands in shell history and can be visible to other users through the process list while curl runs. Two ways to avoid that:

# A config file readable only by you
cat > ~/.config/proxy.curlrc <<'EOF'
proxy = "http://gateway.proxyforge.io:PORT"
proxy-user = "USERNAME:PASSWORD"
EOF
chmod 600 ~/.config/proxy.curlrc
curl -K ~/.config/proxy.curlrc https://api.ipify.org

# Or expand an environment variable inside curl (curl 8.3 or later)
curl --variable %PROXY_URL --expand-proxy "{{PROXY_URL}}" https://api.ipify.org

--variable %PROXY_URL imports the environment variable into curl, and the --expand- prefix on any option substitutes it, so the credentials never appear in the arguments. In scripts, load PROXY_URL from your secrets manager at run time rather than writing it into the script; storing proxy credentials covers the options.

Environment variables and NO_PROXY

curl reads these variables, and -x has the same effect as setting them:

Variable Applies to
http_proxy http:// URLs. Lowercase only.
HTTPS_PROXY or https_proxy https:// URLs.
ALL_PROXY or all_proxy Any URL with no scheme-specific proxy set.
NO_PROXY or no_proxy Hosts that bypass the proxy.

The lowercase form wins when both are set. http_proxy is the exception that catches people: curl ignores uppercase HTTP_PROXY entirely. We confirmed this by setting only HTTP_PROXY and watching curl go direct. The reason is historical: in CGI environments, a request header named Proxy becomes HTTP_PROXY, so honoring it would let a remote client choose your proxy. Set both cases if other tools on the machine expect the uppercase name.

export http_proxy="$PROXY_URL"
export https_proxy="$PROXY_URL"
export no_proxy="localhost,127.0.0.1,.internal.example.com,10.0.0.0/8"

NO_PROXY entries match the host and its subdomains, and curl 7.86 and later accept CIDR ranges. Note the precedence: NO_PROXY applies even when you pass -x, so a host listed there goes direct regardless of the command line. --noproxy on the command line overrides the variable. To bypass an environment proxy for one command, use --noproxy '*' or -x "".

Keeping internal hosts, health checks and metadata endpoints in NO_PROXY matters for cost as well as correctness when the proxy is billed per GB.

HTTPS proxies: --proxy-cacert and --proxy-insecure

With an https:// proxy, curl verifies the proxy's certificate just as it verifies the target's. If the proxy uses a private CA, point curl at it:

curl --proxy-cacert /etc/ssl/private-ca.pem -x "https://USERNAME:[email protected]:PORT" https://api.ipify.org

Without it, curl fails with exit code 60, SSL certificate problem: self-signed certificate. --proxy-insecure skips the check for the proxy connection only, and is fine for a local test proxy and wrong anywhere else, since it hands your proxy credentials to whoever answers. The --proxy- options are separate from the target's --cacert and -k, so relaxing one does not relax the other.

Debugging CONNECT and 407 with -v

When a curl proxy request fails, -v shows which leg failed. A rejected login looks like this:

* Connected to gateway.proxyforge.io (203.0.113.10) port PORT
* Establish HTTP proxy tunnel to api.ipify.org:443
> CONNECT api.ipify.org:443 HTTP/1.1
> Host: api.ipify.org:443
> User-Agent: curl/8.5.0
> Proxy-Connection: Keep-Alive
>
< HTTP/1.1 407 Proxy Authentication Required
< Proxy-Authenticate: Basic realm="proxy"
* CONNECT tunnel failed, response 407
curl: (56) CONNECT tunnel failed, response 407

Read the trace in order:

  1. "Connected to" the proxy. If this line is missing, curl never reached the gateway: check the host, the port and any egress firewall. Exit code 7 means the connection was refused; 28 means it timed out.
  2. "Proxy auth using Basic with user". This appears when curl has credentials to send. If it is missing, the credentials did not parse; look for an unencoded @ in the password.
  3. The CONNECT response. 200 means the tunnel is open and anything after it is between you and the target. A 407 is the proxy refusing the credentials or, with IP allowlisting, your source address. IP allowlist vs username and password covers which to use where.
  4. The TLS handshake and the target's response. A 403 or 429 here comes from the target site, not the proxy. Proxy 403 and 429 errors explains how to tell a block from a rate limit.

In scripts, -w '%{http_connect}' prints the proxy's CONNECT status and -w '%{http_code}' the target's, so you can tell the two apart without parsing verbose output.

Timeouts and retries

curl has no overall timeout by default. Set both limits on anything unattended:

curl -sS -x "$PROXY_URL" --connect-timeout 5 --max-time 30 \
  --retry 3 --retry-max-time 60 \
  https://api.ipify.org

--connect-timeout bounds the connection phase and --max-time the whole transfer. A gateway that has not accepted a connection within a few seconds is unlikely to do better at thirty.

--retry treats a timeout and HTTP 408, 429, 500, 502, 503 and 504 as transient. It waits one second, then doubles the wait on each attempt, and it honors a Retry-After header. --retry-delay replaces the backoff with a fixed wait, and --retry-max-time caps the total time spent retrying. A proxy 407 is exit code 56 and is not retried by --retry alone, which is correct. --retry-all-errors does retry it, along with every other failure; avoid it unless you have a specific reason, because repeated failed logins against a gateway help nobody.

Measuring timing and checking the exit IP

Timing each leg with -w

-w prints timing variables after the transfer. Through a proxy, each one measures a different leg:

curl -s -o /dev/null -x "$PROXY_URL" -w '
dns        %{time_namelookup}
connect    %{time_connect}
tunnel+tls %{time_appconnect}
ttfb       %{time_starttransfer}
total      %{time_total}
' https://www.example.com
Variable Through a proxy, it measures
time_namelookup Resolving the proxy's hostname on your machine.
time_connect The TCP connection to the proxy, not the target.
time_appconnect Up to the end of the TLS handshake with the target, which includes the CONNECT round trip and the exit's connection to the target.
time_starttransfer Up to the first byte of the target's response.
time_total The whole operation.

time_appconnect minus time_connect is the part the proxy network adds for an HTTPS request: the gateway's work, the exit's connection and the TLS handshake over the tunnel. It is the number to watch when comparing exits or providers. Run it in a loop and look at percentiles rather than a single sample; proxy monitoring and benchmarking proxy providers cover how to turn samples into something you can act on. Note that %{remote_ip} reports the proxy's address, not the target's, when a proxy is in use.

Checking the exit IP

The quickest check that traffic is really going through the proxy is to compare the address an echo service sees with and without it:

direct=$(curl -s https://api.ipify.org)
proxied=$(curl -s -x "$PROXY_URL" https://api.ipify.org)
echo "direct=$direct proxied=$proxied"
[ "$direct" != "$proxied" ] || echo "WARNING: not going through the proxy" >&2

If the environment sets a proxy, the "direct" request may be proxied too; add --noproxy '*' to it. When you pass several URLs to one curl invocation, curl reuses the connection, and our test proxy saw one CONNECT for two requests. Through a rotating gateway that usually means both requests share an exit. Use separate invocations when you want to see rotation, and see rotating vs sticky proxies for why the tunnel, not the request, is usually the unit of rotation.

Using curl with ProxyForge

Every ProxyForge line accepts HTTP, HTTPS and SOCKS5 with username and password or IP allowlist authentication, so every command above works with the connection string from your dashboard. The first proxy request guide walks through finding it and making a first call.

curl is also the fastest way to compare lines before you commit to one. Run the timing and exit-IP checks against the residential and ISP endpoints for your target, then see the pricing page for published pay-as-you-go rates.

FAQ

Related questions

Why does curl ignore my HTTP_PROXY environment variable?

curl reads the proxy for plain http:// URLs only from lowercase http_proxy, a guard against request headers being turned into an environment variable in CGI setups. HTTPS_PROXY and ALL_PROXY work in either case, and the lowercase form wins when both are set.

Which port does curl use if the proxy URL has no port?

1080, whatever the scheme. A proxy string such as http://gateway.example.com without a port connects to port 1080, so always include the port your provider issued.

How do I stop curl from using a proxy set in the environment for one command?

Pass --noproxy '*' to bypass the proxy for every host, or pass -x with an empty string. Both override the environment for that invocation only.

What is the difference between curl --socks5 and --socks5-hostname?

--socks5 resolves the target hostname on your machine and sends the proxy an IP address. --socks5-hostname sends the name and lets the proxy resolve it, which is the same as using the socks5h:// scheme with -x.

Run it on a network you can account for

Order from 1 GB or 1 IP with no monthly minimum, or talk to an engineer about your workload first.

One business day, from a named engineer.