How to use a proxy with cURL
Add -x http://GATEWAY_IP:PORT to any cURL command: curl -x http://GATEWAY_IP:PORT https://httpbin.org/ip. cURL tunnels HTTPS through the proxy with CONNECT automatically. For a dedicated proxy with a login, add -U USER:PASS. Use -v to see exactly where a failing connection stops.
cURL is the command-line HTTP client that ships with macOS, Windows 10 and later, and nearly every Linux system, and libcurl sits under PHP’s cURL extension and many other languages. Proxy support is built in: one flag and your request leaves through a different IP.
People use cURL with proxies for three jobs: checking that a new proxy works before configuring a bigger tool, running small scheduled fetches from shell scripts, and debugging why a scraper can’t connect. The usual trouble spots are the proxy URL scheme, environment variables that cURL reads (or ignores) without telling you, Windows PowerShell’s fake curl, and expecting a new IP inside one command that reuses its connection.
Below you’ll find every proxy flag you need, how to read the -v output of a CONNECT tunnel, a loop that proves rotation works, the error codes you’ll meet, and the same setup in PHP cURL.
Which Storm plan fits
cURL is usually where you test a gateway before wiring it into code, and for that any plan works. For scripted fetching from cron jobs or shell loops, rotating proxies fit best: each new cURL process gets a new IP through the Main gateway, and bandwidth is unlimited. If the script runs from a host whose IP changes, or several servers share the job, use private dedicated proxies with -U credentials.
Rotating proxies (from $14/mo): 700,000+ IPs behind fixed gateway IP:PORTs. New IP on every request, or every 3 or 15 minutes. USA, EU, USA+EU or Worldwide. Unlimited bandwidth on every plan.
Get 40 threads for $39/mo See all rotating proxies plansBefore you start
- Log in to the member area and copy your gateway
IP:PORTs. They never change; the rotation happens on our side. - Add the public IP of the computer or server that will run your tool under Authorized IPs, click Save, and allow up to 15 minutes before testing. Rotating and residential proxies use IP authentication, so there is no username or password.
- Dedicated proxies work with either IP authentication or a username and password. Use user:pass if your IP changes or the tool runs on several machines.
- Count your threads: the tool’s total open connections must stay within your plan (for example 40 threads on the 40-thread plan).
Set up a proxy in cURL, step by step
- Make sure you run the real cURL
Run
curl --version. On Windows, typecurl.exein PowerShell: in Windows PowerShell 5.1,curlis an alias forInvoke-WebRequest, which doesn’t understand-x. Command Prompt and PowerShell 7 call the real binary. - Authorize your IP (rotating and residential)
Find your public IP with
curl https://api.ipify.org(without a proxy), add it under Authorized IPs in the member area, click Save, and wait up to 15 minutes. Dedicated proxies can skip this and use-Uinstead. - Send one request through the gateway
curl -x http://GATEWAY_IP:PORT https://httpbin.org/ip. The response should show a proxy IP.--proxyis the long form of-x. Always include the port: if you leave it out, cURL assumes port 1080. - Add credentials only where they apply
For a dedicated proxy with username and password:
curl -x http://PROXY_IP:PORT -U USER:PASS https://httpbin.org/ip.-Uis short for--proxy-userand sends the login to the proxy, not to the website. Rotating and residential gateways don’t take credentials. - Watch the handshake with -v
Add
-v. You should see aCONNECT host:443line and anHTTP/1.1 200 Connection establishedreply before the TLS handshake. If it stops earlier, the error table below tells you why. - Make it permanent if you want
Export
https_proxyandhttp_proxyfor a shell session, or putproxy = "http://GATEWAY_IP:PORT"in~/.curlrc(_curlrcon Windows) so every cURL call uses it.--noproxyor-qskips them for a single command.
Copy-paste commands
Replace GATEWAY_IP:PORT with a gateway from the member area, or PROXY_IP:PORT with a dedicated proxy.
# rotating or residential gateway (IP authorized, no login)
curl -x http://GATEWAY_IP:PORT https://httpbin.org/ip
# print only the status code and total time
curl -s -o /dev/null -w "%{http_code} %{time_total}s\n" -x http://GATEWAY_IP:PORT https://example.com/
# dedicated proxy with username and password
curl -x http://PROXY_IP:PORT -U "USER:PASS" https://httpbin.org/ip
# same thing, credentials in the URL (percent-encode @ : / in the password)
curl -x "http://USER:PASS@PROXY_IP:PORT" https://httpbin.org/ipcurl -v -x http://GATEWAY_IP:PORT https://httpbin.org/ip
# healthy output, trimmed:
# * Trying GATEWAY_IP:PORT...
# * Connected to GATEWAY_IP port PORT
# * CONNECT tunnel: HTTP/1.1 negotiated
# > CONNECT httpbin.org:443 HTTP/1.1
# < HTTP/1.1 200 Connection established
# * TLSv1.3 (OUT), TLS handshake, Client hello (1):
# ...
# < HTTP/2 200# Main rotating gateway: every curl process opens a new connection = new IP
for i in $(seq 1 10); do
curl -s --max-time 20 -x http://GATEWAY_IP:PORT https://api.ipify.org; echo
done
# one curl call with several URLs reuses the connection = usually one IP
curl -s -x http://GATEWAY_IP:PORT https://api.ipify.org https://api.ipify.org
# Windows PowerShell
1..10 | ForEach-Object { curl.exe -s -x http://GATEWAY_IP:PORT https://api.ipify.org; "" }# Linux / macOS: http_proxy must be lowercase for curl
export http_proxy="http://GATEWAY_IP:PORT"
export https_proxy="http://GATEWAY_IP:PORT"
export no_proxy="localhost,127.0.0.1,.internal.example"
curl https://httpbin.org/ip # uses the proxy
curl --noproxy "*" https://httpbin.org/ip # skips it once
# ~/.curlrc (one option per line)
proxy = "http://GATEWAY_IP:PORT"
connect-timeout = 10# 40-thread plan: xargs keeps at most 30 curl processes running at once
cat urls.txt | xargs -P 30 -I{} \
curl -s -o /dev/null --retry 2 --max-time 30 \
-w "%{http_code} {}\n" -x http://GATEWAY_IP:PORT "{}"<?php
$ch = curl_init("https://httpbin.org/ip");
curl_setopt_array($ch, [
CURLOPT_PROXY => "http://GATEWAY_IP:PORT", // scheme + host + port
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_FRESH_CONNECT => true, // new connection, so a new IP on the Main gateway
// dedicated proxy with a login:
// CURLOPT_PROXYUSERPWD => "USER:PASS",
]);
$body = curl_exec($ch);
if ($body === false) {
echo "cURL error " . curl_errno($ch) . ": " . curl_error($ch) . PHP_EOL;
} else {
echo $body;
}
curl_close($ch);What -v tells you about a failing proxy
Reading verbose output is the fastest way to find out which hop is broken. Look at where it stops:
- Stops at “Trying GATEWAY_IP:PORT” then fails: cURL can’t reach the gateway at all. Wrong port, a local firewall, or an office network blocking outbound ports.
- Connected, then the connection is closed or reset before any CONNECT reply: the gateway saw you but your source IP isn’t authorized (or was saved less than 15 minutes ago).
- CONNECT answered with 407: credentials missing or wrong on a dedicated proxy, or a different proxy in the path asking for its own login. Storm rotating and residential gateways don’t send a 407 for an unauthorized IP; that shows up as the reset above.
- 200 Connection established, then a TLS error: the tunnel is fine; the problem is between you and the website (an old cURL or OpenSSL, or the site’s TLS setup).
- Everything succeeds but the site returns 403 or 429: the proxy works and the website is refusing or rate-limiting the request.
Add --trace-ascii trace.txt when you need the full bytes, for example to send to support through the contact page.
Why the proxy URL starts with http:// for https sites
In -x, the scheme describes how cURL talks to the proxy. http:// means “plain HTTP to the proxy, then CONNECT for HTTPS targets”, which is how Storm gateways work. The website traffic inside the tunnel is still end-to-end TLS, and the proxy can’t read it.
Writing -x https://GATEWAY_IP:PORT asks cURL to start TLS with the proxy itself, and you get errors such as (35) SSL routines::wrong version number. Also leave out socks5://: our gateways are HTTP proxies, not SOCKS. And skip -p/--proxytunnel for normal use; cURL already tunnels HTTPS on its own, and forcing it for plain HTTP targets only changes how those are sent.
Environment variables: the uppercase trap
cURL reads http_proxy, https_proxy, all_proxy and no_proxy. All of them also work in uppercase except one: http_proxy is accepted only in lowercase, because in CGI environments HTTP_PROXY can be set by an incoming request header. So a script that exports only HTTP_PROXY sends plain-HTTP requests directly, without a proxy, while HTTPS requests go through. Set the lowercase names and both cases behave the same.
Command-line flags beat environment variables, and .curlrc is read on every call unless you pass -q as the first argument. When cURL uses a proxy you didn’t expect, check those three places in that order: flags, env | grep -i proxy, then ~/.curlrc.
Rotation, connection reuse and thread math
The Main rotating gateway picks a new IP for each new connection. Separate cURL commands open separate connections, so a shell loop gives a new IP each time. A single command with several URLs, or a PHP handle reused for many requests, keeps its connection open and usually exits from one IP. That’s what you want for a short login flow; for per-request rotation use CURLOPT_FRESH_CONNECT in PHP or separate commands in shell. To keep one IP on purpose for minutes, use the 3- or 15-minute gateway, or a residential port (one IP per 5-minute window, changing at minute 1, 6, 11 and so on).
Concurrency is the other half. Each running cURL process (or each handle in a curl_multi batch) is one thread on your plan. With xargs -P or curl_multi, keep the number below your plan, and below 25% of it if you’re querying search engines (10 parallel requests on a 40-thread plan). cURL 7.66+ also has --parallel with --parallel-max, but it reuses connections where it can, which reduces rotation.
PHP cURL: the settings that matter
PHP’s cURL extension is libcurl underneath, so everything above applies. Set CURLOPT_PROXY to http://GATEWAY_IP:PORT. Leave CURLOPT_PROXYTYPE at its HTTP default and don’t set CURLOPT_HTTPPROXYTUNNEL; HTTPS URLs are tunnelled anyway. Add CURLOPT_PROXYUSERPWD only for dedicated proxies with a login.
For parallel jobs use curl_multi_init() with no more handles running than your plan allows. Always set CURLOPT_CONNECTTIMEOUT and CURLOPT_TIMEOUT: without CURLOPT_TIMEOUT a transfer has no overall time limit, and a stalled one ties up a thread and a PHP worker. Shared hosting is a poor fit for rotating or residential plans when the outgoing IP isn’t fixed or isn’t known; a VPS or dedicated proxies with a login solve that. When the job grows past a shell script, move it to Python requests or Node.js.
Common errors and fixes
curl: (7) Failed to connect to GATEWAY_IP port PORTNothing answered on that address. Check the IP and port against the member area and test from another network to rule out a firewall.curl: (56) CONNECT tunnel failed, response 407The proxy wants authentication. On dedicated proxies, check -U USER:PASS and quote it in the shell. On rotating/residential, a 407 isn’t the IP symptom, so look for another proxy in https_proxy or ~/.curlrc. More in the 407 guide.curl: (56) Recv failure: Connection reset by peer / (52) Empty replyOn rotating and residential gateways this is what an unauthorized source IP looks like: curl connects, then the gateway resets the connection. Confirm Authorized IPs and wait 15 minutes after saving. If it only happens under load, reduce parallel processes.curl: (35) ... wrong version numberProxy URL written as https://. Use -x http://GATEWAY_IP:PORT.curl: (5) Could not resolve proxyA typo in the proxy host, or a stray environment variable pointing at an old proxy. Run env | grep -i proxy.curl: (28) Operation timed outA slow site or a saturated plan. Add --connect-timeout 10 --max-time 30 --retry 2 and lower parallelism.HTTP_PROXY (uppercase) is set, which cURL ignores, or no_proxy matches the host. Use lowercase http_proxy or pass -x.FAQ
Do I need -U for Storm rotating proxies?
No. Rotating and residential gateways check your source IP instead of a login, so -x http://GATEWAY_IP:PORT is enough once your IP is authorized. -U is only for private dedicated proxies set to username and password.
Why does cURL show the same IP twice in one command?
cURL reuses the open connection for the second URL, and the Main gateway assigns the IP per connection. Run separate commands, or use the CURLOPT_FRESH_CONNECT option in PHP.
Can I use Storm proxies with curl --socks5?
No. Storm gateways are HTTP(S) proxies only. Use -x http://..., which works for both http and https sites.
Why does PowerShell say “A parameter cannot be found that matches parameter name ‘x’”?
You ran the curl alias for Invoke-WebRequest. Type curl.exe to run the real cURL.
Can I ignore certificate errors with -k?
You shouldn’t need to. HTTPS through an HTTP proxy is a tunnel, so the site’s own certificate is checked as normal. If you see certificate errors, the cause is usually an outdated CA bundle or an intercepting corporate proxy, not the proxy you added.
How do I check which IP a proxy gives me from the command line?
curl -x http://GATEWAY_IP:PORT https://api.ipify.org prints just the exit IP, which makes it easy to compare runs in a loop.
Still have questions? Contact us here. A real person answers.
Related guides
Tool facts checked against the official documentation (October 2026): curl man page · Everything curl: HTTP proxy · Everything curl: proxy environment variables · libcurl CURLOPT_PROXY · PHP curl_setopt. Storm Proxies facts: our plans page and refund policy.
Unlimited bandwidth. One flat monthly price.
Access is live the moment you pay, and the smallest package of each proxy type has a 24-hour money-back guarantee on your first order.