The starting point
Every way curl selects a proxy, from one -x flag to environment variables, with the authentication, SOCKS5 and tunnel details that decide whether the request succeeds.
Set a proxy for one request with -x
The -x option, spelled --proxy in long form, routes a single curl request through a proxy. Give it the proxy scheme, host and port. When the scheme is omitted curl assumes http://, and when the port is omitted it assumes 1080, so write both explicitly.
The scheme describes the connection to the proxy, not the destination. An https:// destination through an ordinary HTTP proxy still uses -x http://host:port; curl opens a CONNECT tunnel through it. Write -x https://host:port only when the provider terminates TLS on the proxy itself. curl also accepts socks4://, socks4a://, socks5:// and socks5h:// here.
The command below keeps two habits from the rest of this guide: --disable ignores a curlrc that might add its own proxy, and --noproxy '' cancels any inherited no_proxy exclusion so the request really goes through the gateway you named. proxy.example.invalid deliberately cannot resolve; substitute your provider's gateway. Without --compressed, the command sends no Accept-Encoding header, so a server that compresses only when asked returns the full uncompressed page through the proxy; if you pay per GB, see which clients skip compression and how to check the bytes.
curl --disable --noproxy '' \
-x http://proxy.example.invalid:8080 \
https://example.com/Proxy authentication: -U, --proxy-user and 407
Proxy credentials are separate from destination credentials. -U or --proxy-user sends them to the proxy; -u or --user sends them to the destination. Using the wrong option can leave the proxy asking for credentials with 407 or the destination asking with 401, even when the supplied password is correct. When the 407 persists with correct credentials, work through fix proxy error 407 one variable at a time.
You can also embed credentials in the proxy URL as http://user:password@host:port. Percent-encode reserved characters first: an @ in the password must become %40 or curl reads it as the start of the host. Basic is the default proxy authentication scheme; --proxy-digest, --proxy-ntlm and --proxy-anyauth select others when the provider documents them.
Typing a password directly in either form can record it in shell history and exposes it in the initial process arguments. A shell variable can avoid a literal password in the command you type, if you populate it privately, but --proxy-user "$PROXY_USERNAME:$PROXY_PASSWORD" still expands the secret into an argument before curl starts. curl hides that argument on supported systems only after startup; a later process listing can miss the exposure. See the [curl proxy-user documentation](https://curl.se/docs/manpage.html#--proxy-user).
For scripts with curl 8.3.0 or later, the example below imports exported variables inside curl. Have your secret manager supply them, keep shell tracing off, and do not paste secret-bearing export commands into history. This avoids a password in curl's arguments; it does not hide the inherited environment or process memory from accounts permitted to inspect them. For an interactive check, --proxy-user username with no colon prompts for the password instead.
Argument handling does not encrypt the proxy connection. HTTP Basic credentials sent to an http:// proxy are not protected by the later HTTPS destination tunnel. Use an https:// gateway when your provider supports it, and keep proxy certificate verification enabled.
- connect=407 or the message Received HTTP code 407 from proxy after CONNECT means the proxy rejected the credentials or received none. Check the -U value, the encoding and the provider's username format.
- A 401 from the destination while the tunnel succeeded is a destination problem; the proxy step is already working.
- If your provider encodes country, session or protocol options in the username, follow its documented format. --proxy-user expects the supplied username and password separated by a colon; it is not a URL-encoding step.
: "${PROXY_USERNAME:?}" "${PROXY_PASSWORD:?}"
curl --disable --noproxy '' \
-x http://proxy.example.invalid:8080 \
--variable %PROXY_USERNAME \
--variable %PROXY_PASSWORD \
--expand-proxy-user '{{PROXY_USERNAME}}:{{PROXY_PASSWORD}}' \
https://example.com/Environment variables: http_proxy, https_proxy, no_proxy and .curlrc
Without -x, curl looks for a proxy in the environment. It reads http_proxy in lowercase only, because the uppercase name can be set by CGI request headers; it accepts https_proxy or HTTPS_PROXY, all_proxy or ALL_PROXY, and no_proxy or NO_PROXY. The variable is chosen by the destination scheme: an https:// destination uses https_proxy, so http_proxy alone does not configure the HTTPS route; an ALL_PROXY fallback or another setting can still affect it. curl_cffi, the Python binding to libcurl, follows the same lowercase rule; curl_cffi proxies records it together with the exceptions that a rejected CONNECT and a failed SOCKS handshake raise.
no_proxy is a comma-separated list of hostnames or domain suffixes that bypass the proxy, and a single * disables proxying entirely. -x overrides the gateway selected by proxy environment variables, but no_proxy can still bypass that gateway. --noproxy overrides no_proxy; an empty --noproxy value cancels inherited exclusions. The proxy environment variables guide covers how other clients read the same variables, which matters when curl behaves differently from the application you are debugging.
A ~/.curlrc file can set proxy = "http://host:port" for every invocation, which is convenient on a workstation and surprising inside a container or CI job. Run curl with --disable, or its short form -q as the first argument, when a script must ignore it.
export http_proxy='http://proxy.example.invalid:8080'
export https_proxy='http://proxy.example.invalid:8080'
export no_proxy='localhost,127.0.0.1,.internal.example'
curl https://example.com/ # uses https_proxy
curl --noproxy '*' https://example.com/ # bypasses the proxy onceSOCKS5 with curl: socks5 versus socks5h
curl speaks SOCKS through the same -x option. socks5:// resolves the destination hostname on your machine and sends the proxy an IP address. socks5h:// sends the hostname and lets the proxy resolve it, which is what you want when the destination must be resolved from the proxy's network or when local DNS should not see the destination at all. The long options --socks5 and --socks5-hostname are equivalent. If you are still deciding between the two protocols, HTTP vs SOCKS5 proxies explains where DNS and TLS happen in each.
Username and password authentication for SOCKS5 uses the same --proxy-user option. SOCKS has no HTTP CONNECT status; a rejected SOCKS handshake can surface as curl exit code 97 with a reason, rather than an HTTP 407. The shell-expanded credentials in this compatibility example still enter the initial argument list; use the curl 8.3+ import form above when that exposure must be avoided.
curl --disable --noproxy '' \
-x socks5h://proxy.example.invalid:1080 \
--proxy-user "$PROXY_USERNAME:$PROXY_PASSWORD" \
https://example.com/HTTPS destinations: the CONNECT tunnel and -v
For an https:// destination through an HTTP proxy, curl first sends CONNECT example.com:443 to the proxy. After a successful CONNECT response, curl starts TLS with the destination inside the tunnel. An ordinary forwarding proxy cannot read that encrypted HTTP exchange; a TLS-intercepting proxy trusted by your client is a different case. Run with -v to watch both steps; the CONNECT response arrives before any destination headers. -p or --proxytunnel forces the same tunnel for a plain http:// destination.
For a plain http:// destination there is no tunnel. curl sends the full URL to the proxy, the proxy fetches it, and the response headers you see may come from either party. When a status code needs attributing to a layer, prefer the tunneled case and read the CONNECT status separately with the %{http_connect} write-out variable.
With an https:// proxy, curl verifies the proxy's certificate as well as the destination's. --proxy-cacert supplies a private trust bundle for the proxy; keep both verifications enabled rather than reaching for --proxy-insecure or -k.
Send one bounded request as a baseline
Save this as proxy-check.sh and run it with sh proxy-check.sh. It requires curl 8.3.0 or later and HTTP Basic proxy authentication. Have your secret manager export PROXY_USERNAME and PROXY_PASSWORD, and export PROXY_URL with your provider's gateway with its scheme and port; https://proxy.example.invalid:8443 is an illustrative value that deliberately cannot connect.
curl imports the credentials itself, so the password is not passed in curl's arguments. The guards reject missing or empty values; environment and tracing limits still apply. The command prints the destination status, the CONNECT status and the elapsed time, and discards the response body. Keep this baseline before adding a browser, concurrency or application code.
If this baseline works but an HTTP(S) Git repository download fails, use Git proxy setup to inspect Git's URL and remote settings. curl's configuration does not select Git's route.
: "${PROXY_URL:?Set your provider proxy URL}"
: "${PROXY_USERNAME:?}" "${PROXY_PASSWORD:?}"
curl --disable --silent --show-error --fail \
--noproxy '' \
--proxy "$PROXY_URL" \
--variable %PROXY_USERNAME \
--variable %PROXY_PASSWORD \
--expand-proxy-user '{{PROXY_USERNAME}}:{{PROXY_PASSWORD}}' \
--connect-timeout 10 --max-time 30 \
--output /dev/null \
--write-out 'http=%{http_code} connect=%{http_connect} seconds=%{time_total}\n' \
'https://example.com/'Read curl exit codes and find the failing layer
A synthetic loopback check with curl 8.7.1 compared the credential paths: shell expansion put the password in the initial arguments; curl-internal import did not. Both sent the same test credentials. A later process listing showed curl had cleared the shell-expanded value, so that listing alone would have missed the exposure. These checks establish local command behavior, not provider security or proxy performance.
Reproduce the check with the loopback fixture and inspect the synthetic results. It tests HTTP Basic with invented credentials; it does not test an external proxy service.
A successful response proves this request completed; it does not establish a particular exit country or carrier. For that, use your provider's documented diagnostic endpoint or an endpoint you control that reports the observed source address.
Keep the same destination and credentials when comparing your application with curl. Changing three settings at once makes a successful retry hard to explain. Share the exit code, timing and sanitized status with support, rather than a verbose trace containing authentication data.
- Exit 5, could not resolve proxy: the gateway hostname is wrong or local DNS cannot see it.
- Exit 7, failed to connect: the proxy host resolved but the port is closed, filtered or unreachable from this network.
- Exit 28, operation timed out: compare the connection and total time limits with the recorded timing. A repeat does not by itself identify whether the gateway, destination or chosen deadline caused it.
- connect=407: the proxy requires authentication. Our curl 8.7.1 loopback test returned exit 22 with the baseline's --fail and exit 56 without it. Read the CONNECT status with the exit code; do not require exit 56 before checking proxy credentials.
- Exit 97, proxy handshake error: the SOCKS handshake was rejected; check the socks5 versus socks5h scheme and the credentials.
- Exit 35 or 60, TLS failure: check the hostname, trust chain and clock. Keep certificate verification enabled.
- Exit 35 with wrong version number is not a certificate problem: a TLS handshake was answered in plaintext, either by the proxy port or by the destination port. SSL WRONG_VERSION_NUMBER behind a proxy shows how %{http_connect} tells the two apart.
From reading to doing
Before you ship
- Use an actual gateway from your provider, with the scheme and port it documents.
- Inject credentials privately; do not paste secrets into shell history.
- Record one bounded baseline before testing parallel traffic.
- Note whether the failure came from the CONNECT step or the destination before changing settings.
Sources & further reading
Technical references used for this guide. Check the documentation for your installed version and your provider’s supported configuration.