In this episode we'll open the engine behind curl: libcurl with the curl_easy and curl_multi architecture, the cross-language binding ecosystem, when to use the CLI or the library, how to build from source, and starting to embed via --libcurl.

In episode 19, you ran curl --libcurl request.c and produced a C code file without writing a single line. That's not just a cool trick — it's the doorway into the world behind curl. For 19 episodes, we've used curl as a tool from the terminal. Now it's time to open the hood and see the actual engine: libcurl.
The fundamental difference is this. The curl CLI is a finished car: you get in, start the engine, and reach your destination. libcurl is that engine itself — it can be installed into any car you build yourself. Understanding libcurl unlocks capabilities impossible through the terminal: requests from inside an application, full control over the lifecycle, data streaming, and integration with your programming language's error handling.
curl and libcurl are two different entities. curl is a command-line program run by humans and scripts. libcurl is the C library that performs raw data transfers — and the curl CLI is just one of its "users". This explains why --libcurl can produce equivalent code: the CLI and the library share the same transfer logic.
The most fitting analogy: curl is a restaurant serving dishes, libcurl is the kitchen. The restaurant can change menus or close — the kitchen is still used by many other chefs. Every time you see "powered by libcurl" behind another tool, remember it's the same kitchen as curl.
curl_easy and curl_multi Architecturelibcurl works through two main interfaces. curl_easy is the most basic: one handle, one transfer, executed synchronously. The flow is always the same — init, set options, perform, cleanup:
#include <curl/curl.h>
int main(void)
{
CURL *hnd = curl_easy_init();
curl_easy_setopt(hnd, CURLOPT_URL, "https://api.example.com/users");
CURLcode ret = curl_easy_perform(hnd);
curl_easy_cleanup(hnd);
return (int)ret;
}Those four core lines are the skeleton of every libcurl program. curl_easy_setopt is where you set all the options you've been typing as CLI flags — URL, headers, timeout, and more, each as a CURLOPT_* constant.
curl_multi is the level above: many transfers in one process without separate threads. This is how libcurl handles hundreds of concurrent connections — essentially the same foundation as --parallel in episode 19:
CURLM *multi = curl_multi_init();
curl_multi_add_handle(multi, easy_a);
curl_multi_add_handle(multi, easy_b);
curl_multi_perform(multi, &running);
curl_multi_cleanup(multi);Imagine curl_easy as one employee serving one customer to completion, while curl_multi is a supervisor watching many employees at once — efficient for applications downloading or testing many resources in parallel. That's why many crawlers and download managers are fast: they run on curl_multi, not on one thread per request.
libcurl's influence goes beyond C. Many programming language ecosystems build bindings on top of it — or choose to build their own HTTP stack. Reading this table helps you understand where the "HTTP brain" of your application comes from:
| Ecosystem | HTTP implementation | Notes |
|---|---|---|
Python pycurl | libcurl | Direct binding, all libcurl features available |
Python requests | Own stack (urllib3) | Doesn't use libcurl, pure Python |
| PHP cURL extension | libcurl | The de-facto HTTP standard in the PHP ecosystem |
Rust curl crate | libcurl | A clean binding to libcurl |
Node.js node-libcurl | libcurl | libcurl binding for JavaScript |
Ruby curb | libcurl | libcurl binding for Ruby |
Go net/http | Own stack | Native implementation, no libcurl |
The pattern to see: there are two philosophies. Ecosystems like PHP and Rust choose to stick to libcurl — decades of data transfer experience come for free. Ecosystems like Go and Python choose their own implementation — full control, free from C dependencies. Neither is wrong; what matters is knowing which is which when debugging. A "timeout" error from requests and from pycurl can come from completely different code.
The most practical question: when do I use the curl CLI, when do I use bindings in an application?
subprocess) is expensive and fragile: bootstrapping a new process per request, prone to quoting issues, and complicating error handling. If the request is part of an application, use bindings.The rule of thumb: if a request can be done by a human in the terminal, use the CLI. If a request is done by a program as part of business logic, use the library.
Sometimes your system's packaged curl doesn't include the features you need — for example HTTP/3 or WebSocket. The solution is building it yourself. libcurl uses a flexible build system; the two most common approaches are configure and CMake:
./configure --with-openssl --enable-websocket
make
sudo make installBuilding from source gives you three advantages: choosing features explicitly, using the latest version before distro packages catch up, and controlling dependencies like OpenSSL and the HTTP/3 library. Once installed, verify with curl --version that the desired features appear in the Features line. Also make sure the libcurl library is installed — for C development, you need the development package like libcurl4-openssl-dev.
--libcurlThe best way to start writing libcurl code is not writing from scratch — let curl write the skeleton for you. In episode 19, you already generated a file:
curl --libcurl request.c https://api.example.com/usersThe generated file is a complete template with curl_easy_init, curl_easy_setopt for the URL, and curl_easy_cleanup. The next step is compiling it:
gcc -o request request.c -lcurl
./requestThe -lcurl flag links the program against the libcurl library. If you want to know exactly where that library comes from, pkg-config --libs libcurl and curl-config --version show its location and version. From this small template, you can add callbacks to capture the response body, set timeouts, or swap curl_easy for curl_multi when requests start multiplying.
Episode 20 opens the layer that has been hidden behind the terminal: libcurl as the library the curl CLI runs, the curl_easy architecture for one synchronous transfer and curl_multi for parallel transfers, the cross-language binding ecosystem map, a guide on when to use the CLI or the library, how to build from source, and the easiest entry point via --libcurl.
The core takeaway: curl is the tool, libcurl is the foundation — and the same foundation supports other tools in your ecosystem. Understanding both means you're no longer "driving a car without understanding the engine".
In the next episode 21, we'll raise everything to production level: production readiness and security hardening — proper TLS verification, secrets management, measured timeouts and retries, up to a security checklist for backends using curl. See you!