← Back to list

TLS, Certificates, and Certificate Authorities: A Complete Mental Model (From Absolute Zero)

TLS is one of those topics almost every backend engineer relies on daily and almost none can explain from scratch. You configure it for a…

Nitish Rana · 2026-07-08 11:33 · 0 claps · 19.2 min read paywalled
#tls #tls-certificate #computer-networking
Open on Medium ↗
Wiki topics: LIT · Literature & Writing 🌐 · Web Development

TLS, Certificates, and Certificate Authorities: A Complete Mental Model (From Absolute Zero)

TLS is one of those topics almost every backend engineer relies on daily and almost none can explain from scratch. You configure it for a database, an API, an internal service — you copy a .crt file into the right folder, you move on — until the day something doesn't line up, and you realize you never actually understood what any of these files or steps are for.

This article takes the slow road on purpose. We’re going to build up TLS, certificates, keys, and Certificate Authorities from nothing — no assumed networking knowledge, no assumed cryptography knowledge. Along the way we’ll use real, concrete examples — a Node.js app connecting to MongoDB, a browser talking to an HTTPS API, services inside Docker and Kubernetes talking to each other — but the concepts underneath apply to any client and server that need a secure connection, whatever they happen to be.

Grab a coffee. This is a long one, but it’s the last time you’ll need to learn this.

1. Why do we even need TLS?

Let’s start with the simplest possible picture of two computers talking to each other — it could be a web browser and a server, a backend app and a database, or two microservices in the same cluster.

 ┌─────────────┐                         ┌─────────────┐
 │   Client    │ ───── raw bytes ──────► │   Server    │
 │             │ ◄──── raw bytes ─────── │             │
 └─────────────┘                         └─────────────┘

Under the hood, this connection travels across a network — maybe just a Docker bridge network on your laptop, maybe the public internet, maybe a corporate data center. Somewhere along that path, there are routers, switches, cables, and Wi-Fi access points that your data physically passes through.

Here’s the uncomfortable truth: anyone who controls a device along that path can read every byte you send, unless you do something about it.

Without protection, credentials, tokens, and application data all travel as plain text — readable by anyone who intercepts them, the same way a postcard can be read by anyone who handles it in transit, unlike a sealed letter.

Client sends:  {"user": "admin", "pass": "hunter2", "op": "login"}

 Attacker on the network sees exactly that. No effort required.

The Man-in-the-Middle problem

It gets worse. Without any way to verify who you’re actually talking to, an attacker can sit in the middle of the conversation, pretending to be the server to your client, and pretending to be your client to the server.

 ┌─────────────┐        ┌───────────────┐        ┌─────────────┐
 │   Client    │ ─────► │   Attacker    │ ─────► │   Server    │
 │             │ ◄───── │  (pretends to │ ◄───── │             │
 └─────────────┘        │   be both)    │        └─────────────┘
                        └───────────────┘

The attacker can read everything, modify data in flight, or quietly steal credentials — and neither side notices, because nothing about a plain TCP connection tells you who is really on the other end.

This is why sensitive traffic — logins, payment data, database queries, internal API calls — should never travel unencrypted. We need three things:

  1. A way to know we’re really talking to the server we intended to talk to (authentication)
  2. A way to make sure no one in the middle can read the data (encryption)
  3. A way to know the data wasn’t tampered with in transit (integrity)

That’s exactly what TLS was built to provide. But before we get into TLS itself, we need to talk about what a “protocol” even is.

2. What is a protocol?

A protocol is simply an agreed-upon set of rules for communication. Nothing more mystical than that. Humans use protocols constantly without naming them: when you call someone, you say “hello,” they say “hello” back, then you talk, then you say “bye” before hanging up. That’s a protocol — a shared script both sides follow so the conversation makes sense.

Computer networking is built out of layers of these agreed-upon scripts, each one responsible for a different part of the job. Think of it like nested envelopes:

 ┌───────────────────────────────────────────┐
 │  Application data (e.g. an API request,   │
 │  a database query, a chat message)        │
 │  ┌───────────────────────────────────────┐│
 │  │  TLS  (encrypts + authenticates)      ││
 │  │  ┌───────────────────────────────────┐││
 │  │  │  TCP  (reliable delivery, order)  │││
 │  │  │  ┌───────────────────────────────┐│││
 │  │  │  │  IP  (addressing, routing)    ││││
 │  │  │  └───────────────────────────────┘│││
 │  │  └───────────────────────────────────┘││
 │  └───────────────────────────────────────┘│
 └───────────────────────────────────────────┘
  • IP figures out how to route a packet from one machine to another across networks.
  • TCP sits on top of IP and guarantees your bytes arrive in order, without gaps, and get retransmitted if lost.
  • HTTP is a protocol built on top of TCP that defines how web requests and responses look (GET /users HTTP/1.1, status codes, headers, etc). Other systems — MongoDB, PostgreSQL, Redis, gRPC services — define their own application-level wire protocols instead, but the shape of the idea is the same: an agreed format for exchanging application-level messages.
  • TLS also sits on top of TCP, below whichever application protocol is being used. It doesn’t care whether the data flowing through it is HTTP, a database wire protocol, or anything else. Its job is narrower and more focused: encrypt the bytes, verify identity, and detect tampering.

This is the first big misconception to knock out early: TLS is not a certificate, and it’s not a piece of software you install. It’s a protocol — a set of rules two programs follow to establish a secure channel before the “real” conversation begins, whatever that conversation happens to be.

3. What is TLS actually doing?

TLS (Transport Layer Security — the modern name for what used to be called SSL) exists to provide three guarantees on top of a plain TCP connection:

Authentication — “Am I talking to who I think I am?”

Imagine calling a bank and someone answers claiming to be your bank. Without some way to verify that, you’d never read your account number out loud. TLS gives the server a way to prove its identity cryptographically, so the client doesn’t have to just take its word for it.

Encryption — “Can anyone eavesdrop?”

Once identity is established, TLS scrambles the data using a key that only the two parties in the conversation know, so anyone intercepting the traffic just sees noise.

Without TLS:  {"user": "admin", "pass": "hunter2"}
 With TLS:     8f 3a c2 91 d4 6b 00 17 ... (unreadable garbage)

Integrity — “Was this tampered with in transit?”

TLS also attaches a cryptographic checksum to the data so that if even a single bit is altered in transit (by accident or by an attacker), the receiving side can detect it and reject the message.

Put together: TLS lets two computers verify each other’s identity, agree on a secret only they know, and exchange data that can’t be silently read or altered. Everything else in this article is really just explaining how those three guarantees are actually achieved.

4. Public key cryptography from scratch

Before we can talk about certificates, we need to understand the tool that makes them possible: public key cryptography (also called asymmetric cryptography).

Forget math for a second. Here’s the analogy.

Imagine a special kind of padlock that has two different keys:

  • Key A can lock the padlock, but cannot unlock it.
  • Key B can unlock the padlock, but cannot lock it.

Whoever owns Key B can hand out copies of the padlock (and even Key A) to anyone in the world. People can use Key A to lock messages inside the padlock and send them over. But only the person holding Key B can ever open it.

That’s the essence of a key pair:

  • The private key is Key B — kept secret, never shared, never leaves the owner’s machine.
  • The public key is Key A — freely distributed to anyone, because possessing it only lets you lock things for the owner, not unlock things the owner locked.
                 ┌───────────────┐
   Data  ─────►  │  Public Key   │ ─────►  Encrypted Data
                 │  (Key A)      │
                 └───────────────┘
   Encrypted    ┌───────────────┐
   Data   ─────►│  Private Key  │ ─────► Original Data
                │  (Key B)      │
                └───────────────┘

There’s a second trick this same pair of keys can do, and it’s the one that actually matters for certificates: signing.

Instead of “locking” data so only the private key holder can read it, the private key holder can “sign” data — producing a small stamp that anyone with the public key can verify came from that private key, without ever being able to forge that stamp themselves.

Private key  ──sign──►   Signature   ──verify with public key──► valid / invalid

This asymmetry — one key that can only verify, the other that can only create — is the entire foundation of trust on the internet. Public keys are safe to hand out to literally anyone, because possessing a public key only lets you verify things or encrypt things for that owner. It never lets you impersonate them or read what’s meant for them.

5. What is a certificate?

Here’s a problem public/private keys alone don’t solve: if a server hands you its public key, how do you know it’s actually that server’s public key, and not an attacker’s?

A certificate is the answer. It’s essentially a digital ID card that bundles together:

  • Who this certificate belongs to (e.g. api.example.com, or mongodb.mycompany.com)
  • That entity’s public key
  • Who is vouching for this information (the issuer)
  • A validity period (not before / not after dates)
  • A digital signature from the issuer, proving the above hasn’t been tampered with
┌─────────────────────────────────────────────┐
 │  CERTIFICATE                                 │
 │  Subject:      api.example.com               │
 │  Public Key:   3f:9a:...:d2 (this server's)  │
 │  Issuer:       "Some Trusted CA"             │
 │  Valid From:   2026-01-01                    │
 │  Valid Until:  2027-01-01                    │
 │  Signature:    <signed by issuer's private key>│
 └─────────────────────────────────────────────┘

A certificate is not a secret. It’s meant to be handed out freely — that’s the whole point, since it only contains a public key, not a private one. Anyone can look at it and, if they trust the issuer, trust the identity and public key it describes.

But that raises the obvious next question: why should anyone trust the issuer?

6. What is a Certificate Authority (CA)?

Here’s the catch with certificates: absolutely anyone can create one. You could open a terminal right now and generate a certificate claiming to be google.com. The certificate itself doesn't magically know whether it's telling the truth — it's just a signed document.

Attacker generates:
 ┌─────────────────────────────────────────────┐
 │  Subject: google.com                         │
 │  Public Key: <attacker's own key>            │
 │  Issuer: "totally legit CA" (self-signed)    │
 └─────────────────────────────────────────────┘

Anyone can create this. So the real question isn’t “is this certificate well-formed,” it’s “do I trust whoever signed it?”

That’s the entire job of a Certificate Authority (CA): it’s a trusted third party whose sole purpose is to verify identity before signing a certificate, vouching for it with its own signature.

  • Public CAs (like Let’s Encrypt, DigiCert, or Amazon’s ACM) are trusted by essentially every browser and operating system by default. Your OS and browser ship with a built-in list of these CAs’ public keys, called a trust store. This is what makes visiting an HTTPS website “just work” without any extra configuration on your end.
  • Internal / private CAs are ones companies run themselves, typically for infrastructure that’s never meant to be reachable from the public internet — an internal database cluster, a service mesh, an admin dashboard on a private network. Nobody outside the company trusts this CA by default — you have to explicitly tell your application to trust it, usually by handing it the CA’s certificate file.

A CA has its own key pair too, just like any other entity:

CA
        ┌───────────────────────────┐
        │  CA Private Key (secret)  │ ── signs other certificates
        │  CA Public Key (shared)   │ ── used to verify those signatures
        └───────────────────────────┘

When the CA signs a certificate for some server — a website, a database, an internal API — it uses its own private key to produce that signature. Anyone holding the CA’s public key (bundled inside the CA’s own certificate) can verify that the signature is genuine — i.e., that this certificate really was vouched for by that CA, and hasn’t been altered since.

This is exactly why an application often ships with a file like ca.crt or mongodb_ca.crt — it's not the server's certificate, it's the CA's certificate, so the client has something to check the server's certificate against.

7. How a server certificate is actually created

This is the part that trips up almost everyone, because the server itself is not usually the thing generating trusted certificates. Here’s the real workflow an admin (or a script, or Terraform, or a tool like cert-manager in Kubernetes) follows — whether the server in question is a database, an internal API, or anything else:

Step 1: Generate a private key for the server
 ──────────────────────────────────────────────────────
   openssl genrsa -out server.key 2048
 Step 2: Create a Certificate Signing Request (CSR)
 ──────────────────────────────────────────────────────
   openssl req -new -key server.key -out server.csr \
     -subj "/CN=mongodb.mycompany.com"
   (The CSR contains the public key + identity info,
    "please sign this for me" — but NOT the private key.)
 Step 3: Send the CSR to the CA
 ──────────────────────────────────────────────────────
   CA verifies the requester really controls that domain/
   hostname, then signs the CSR using the CA's private key.
 Step 4: CA returns a signed certificate
 ──────────────────────────────────────────────────────
   server.crt   ← signed certificate
   ca.crt       ← the CA's own certificate (public)
 Step 5: The server is configured to use both files
 ──────────────────────────────────────────────────────
   - server.key  (private key — stays on the server, secret)
   - server.crt  (server's signed certificate — public)

openssl is the tool doing the heavy lifting in most of these steps — generating key pairs, building CSRs, and (if you're running your own internal CA) doing the actual signing. It's the Swiss army knife of TLS tooling, and almost every guide you'll find online leans on it. A managed database like MongoDB Atlas, an HTTPS web server, or a self-hosted MongoDB cluster all follow this same basic shape, just with different tooling wired around it (Atlas issues certificates for you automatically; a self-hosted cluster usually relies on an internal CA someone on the team set up).

The important takeaway: the private key never leaves the machine that generated it. The CSR only ever contains the public key. The CA never sees, needs, or touches the private key at any point. This is exactly why the padlock analogy from Section 4 matters — everything that travels between admin and CA is safe to expose, because none of it lets anyone impersonate the server.

8. PEM, CRT, CER, KEY, CSR — what are all these files?

This is where a lot of confusion comes from, because these extensions are mostly conventions, not strict formats. Here’s the plain-English breakdown:

Extension What it usually contains .pem A generic container format (Base64 text wrapped in -----BEGIN ...----- / -----END ...-----). Can hold a certificate, a private key, or both. "PEM" is a format, not a specific kind of content. .crt / .cer Almost always a certificate (public info: identity + public key + signature). Usually PEM-encoded text, sometimes binary (DER). .key A private key. Should be treated as a secret — restrictive file permissions, never committed to git, never logged. .csr A Certificate Signing Request — the "please sign this" document sent to a CA. Contains a public key and identity info, not a signed certificate yet.

A typical PEM-encoded certificate looks like this (this is just structure, not a real key):

-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAJC1H...(base64 data)...
-----END CERTIFICATE-----

And a private key file looks similar but with a different header:

-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqh...(base64 data)...
-----END PRIVATE KEY-----

So when someone says “give me the PEM,” they usually mean “give me the cert or key file,” but technically PEM just describes how the bytes are encoded, not what’s inside them. This is why you’ll sometimes see a single .pem file that contains a certificate and a private key concatenated together — convenient for some tools, dangerous if you're not careful about who gets access to that file.

9. The TLS handshake, step by step

Now we have all the pieces — key pairs, certificates, CAs — to walk through what actually happens on the wire when a client connects to a server over TLS, whether that’s a browser hitting an HTTPS API or a backend app connecting to a database. This exchange is called the TLS handshake, and it all happens before a single byte of real application data is sent.

Client                                      Server
        │                                            │
        │ 1. ClientHello                             │
        │    "Here are the TLS versions and          │
        │     cipher suites I support"               │
        │ ─────────────────────────────────────────► │
        │                                            │
        │ 2. ServerHello                             │
        │    "Let's use TLS 1.3 with cipher X"       │
        │ ◄───────────────────────────────────────── │
        │                                            │
        │ 3. Certificate                             │
        │    Server sends its certificate            │
        │    (contains its public key)               │
        │ ◄───────────────────────────────────────── │
        │                                            │
        │ 4. Client verifies the certificate:        │
        │    - Is it signed by a CA I trust?         │
        │      (checks against a trusted CA cert)    │
        │    - Has it expired?                       │
        │    - Does the hostname match?              │
        │                                            │
        │ 5. Server proves it owns the private key   │
        │    (signs a piece of the handshake data;   │
        │     client verifies using the public key   │
        │     from the certificate)                  │
        │◄──────────────────────────────────────────►│
        │                                            │
        │ 6. Both sides derive a shared session key  │
        │    using key-exchange math (e.g. Diffie-   │
        │    Hellman) — this key never travels       │
        │    over the wire in plain form             │
        │◄──────────────────────────────────────────►│
        │                                            │
        │ 7. Secure channel established.             │
        │    All further application traffic is      │
        │    encrypted with the shared session key.  │
        │◄══════════════════════════════════════════►│

A few things worth calling out explicitly:

  • Step 3 is why the server needs its certificate readily available — it’s sent over the wire, in the clear, as part of the handshake. That’s fine, because a certificate contains no secrets.
  • Step 4 is why the client needs a trusted CA certificate on hand. Verifying a signature requires the CA’s public key, and that public key lives inside the CA’s own certificate. For public websites, this CA certificate usually already lives in your browser or OS’s trust store; for internal services, it typically has to be supplied explicitly.
  • Step 5 is the crucial proof step. Anyone could copy a certificate and try to present it as their own — but they can’t fake proving ownership of the private key, since that key never left the real server.
  • Step 6 produces a brand new symmetric session key, used only for this connection, that both sides know but that never traveled across the network in a form an eavesdropper could extract.

Only after all of this does the actual application traffic — an HTTP request, a database query, a chat message — get sent, now wrapped inside an encrypted, authenticated tunnel.

10. “But the public key is public — why can’t attackers abuse it?”

This is the question that trips people up the most, so let’s answer it head-on.

“If the CA’s public key is public, why can’t an attacker use it to sign fake certificates?”

Because the public key can only verify signatures — it mathematically cannot produce them. Remember the padlock analogy: Key A can lock, but only Key B can unlock. Signing works in the mirror-image direction: only the private key can produce a valid signature; the public key can only check whether a given signature was genuinely produced by that private key.

“Why can’t an attacker just generate their own fake certificate for a domain or server they don’t own?”

They can generate one trivially — that’s not the hard part. The hard part is getting the client to trust it. A client doesn’t trust certificates in general; it trusts certificates signed by a specific CA whose public key it already has. An attacker’s self-signed fake certificate won’t verify against a trusted CA’s public key, so the handshake fails and the connecting application throws exactly the kind of error you’ll see in the debugging section below — unable to verify the first certificate or self signed certificate.

“Couldn’t an attacker steal the CA’s public key and use it?”

They already have it — it’s public! And it doesn’t help them at all, for the reason above. What they’d actually need is the CA’s private key, which is kept extremely locked down precisely because it’s the one thing that lets someone mint certificates the world will trust.

11. Why applications ship with a CA certificate file (and never a private key)

By now the shape of a file like ca.crt or mongodb_ca.crt sitting inside an app's config should make a lot more sense. A client needs this file because, per the handshake above, it has to verify the server's certificate against something. That something is the CA's public certificate. The client will never need, and should never have, the server's private key — that stays exclusively on the server.

Server                              Client Application
  ┌───────────────────┐             ┌───────────────────────┐
  │ server.key (secret)│            │ ca.crt (public)         │
  │ server.crt (public)│  ───────► │  used to verify what    │
  └───────────────────┘  handshake  │  the server presents    │
                                     └───────────────────────┘

Let’s make this concrete with a couple of common examples.

Example: connecting to MongoDB over TLS from Node.js

const { MongoClient } = require("mongodb");
const client = new MongoClient(process.env.MONGO_URI, {
  tls: true,
  tlsCAFile: "/app/certs/mongodb_ca.crt",
  // Only needed if MongoDB requires client certificates too (mutual TLS):
  // tlsCertificateKeyFile: "/app/certs/client.pem",
});
async function main() {
  await client.connect();
  console.log("Connected securely over TLS");
}
main().catch(console.error);

Notice: tlsCAFile points at the CA's certificate, not MongoDB's own certificate, and definitely not any private key.

Example: an internal service calling an internal HTTPS API

The same idea applies outside of databases. If a Node.js service calls another internal service secured with a certificate from your company’s private CA, it needs that same kind of trust configuration:

const https = require("https");
const fs = require("fs");
const agent = new https.Agent({
  ca: fs.readFileSync("/app/certs/internal-ca.crt"),
});
https.get("https://internal-api.mycompany.local/status", { agent }, (res) => {
  console.log("Status code:", res.statusCode);
});

Same pattern, same reasoning — the client holds a CA certificate to verify the server, never a private key.

Docker

In Docker, a CA certificate file typically gets mounted into the container as a volume, rather than baked into the image (baking secrets/certs into images is a common anti-pattern, since anyone who pulls the image can extract them):

# docker-compose.yml
services:
  app:
    image: my-node-app
    volumes:
      - ./certs/mongodb_ca.crt:/app/certs/mongodb_ca.crt:ro
    environment:
      - MONGO_CA_PATH=/app/certs/mongodb_ca.crt

If this mount is missing, misspelled, or points at a path that doesn’t exist inside the container, your app will fail to even find the CA file before it gets anywhere near the TLS handshake — worth checking first whenever certificate-related startup errors show up, regardless of which service the certificate belongs to.

Kubernetes

In Kubernetes, this is typically stored as a Secret and mounted as a file into the pod:

apiVersion: v1
kind: Secret
metadata:
  name: mongodb-ca-cert
type: Opaque
data:
  mongodb_ca.crt: <base64-encoded CA certificate>
---
# In your Deployment spec:
volumeMounts:
  - name: ca-cert-volume
    mountPath: /app/certs
    readOnly: true
volumes:
  - name: ca-cert-volume
    secret:
      secretName: mongodb-ca-cert

This same pattern — a Secret mounted as a read-only file — is how most internal CA certificates get distributed to pods, whatever service they’re meant to authenticate.

12. Production vs. local development

If you’ve only ever run services locally without TLS and never had a problem, that’s completely normal, and here’s why:

  • Local development usually happens on localhost or inside a private Docker network you fully control. There's no untrusted network path between client and server — they might even be on the same machine — so the risk TLS protects against barely applies. Many teams skip TLS locally to reduce setup friction, for databases, internal APIs, and everything in between.
  • Production traffic, on the other hand, often crosses real networks: between cloud regions, across VPC boundaries, through load balancers, or over the public internet. Here, TLS is close to non-negotiable, since the “nobody else is on this network” assumption no longer holds.

Managed cloud providers reflect this directly in their defaults:

  • MongoDB Atlas enforces TLS on all connections by default — you can’t turn it off.
  • AWS, GCP, and Azure managed database offerings similarly default to encrypted connections, often issuing certificates automatically via their own managed CAs.
  • Public HTTPS APIs rely on public CAs (like Let’s Encrypt) so any browser or client can verify them without extra configuration.
  • Internal company infrastructure — a self-hosted database, an internal admin panel, service-to-service traffic inside a private network — is exactly where you’ll see a hand-rolled internal CA, because there’s no public CA that would ever issue a certificate for an internal hostname.

A reasonable rule of thumb: if there’s any chance the traffic leaves a network boundary you fully trust and control, use TLS — no matter what kind of service is on the other end.

13. Things I misunderstood when I first learned TLS

Everyone builds a slightly wrong mental model before the right one clicks. Here are the most common ones, corrected:

“TLS is a piece of software.” No — TLS is a protocol, a set of rules. OpenSSL, BoringSSL, and Node’s built-in tls module are implementations of that protocol, but TLS itself is just the agreed-upon handshake and message format.

“A database or server creates its own trusted certificates.” No — a server can generate a self-signed certificate for testing, but nothing trusts a self-signed certificate by default. Trusted certificates come from a CA, whether that’s a public one or an internal one your company runs.

“The server sends its private key during the handshake.” Never. The private key never leaves the server. The handshake only ever transmits the certificate (public info) and a signature proving the server holds the corresponding private key — not the key itself.

“Public keys need to be kept secret, just like private keys.” The opposite is true — public keys are meant to be shared freely. Security depends entirely on the private key staying secret; the public key’s job requires it to be public.

“A certificate is an encryption key.” A certificate isn’t a key at all — it’s a signed document that contains a public key along with identity information. The actual encryption uses a session key negotiated during the handshake, not the certificate itself.

“PEM is a type of certificate.” PEM is just an encoding format (Base64 text with header/footer lines). A .pem file could contain a certificate, a private key, or both — the extension alone doesn't tell you.

“TLS and HTTPS are the same thing.” HTTPS is just HTTP running on top of TLS. TLS itself is protocol-agnostic — databases, internal APIs, email servers, and countless other systems use TLS too, with nothing to do with HTTP.

14. A debugging checklist for common TLS problems

Now that you have the full mental model, these failure categories should stop feeling like magic — whether they show up while connecting to a database, an internal service, or anything else over TLS:

Missing or unreadable certificate/key files Your application can’t locate a certificate or CA file at the path it’s configured to look for. Check your volume mounts (Docker) or Secret mounts (Kubernetes), and confirm the path in your app’s config actually matches where the file lands inside the container.

**unable to verify the first certificate** Your client received a certificate chain it can't fully verify — usually because it's missing an intermediate certificate, or it doesn't have the right CA certificate loaded at all. Double-check your CA file config is pointing at the correct CA cert, and that it's actually the CA that signed the server's certificate.

**self signed certificate** The server presented a certificate that isn't signed by any CA your client trusts — often because it's self-signed and your client hasn't been explicitly told to trust it via a CA file option, or a flag like tlsAllowInvalidCertificates (only ever appropriate for local testing, never production).

**certificate verify failed** A general-purpose failure during certificate validation — could be an expired certificate, an untrusted CA, or a broken chain. Inspect the certificate with openssl x509 -in server.crt -text -noout to check its issuer and expiry dates.

**hostname mismatch / IP address mismatch** The certificate's Subject (or Subject Alternative Names) doesn't match the hostname you're connecting to. This happens often when connecting via an IP address or a different DNS name than the one the certificate was issued for — certificates are tied to specific hostnames, not just "the server."

A generally useful debugging habit: use OpenSSL to manually inspect what a server is actually presenting, independent of your application:

openssl s_client -connect example.com:443 -CAfile ca.crt

This shows you the full certificate chain the server sends and whether it verifies against your CA file — often faster to debug than digging through application logs, and works the same way whether you’re debugging a database connection, an HTTPS API, or an internal service.

15. The complete mental model

Here’s everything from this article in a single picture:

                         ┌────────────────────────────┐
                         │   Certificate Authority     │
                         │  (Public CA or Internal CA) │
                         │                             │
                         │  CA Private Key (secret) ───┼─── signs
                         │  CA Public Key  (public) ───┼─── verifies
                         └───────────────┬─────────────┘
                                         │
                          signs CSR into a certificate
                                         │
                                         ▼
 ┌────────────────────────┐   handshake   ┌───────────────────────────┐
 │      Client Application │◄─────────────►│           Server           │
 │                          │                │  (database, API,          │
 │  ca.crt (public)         │  1. ClientHello│   internal service, etc.) │
 │  used to verify what     │  2. ServerHello│                           │
 │  the server presents     │  3. Certificate│  server.key  (secret)     │
 │                          │  4. Verify sig │  server.crt  (public)     │
 │                          │  5. Prove key  │  sent during handshake    │
 │                          │     ownership  │                           │
 │                          │  6. Derive     │                           │
 │                          │     session key│                           │
 └────────────────────────┘◄══════════════►└───────────────────────────┘
                          Encrypted, authenticated
                             traffic flows from
                                here onward

Every piece of this diagram maps back to a real file, a real config option, or a real line in a stack trace — whether the server on the other end is MongoDB, an HTTPS API, or any other TLS-secured service. The CA’s private key never leaves the CA. The server’s private key never leaves the server. The client never holds anything secret at all — just a public certificate it uses to check the server’s identity. That asymmetry, repeated at every layer, is the entire reason TLS works.


메타데이터
post_id
810138f0e1c2
slug
tls-certificates-and-certificate-authorities-a-complete-mental-model-from-absolute-zero-810138f0e1c2
url
https://medium.com/@nitishrana848/tls-certificates-and-certificate-authorities-a-complete-mental-model-from-absolute-zero-810138f0e1c2
canonical_url
https://medium.com/@nitishrana848/tls-certificates-and-certificate-authorities-a-complete-mental-model-from-absolute-zero-810138f0e1c2
author_url
https://medium.com/@nitishrana848
status
ok
fetched_at
2026-07-24 08:12:30