This directory contains scripts to build a custom curl from pinned upstream Git tags and a libunistring release archive, install it into a local prefix, and optionally package that prefix into a Docker runtime image.
The resulting curl is built with OpenSSL 4, ECH/HTTPS RR, HTTP/2 via nghttp2, HTTP/3 via ngtcp2/nghttp3, c-ares, zlib, Brotli, zstd, libidn2, libpsl, libssh, OpenLDAP, and MIT Kerberos/GSS-API.
build-curl.shfetches and builds curl plus all pinned dependencies.test-curl.shruns post-build smoke tests against the built curl.build-docker-image.shpackages the already-built prefix into a Docker image.Dockerfiledefines the runtime image used bybuild-docker-image.sh..dockerignorekeeps the Docker context focused oncurl-build/prefix.
On Ubuntu, the build script expects common build tooling such as:
sudo apt install autoconf automake autopoint bison build-essential cmake curl flex gettext \
gengetopt git gperf libtool make perl pkg-config byacccurl downloads the libunistring release archive; wget is also supported as
a fallback.
Docker is only needed for image packaging:
sudo apt install docker.io
sudo usermod -aG docker "$USER"After changing Docker group membership, start a new login session before using Docker as your normal user.
Run:
./build-curl.shThe script builds under curl-build/:
curl-build/srccontains Git checkouts and extracted release sources.curl-build/buildcontains out-of-tree build directories.curl-build/downloadscaches the libunistring release archive.curl-build/prefixcontains the final installed curl and dependencies.curl-build/statecontains cache keys for successfully completed components.curl-build/logscontains one numbered log per fetch, patch, and build stage.
The terminal reports the current numbered stage and, when attached to a
terminal, displays a spinner. Full command output is redirected to the
corresponding file in curl-build/logs/, keeping normal output concise while
preserving configure and compiler diagnostics. On failure, the script reports
the affected log and prints its final 40 lines.
For example, inspect the curl build log with:
less curl-build/logs/17-building-curl.logSuccessful components are cached by their source identity, build flags,
dependency cache keys, installation prefix, and the contents of
build-curl.sh. An unchanged rerun reuses those components and avoids fetching
an already available requested Git ref or libunistring release archive.
Use these controls when a rebuild or source refresh is needed:
BUILD_CACHE=0 ./build-curl.sh # rebuild every component
REFRESH_SOURCES=1 ./build-curl.sh # refresh Git checkouts and the archiveThe resulting binary is:
./curl-build/prefix/bin/curlCheck it with:
./curl-build/prefix/bin/curl --versionThe Features: line should include ECH, HTTPSRR, GSS-API, Kerberos,
and SPNEGO.
After a build, run the smoke test suite:
./test-curl.shThe script checks the compiled dependency versions against build-curl.sh,
enabled protocols and features, runtime library linkage, HTTP/1.1, HTTP/2,
HTTP/3, c-ares DNS server override support, gzip, Brotli, zstd negotiation,
ECH, HSTS, Alt-Svc, LDAPS, FTP, SFTP, SCP, and WSS.
For local-only checks without public network requests:
SKIP_NETWORK=1 ./test-curl.shThe local-only mode reports each omitted network assertion as SKIP, so its
summary still shows exactly which coverage was not run.
Network checks are grouped by protocol area and run concurrently by default.
NETWORK_JOBS limits how many groups can run at once; the default is 4 and
NETWORK_JOBS=1 runs them serially when diagnosing a service or constrained
network. PASS, FAIL, and SKIP use green, red, and yellow status labels in
an interactive terminal. Output redirected to a file remains plain text.
NETWORK_JOBS=1 ./test-curl.shThe smoke test confirms MIT Kerberos/GSS-API support is present, reported by
curl, and linked from curl-build/prefix. It does not perform a live
Kerberos/SPNEGO authentication exchange because that requires a configured
Kerberos realm, credentials, and an HTTP/FTP/SOCKS/SSH service that accepts
GSS-API or Negotiate authentication. Validate that separately in the target
Kerberos environment.
FTP, SFTP, SCP, and WSS default to public test endpoints:
ftp://demo:password@test.rebex.net/sftp://demo:password@test.rebex.net/scp://demo:password@test.rebex.net/readme.txtwss://echo.websocket.org/
Override them by providing URLs:
FTP_TEST_URL=ftp://example.test/path \
SFTP_TEST_URL=sftp://user@example.test/path \
SCP_TEST_URL=scp://user@example.test/path \
WS_TEST_URL=wss://example.test/socket \
./test-curl.shCloudflare's trace endpoint reports whether SNI was encrypted. This test uses DNS-over-HTTPS so curl can retrieve the HTTPS resource record that carries the ECHConfigList:
./curl-build/prefix/bin/curl -v \
--tlsv1.3 \
--ech hard \
--doh-url https://cloudflare-dns.com/dns-query \
https://crypto.cloudflare.com/cdn-cgi/traceA successful run includes verbose output similar to:
ECH: result: status is succeeded, inner is crypto.cloudflare.com, outer is cloudflare-ech.com
The response body should include:
sni=encrypted
Most versions and paths can be overridden with environment variables. Examples:
CURL_BUILD_SUFFIX=mybuild ./build-curl.shWORK_DIR="$PWD/out" PREFIX="$PWD/out/prefix" ./build-curl.shThe default curl version stamp is 8.21.0-i81b4u, and the release date defaults
to the build date. Override both like this:
CURL_BUILD_SUFFIX=i81b4u CURL_RELEASE_DATE=2026-06-08 ./build-curl.shFor a ready-to-run image, use the published Docker Hub image.
After ./build-curl.sh has completed, build the runtime image:
./build-docker-image.shThe helper tags the image with the curl version and latest, for example:
byo-curl:8.21.0-i81b4u
byo-curl:latest
Run curl from the image:
docker run --rm byo-curl:latest --versionRun the installed smoke test (the image entrypoint normally runs curl):
docker run --rm --entrypoint /opt/byo-curl/bin/test-curl.sh byo-curl:latestFor an offline metadata and linkage-only run, add -e SKIP_NETWORK=1.
Run a MLKEM test on www.google.com:
docker run --rm byo-curl:latest \
--silent --head --tlsv1.3 --curves MLKEM1024 \
https://www.google.comRun an HTTP/2 test on www.cloudflare.com:
docker run --rm byo-curl:latest \
--silent --head --http2 \
https://www.cloudflare.comRun an HTTP/3 and MLKEM test on www.cloudflare.com:
docker run --rm byo-curl:latest \
--silent --head --tlsv1.3 --curves X25519MLKEM768 --http3 \
https://www.cloudflare.comRun an ECH test on crypto.cloudflare.com:
docker run --rm byo-curl:latest \
--verbose --tlsv1.3 --ech hard \
--doh-url https://cloudflare-dns.com/dns-query \
https://crypto.cloudflare.com/cdn-cgi/traceStart a shell inside the runtime image:
docker run --rm -it --entrypoint /bin/bash byo-curl:latestStart smoke test script inside the runtime image:
docker run --rm --entrypoint /opt/byo-curl/bin/test-curl.sh i81b4u/byo-curl:latestTo change the Docker image name:
IMAGE_NAME=my-curl ./build-docker-image.sh-
OpenLDAP
OPENLDAP_REL_ENG_2_6_13needs a small source edit for OpenSSL 4 because it still dereferences opaqueASN1_STRINGinternals. The build script applies that edit before building OpenLDAP. -
libunistring is intentionally downloaded from the official GNU release archive rather than cloned from Git. Its Git checkout requires a separate gnulib fetch, which has caused timeouts and incomplete checkouts. The archive contains the generated configure and gnulib files, so the build avoids that extra network dependency and still builds only the library/header subtree;
texinfois not required. -
MIT Kerberos is built from the
krb5-1.22.2-finalgit tag. curl is configured with--with-gssapi, which enables GSS-API, Kerberos, and SPNEGO support. The smoke test verifies the build artifacts and runtime linkage for this support but leaves live realm authentication to the deployment environment. -
c-ares is built from the
v1.34.8git tag. curl is configured with--enable-ares, soAsynchDNScomes from c-ares instead of the POSIX threaded resolver. The smoke test exercises this at runtime with curl's--dns-serversoption against Cloudflare DNS by default. -
RTMP/librtmp is intentionally not included. curl 8.20.0 removed RTMP support, so building librtmp would not make this curl support RTMP.
A successful build should report features similar to:
curl 8.21.0-i81b4u ... OpenSSL/4.0.1 ... c-ares/1.34.8 ... nghttp2/1.69.0 ngtcp2/1.24.0 nghttp3/1.17.0 ...
Features: alt-svc AsynchDNS brotli ECH GSS-API HSTS HTTP2 HTTP3 HTTPS-proxy HTTPSRR IDN IPv6 Kerberos Largefile libz PSL SPNEGO SSL threadsafe TLS-SRP UnixSockets zstd