curl Cheatsheet

TLS Options

Use this curl reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

Verification is on by default

curl verifies the certificate chain and the hostname on every https:// URL. When it fails you get exit 60 and a clear message, and the fix is almost never -k.

curl https://expired.example.com/
# curl: (60) SSL certificate problem: certificate has expired
FlagEffectUse when
-k / --insecureSkip chain and hostname checksThrowaway debugging only
--cacert <f>Verify against this PEM bundleCorporate or private CA
--capath <dir>Directory of hashed CA certsOpenSSL-style CA dir
--ca-nativeAlso use the OS trust storeWindows or macOS system CAs
--crlfile <f>Check a CRL for revocationRevocation list on hand
--cert-statusRequire a stapled OCSP responseVerifying stapling
--pinnedpubkey <sha256//..>Pin the public keyPinning a known peer
curl --cacert /etc/ssl/corp-root.pem https://internal.example.com/
curl --pinnedpubkey 'sha256//AbCd...=' https://ex.com/

-k disables the check, it does not diagnose it. Anything that runs with -k is unauthenticated and can be intercepted, so never leave it in a script or in .curlrc. Fix the trust store instead.

Reading the certificate

-v prints the negotiated cipher, the peer's subject and issuer, and the validity window, which is enough to spot an expired or wrong-host cert.

curl -vI https://example.com 2>&1 | grep -E 'subject|issuer|expire|SSL|ALPN'
curl -so /dev/null -w '%{ssl_verify_result} %{http_version}\n' https://ex.com/

For the full chain, openssl is the better tool. curl tells you whether curl trusts it, openssl tells you what was actually sent.

openssl s_client -connect example.com:443 -servername example.com \
  </dev/null 2>/dev/null | openssl x509 -noout -dates -subject -issuer

Pinning a TLS version

--tlsv1.2 and friends set a minimum, not an exact version. That surprises people who expect --tlsv1.2 to reject TLS 1.3. Pair it with --tls-max for an exact pin.

FlagMeaning
--tlsv1.0 … --tlsv1.3Minimum acceptable TLS version
--tls-max <1.0|1.1|1.2|1.3|default>Maximum acceptable version
--ciphers <list>TLS 1.2 and earlier cipher list
--tls13-ciphers <list>TLS 1.3 cipher suites
--curves <list>Allowed key exchange curves
--no-alpnDo not advertise ALPN
curl --tlsv1.2 --tls-max 1.2 https://ex.com/    # exactly TLS 1.2
curl --tlsv1.3 https://ex.com/                  # 1.3 or better
curl --tlsv1.2 --ciphers 'ECDHE-RSA-AES128-GCM-SHA256' https://ex.com/

--tls-max has no effect on an HTTP/3 transfer, because QUIC always uses TLS 1.3.

Which TLS backend you have

curl is built against a TLS library and the available options follow from it. curl -V is the first thing to check when a --ciphers value is rejected.

curl -V
# curl 8.7.1 ... (SecureTransport) LibreSSL/3.3.6 zlib/1.2.12 nghttp2/1.67.1
BackendNotes
OpenSSL / LibreSSL / BoringSSLWidest option support, PEM --cacert
GnuTLSDifferent --ciphers grammar
Schannel (Windows)Uses the OS store, --cacert support is limited
Secure Transport (older macOS)Uses the Keychain, deprecated upstream
wolfSSL / mbedTLSEmbedded builds, reduced feature set

The system curl on macOS may be linked against Secure Transport, so --cacert behaves differently from a Homebrew curl built on OpenSSL. Check which binary you are running with command -v curl before debugging a trust problem.

--resolve, testing before DNS cutover

--resolve host:port:addr overrides DNS for one host and port pair while keeping the real Host header and the real SNI name. That means TLS verification still works, which is the whole point.

# hit the new server, but present and verify example.com
curl --resolve example.com:443:203.0.113.10 https://example.com/health

# same for the plain HTTP port
curl --resolve example.com:80:203.0.113.10 http://example.com/

# every host on 443 goes to one box
curl --resolve '*:443:203.0.113.10' https://example.com/

You need one entry per port. Extras worth knowing: [brackets] around an IPv6 address, several comma-separated addresses in one entry (7.59.0), * as the host for a wildcard (7.64.0), and a + prefix to let the entry expire after curl's one minute default (7.75.0).

curl --resolve 'ex.com:443:[2001:db8::1],203.0.113.10' https://ex.com/

--resolve vs the alternatives

ApproachHost headerSNITLS verify
--resolve ex.com:443:IPex.comex.compasses
--connect-to ex.com::new.host:ex.comex.compasses
https://IP/ -H 'Host: ex.com'ex.comIPfails
Editing /etc/hostsex.comex.compasses, machine wide
curl --connect-to 'example.com::staging.internal:443' https://example.com/
curl --doh-url https://dns.example/dns-query https://example.com/

--connect-to redirects by name rather than address, so it works when the target is itself a DNS name. The -H 'Host:' trick against a bare IP is the one to avoid, since SNI no longer matches and you end up needing -k, which throws away the verification you were trying to test.