In this episode we'll dissect how HTTPS is secured: CA certificate verification by curl, client certificates for mutual TLS, choosing TLS versions and cipher suites, and reading handshake details with verbose.

In episode 11, you made curl resilient — timeout, retry, and rate limit keep requests from hanging and giving up. But there's a more fundamental question we haven't answered: who are you actually talking to? When curl fires at https://, a layer works before a single byte of HTTP moves — that layer is TLS.
Imagine sending an important letter through a public post office. An envelope can be sealed, but who guarantees the envelope reaches the correct address and isn't opened along the way? TLS answers two things at once: encryption (nobody can read the contents) and authentication (you're certain the other party is the real server). Episode 12 is about understanding — and controlling — the security layer most often taken for granted.
HTTPS isn't a magical new protocol — it's HTTP running inside a TLS tunnel. Before an HTTP request is sent, curl and the server perform a TLS handshake: exchanging parameters, agreeing on a cipher suite, verifying identity, then building the session encryption keys. Once the tunnel is formed, HTTP runs normally inside it, invisible to anyone in between.
Check your curl build's version and features first — TLS support isn't something you can assume exists:
curl --versionIn the output, look at the Features line. If the word SSL is there, your curl is built with TLS support — almost all modern distributions have it, but minimalist builds sometimes don't. This feature list also shows HTTPS-proxy, HTTP2, and others we'll use in the coming episodes.
The core of TLS authentication is the digital certificate. A server certificate is issued by a Certificate Authority (CA) — a party trusted to "sign" over server identities. But in the real world there are thousands of CAs, so certificates are arranged in a chain:
The key concept is chain of trust: curl doesn't need to trust the leaf certificate directly — it just verifies that the chain ends at a CA that's already trusted. This is like an ID card: you've never met the issuer, but because its signature can be verified up to an institution you trust, the card is trusted.
Tip
The same analogy applies to CAs: you trust the root certificate, and that certificate "signs" the certificates below it. If one link in the chain is questionable, the whole chain falls — and that's what an unbreakable chain of trust means.
When you call https://, curl performs three checks at once before the HTTP request is sent:
notBefore and notAfter.All three checks are active by default. None of them should be skipped without a reason.
The CA bundle file is a collection of root certificates installed in the operating system. curl uses it automatically; its location differs per distribution. Because this bundle is a collection of "people you trust", changing it means changing your definition of trust — so update it through the system mechanism (apt update/dnf update), not by overwriting the file.
--cacert and --capath: Controlling the BundleFor special environments — like a corporate internal CA or staging servers — you can point to a specific bundle or directory:
curl --cacert /etc/ssl/certs/ca-certificates.crt https://api.example.comcurl --capath /etc/ssl/certs https://api.example.comThe difference: --cacert points to a single bundle file, while --capath points to a directory containing many certificates. Installing an internal CA into the system directory (/usr/local/share/ca-certificates/ then update-ca-certificates) makes curl trust it without extra flags — a much cleaner approach than adding a flag in every script.
If verification fails — expired certificate, hostname mismatch, or unknown CA — curl refuses the connection and exits with code 60. Note the message:
curl: (60) SSL certificate problem: certificate has expiredcurl refuses before sending any data. That's not a bug — that's a feature. Your data is never sent to an unverified party.
-k Is Dangerous-k (alias --insecure) turns off certificate verification. It's the fastest way to make error 60 disappear — and also the fastest way to open the door to man-in-the-middle attacks. Without verification, anyone on the network path — an ISP, a dishonest proxy, or an attacker on public wifi — can slip in a fake certificate and read the entire contents of your request as if they were the real server.
# DON'T do this in production
curl -k https://api.example.com/secureImagine locking your front door then giving the key to everyone on the street. -k is exactly that: the encryption remains, but the key is shared with anyone. If an endpoint forces you to use -k, the right question isn't "how do I disable verification?", but "why doesn't this server have a valid certificate?".
Warning
The only acceptable exception: momentary local testing against a server intentionally using a self-signed certificate for development — and even then, it's better replaced with an internal CA installed into the bundle, not by disabling verification. In production, -k never has a reason.
Verification so far is one-way — the client verifies the server. There's a stricter scheme, mutual TLS (mTLS), where the server also verifies the client. The server asks the client to prove its identity with a client certificate. curl sends it via --cert and --key:
curl --cert client.pem --key client-key.pem \
https://api.example.com/secure--cert client.pem — the client certificate (public key + identity).--key client-key.pem — its matching private key.If you have a certificate in PKCS#12 format (a single .p12 file containing both), hand it over along with the password:
curl --cert-type p12 --cert client.p12:rahasia123 \
https://api.example.com/securemTLS is used for strict service-to-service communication — for example between microservices inside a cluster, or connections from CI to an internal registry. In episode 21 we'll discuss when mTLS is mandatory and how to secure its private keys — for now, understand that identity can be verified in both directions.
Important
The private key in --cert is the most sensitive asset in this request. Never write the .p12 password directly on the command line — use a prompt or an environment variable, exactly as we'll discuss thoroughly in episode 13. A leaked key is an identity card that can be forged.
TLS has generations: TLS 1.0 and 1.1 have long been considered obsolete and are almost always rejected by modern servers. TLS 1.2 and 1.3 are the current standards. curl automatically picks the highest version both sides support — but for firm policy, you can lock the bounds:
curl --tlsv1.2 --tls-max 1.2 https://api.example.comcurl --tlsv1.2 --tls-max 1.3 https://api.example.comThe first command forces exactly TLS 1.2 (combined minimum and maximum). The second sets a minimum of TLS 1.2 and a maximum of 1.3 — the healthiest combination for production: no legacy versions accepted, while still leaving room for the faster TLS 1.3. --tls-max limits the highest version; --tlsv1.2 sets the lowest. curl's default already picks the best version, but asserting the bounds removes ambiguity in environments with strict policy.
A cipher suite is the complete encryption recipe: the agreed key exchange, authentication, and symmetric encryption algorithms. Each TLS version has its own list. For TLS 1.3, the --tls13-ciphers option chooses among the few modern ciphers; for TLS 1.2 and below, --ciphers accepts a colon-separated list:
curl --tlsv1.3 --tls13-ciphers TLS_AES_256_GCM_SHA384 \
https://api.example.comcurl --tlsv1.2 \
--ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384 \
https://api.example.comIn practice, setting ciphers manually is rarely needed — curl's defaults pick the best the server supports. This understanding is more useful as vocabulary when reading server configurations or debugging why a handshake was rejected (for example because the server only allows certain ciphers).
-vAll the theory above becomes real when you watch the handshake with your own eyes. The -v (verbose) option from episode 9 shows every stage:
curl -v https://example.com 2>&1 | head -25* Trying 104.18.20.123:443...
* Connected to example.com (104.18.20.123) port 443
* ALPN: offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN: server accepted h2
> GET / HTTP/2Reading these lines gives you the complete story:
Trying and Connected — the TCP connection is formed.ALPN: offers h2,http/1.1 — curl offers HTTP/2 (we'll dissect it in episode 14).Client hello through Finished — stage by stage of the TLS 1.3 handshake.SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 — the key summary: the agreed TLS version and cipher suite.ALPN: server accepted h2 — the server agrees to use HTTP/2.If the handshake fails, these lines stop midway — and there you know which stage is problematic. This is the most valuable debugging skill for TLS issues: don't guess, read the evidence.
Tip
Check when a server certificate expires without guessing: echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates. Reading the notBefore and notAfter dates answers half the SSL certificate problem cases before you even touch curl.
Episode 12 opens the hood of the HTTPS security layer: understanding that TLS is a layered tunnel over HTTP, reading certificate chains up to the trust anchor, controlling the CA bundle with --cacert and --capath, avoiding -k for strong reasons, sending client certificates with --cert and --key for mutual TLS, choosing TLS versions with --tlsv1.2 and --tls-max, constraining cipher suites, and dissecting the handshake line by line with -v.
The core thing to remember: certificate verification isn't a formality — it's a verifiable identity. Disabling verification doesn't make the problem disappear; it just sweeps it under the rug, and anyone passing by can take your data.
In the next episode 13, we'll close the security circle from a different angle: secrets management — how to store passwords, tokens, and credentials so they don't leak through shell history, process lists, or logs. See you!