Tutorial

cURL to Python: convert a curl command to requests and httpx

curl to Python, tested: requests follows redirects curl stops at, a data= string loses its Content-Type and httpx quits after 5 s. Every fix, option by option.

HProxy Team··Updated October 11, 2026·9 min read
HProxy.Tutorial

Skip the dead lists.

Our free proxy list re-checks every exit every few minutes across 100+ countries, with a live last-checked time, so you copy IPs that worked moments ago, not a stale text dump.

Open the free proxy list→

To convert a curl command to Python, map each option to an argument of requests or httpx. -H becomes headers, -d becomes data, --json becomes json, -u becomes auth, and -x with -U becomes a proxy URL with the login inside. Then check the result, because neither library copies curl's defaults. In our side-by-side test on 11 October 2026, requests followed redirects that curl stopped at. A data= string went out with no Content-Type, and httpx gave up on a slow answer after 5 seconds.

We sent 19 curl commands from our own server with curl 8.5.0, requests 2.34.2 and httpx 0.28.1. We also ran the code that the converter curlconverter generated from each command. Every request went to an echo that shows what arrived. This page gives the mapping, the differences we measured and the fix for each.

Which Python argument does each curl option become?

curlrequestshttpx
-X POST, or -d alonerequests.post(url, ...)httpx.post(url, ...)
-H "Name: value"headers={"Name": "value"}headers={"Name": "value"}
-d "a=1&b=2"data={"a": "1", "b": "2"}data={"a": "1", "b": "2"}
--json '{"a": 1}'json={"a": 1}json={"a": 1}
-F "file=@a.txt"files={"file": f}, f opened with "rb"files={"file": f}
-G --data-urlencode "q=x y"params={"q": "x y"}params={"q": "x y"}
-u USER:PASSauth=("USER", "PASS")auth=("USER", "PASS")
-b "session=abc"cookies={"session": "abc"}cookies={"session": "abc"}
-Irequests.head(url)httpx.head(url)
no -Lallow_redirects=Falsethe default
-Lthe defaultfollow_redirects=True
-kverify=Falseverify=False
--compressedthe defaultthe default
-m 20timeout=20, per waittimeout=20, per wait
-x http://host:port -U USER:PASSproxies={"http": P, "https": P}proxy=P

In the last row, P is http://USER:PASS@host:port. Two rows need care. A string in data= is not the same as -d, as the next sections show. And timeout= is not --max-time. curl's option limits "each transfer", while requests says its timeout "is not a time limit on the entire response download". It limits each wait for data, and httpx counts "network inactivity" the same way.

How do you convert a command step by step?

You need Python 3 with requests or httpx installed (pip install requests httpx), and the curl command you want to convert. Take this command. It logs in, asks for JSON, sends two form fields and stops after 20 seconds:

curl https://httpbin.org/anything \
  -u USER:PASS \
  -H "Accept: application/json" \
  -d "name=alice&age=30" \
  --max-time 20
  1. Pick the method. With -d, curl sends a POST "in the same way that a browser does" with a form. So the call is requests.post.
  2. Copy the headers. Each -H becomes one entry in headers.
  3. Move the body. Split the -d string into a dict. The library then sends a form with the right Content-Type.
  4. Carry the login over. -u USER:PASS becomes auth=("USER", "PASS").
  5. Match curl's defaults. curl stops at a redirect without -L, so requests needs allow_redirects=False. Pick a timeout as well.

With requests:

import requests

response = requests.post(
    "https://httpbin.org/anything",
    auth=("USER", "PASS"),
    headers={"Accept": "application/json"},
    data={"name": "alice", "age": "30"},
    timeout=20,
    allow_redirects=False,  # curl does not follow redirects without -L
)
print(response.status_code, response.json()["form"])

With httpx, where redirects stay off by default:

import httpx

response = httpx.post(
    "https://httpbin.org/anything",
    auth=("USER", "PASS"),
    headers={"Accept": "application/json"},
    data={"name": "alice", "age": "30"},
    timeout=20,
)
print(response.status_code, response.json()["form"])

Both printed 200 {'age': '30', 'name': 'alice'} in our run. That is the same form the echo received from curl, with the same login.

What changes when you keep the defaults?

These are the differences we measured with the same requests from all three clients:

Casecurl 8.5.0requests 2.34.2httpx 0.28.1
User-Agentcurl/8.5.0python-requests/2.34.2python-httpx/0.28.1
Body given as a stringa form, with -dno Content-Type (data=)no Content-Type (content=)
Accept header for JSONapplication/json (--json)*/* (json=)*/* (json=)
Page that redirects twicestops at 302200, after 2 redirectsstops at 302
Self-signed certificateexit code 60SSLErrorConnectError
gzip, nothing setraw gzip bytesdecodeddecoded
Answer after 7 seconds200 after 8.1 s200 after 7.2 sReadTimeout after 5.4 s
Terminal output of our test: the same requests from curl, requests and httpx, and what the echo received from each
Our own capture, 11 October 2026: the saved results of our 01:32 UTC run on our server, printed by summarize.py. Echo: httpbin.org.

Redirects. curl follows a redirect only with -L. The requests documentation says it "will perform location redirection for all verbs except HEAD". HTTPX does the opposite: "Unlike requests, HTTPX does not follow redirects by default". Our guide to curl and redirects covers the curl side.

A body given as a string. curl's -d labels the body application/x-www-form-urlencoded. In requests, a dict is form-encoded, but "if you pass in a string instead of a dict, that data will be posted directly". Our echo received that string with no Content-Type and an empty form. A strict API may answer 415 Unsupported Media Type to it. In httpx, raw text belongs in content=, and data= with text is deprecated.

JSON. --json sets both Content-Type and Accept to application/json. In both libraries, json= sets only the Content-Type, and our echo saw Accept: */*. If an API checks Accept, add the header yourself. The curl POST guide shows the curl options for JSON.

Timeouts. In requests, calls "do not time out unless a timeout value is set explicitly". httpx raises a timeout "after 5 seconds of network inactivity". Against a page that answered after 7 seconds, curl and requests waited, and httpx gave up after 5.4 seconds. See Python requests timeout for the settings.

Compression and certificates. Without --compressed, curl printed the raw gzip bytes, while both libraries decoded them. All three refused a self-signed certificate until told otherwise: -k in curl, verify=False in the libraries, where requests adds an InsecureRequestWarning. Keep verification on outside tests; cURL ignore SSL shows the safer options.

User-Agent. The server saw three different programs. A site that treats clients by this header can answer each of them differently.

Can a converter do it for you?

curlconverter is an open-source converter that can "transpile curl commands into" Python and many other languages. We ran its requests code for all 19 commands, and it behaved like curl in 15.

Two of the differences are documented. The converted code follows redirects and decodes gzip, because "the generated code will do whatever the default is for that runtime". The project's advice is to set the redirect policy in the command. With --no-location, the code got allow_redirects=False.

The other two were the proxy cases, and the project's README says nothing about them. curlconverter 4.12.0 turned -U USER:PASS into a proxy setting without the login, and it gave no warning. Our logging proxy received the user name from curl, requests and httpx, and nothing from the converted code. Its parser keeps the value, but its Python generator never reads it.

Terminal output: the requests code curlconverter generated from curl -x with -U, and what our logging proxy received from each client
Our own capture, 11 October 2026: the saved results of our 01:36 UTC run, printed by show_proxy_case.py. The proxy ran on our server on 127.0.0.1.

A proxy that requires a login answers such code with 407. That status says the client "needs to authenticate itself in order to use a proxy". The fix is simple. Write the login into the proxy URL before you convert, as -x http://USER:PASS@host:port. curlconverter kept credentials written that way.

How do you send the request through a proxy?

curl takes the proxy with -x and the login with -U, "the username and password to use for proxy authentication". Python puts both into one URL. The requests documentation says to "use the http://user:password@host/ syntax". httpx takes the login "as the userinfo section of the proxy URL". With our gateway's address and placeholder credentials:

curl -x http://premium.hproxy.com:10000 -U USER:PASS https://httpbin.org/anything
import requests

proxy = "http://USER:PASS@premium.hproxy.com:10000"
response = requests.get(
    "https://httpbin.org/anything",
    proxies={"http": proxy, "https": proxy},
    timeout=20,
)
import httpx

response = httpx.get(
    "https://httpbin.org/anything",
    proxy="http://USER:PASS@premium.hproxy.com:10000",
    timeout=20,
)

We ran both blocks through our logging proxy instead of the gateway. Each answered 200, and the proxy received the user name from both.

For a SOCKS proxy, install requests[socks] or httpx[socks] first. The scheme decides where the host name is looked up. In requests, "the scheme socks5 causes the DNS resolution to happen on the client". To resolve names on the proxy, you "use socks5h as the scheme", in line with curl. Our guides to proxies in Python requests and proxies in httpx go further. Lines from our residential proxies use the same URL form as above.

When should you run curl itself from Python?

Run curl itself when the command must stay exactly as written, for example with an option neither library has. Use subprocess and pass the arguments as a list. The Python documentation explains that a list "allows the module to take care of any required escaping and quoting of arguments". With shell=True, the quoting is your job, and mistakes open "shell injection vulnerabilities".

import subprocess

result = subprocess.run(
    ["curl", "-sS", "--max-time", "20", "https://httpbin.org/anything"],
    capture_output=True,
    text=True,
    timeout=30,
)
print(result.returncode, result.stdout[:80])

In our run it returned 0 and the echo's answer. Read result.returncode before you trust the output, since curl reports failures there, such as 60 for a certificate it does not trust. To use curl's engine inside Python instead, PycURL is "a Python interface to libcurl", for libcurl 7.19.0 or later.

How do you check that the conversion worked?

Send the curl command and the Python code to the same echo and compare what arrived. https://httpbin.org/anything returns the method, the URL, the headers and the body as it parsed it. Compare five things: the method, the Content-Type, the form or JSON fields, the Authorization header and where redirects ended.

This is how the string trap shows itself. The same body sent as data="name=alice&age=30" came back as an empty form with no Content-Type. curl's -d came back with both form fields. Every difference on this page turned up this way.

What this page could not check

  • We tested on Linux with curl 8.5.0, the build Ubuntu 24.04 ships, not the newest release, 8.22.0, and not curl.exe on Windows.
  • Every request went to httpbin.org, an echo service, or to our own stub proxy. We did not test a real API, an HTTPS proxy or a SOCKS proxy.
  • Our stub proxy accepted every request, so the lost login showed in its log, not as a 407.
  • We tested one converter, curlconverter 4.12.0, not uncurl or other online converters. A later release may handle -U.
  • These were single runs, so the timings include ordinary network jitter. We did not measure how many sites treat the Python User-Agents differently from curl's.
  • requests, httpx and curlconverter change between releases. We will run the test again by 11 January 2027.

Sources

  • curl manual, curl project, current version, read 11 October 2026: curl.se.
  • Requests documentation, Quickstart and Advanced Usage, Requests 2.34.2 (14 May 2026), read 11 October 2026: requests.readthedocs.io.
  • HTTPX documentation, Requests Compatibility, Timeouts and Proxies, HTTPX 0.28.1 (6 December 2024), read 11 October 2026: python-httpx.org.
  • Python documentation, subprocess, read 11 October 2026: docs.python.org.
  • curlconverter, README and the package source of version 4.12.0 (7 February 2025), read 11 October 2026: github.com/curlconverter.
  • PycURL, README, read 11 October 2026: github.com/pycurl.
  • RFC 9110, HTTP Semantics, section 15.5.8, IETF, June 2022: rfc-editor.org.
  • Our own test, 11 October 2026, 01:32 to 01:36 UTC, from our server: 19 curl commands against requests, httpx and curlconverter's code, and the page's code blocks.

Frequently asked questions

How do I convert a curl command to Python requests?
Map each curl option to a requests argument: -H to headers, -d to data as a dict, --json to json, -u to auth, -b to cookies, -k to verify=False, and -x with -U to proxies with the login in the URL. Add allow_redirects=False if the command has no -L, and set a timeout. Then send both to an echo service such as httpbin.org/anything and compare.
Why does my request work in curl but not in Python requests?
Because the libraries do not copy curl's defaults. In our test, a data= string went out with no Content-Type, requests followed redirects that curl stopped at, and each client sent its own User-Agent. Send the same request from both to an echo service and compare the headers and the body.
What is the Python equivalent of curl -d?
data= with a dict. curl -d 'name=alice&age=30' becomes data={'name': 'alice', 'age': '30'}, which requests sends as a form. A string in data= is posted as it is, and in our test it arrived with no Content-Type, so the server did not read it as a form.
Does Python requests follow redirects like curl?
No. curl follows a redirect only with -L. requests follows redirects by default for every method except HEAD, so add allow_redirects=False to copy curl. httpx does not follow them unless you pass follow_redirects=True.
How do I use curl's -x and -U proxy options in Python?
Put the login into the proxy URL. In requests, pass proxies={'http': P, 'https': P} with P = 'http://USER:PASS@host:port'. In httpx, pass proxy=P. Converters can lose -U: curlconverter 4.12.0 dropped it without a warning in our test, so write the login into the -x URL before you convert.
Why does httpx time out when requests does not?
httpx raises a timeout after 5 seconds of network inactivity by default, while requests waits until you set a timeout. In our test, a page that answered after 7 seconds came back through requests, and httpx gave up after 5.4 seconds. Pass timeout=10, or whatever your server needs.

Get proxies that are alive right now

Our free proxy list re-checks every exit every few minutes across 100+ countries, with a live last-checked time, so you copy IPs that worked moments ago, not a stale text dump. When the location has to survive a real check, the paid network holds up.

129M+ proxy checks run · 100+ countries · HTTP / HTTPS / SOCKS · re-checked every few minutes · no signup

HProxy.

Honest guides and comparisons on proxies, scraping and staying unblocked, from the team that runs the network.

RSS feed