Skip to main content

Concepts

Why nothing inside the network is trusted without proof, and how to run the CA that makes it possible.

This book is a working reference for putting mutual TLS (mTLS) between every internal component of a self-hosted environment, backed by a private certificate authority you run yourself. It opens with the case for doing it, then walks through standing up the CA, issuing and installing certificates, enforcing mTLS at the proxy layer, and the part that makes this sustainable, keeping renewal boring. The specific use-case I provide as an example is Web Application Firewall (WAF)-to-host, but the same pattern can be applied to other services.


Why internal traffic shouldn't be trusted

For a long time, internal networks ran on an assumption of trust by location. A request coming from inside the perimeter was treated as trustworthy simply because of where it came from. But location is not identity. Anything that gains ingress to the internal network including a compromised workstation, a rogue device, or a service host an attacker has gained a foothold on, inherits the trust the perimeter was supposed to gate. On a flat network where internal callers are trusted by default, one compromised device can reach every service that assumes internal traffic is safe, because nothing along the way asks it to prove who it is. The blast radius of a single foothold is everything.

Mutual TLS (mTLS) breaks that assumption. It does two things. It encrypts every internal connection, so a device that manages to intercept traffic cannot read it, and it requires both ends of every connection to present a certificate proving who they are, issued by an authority both sides trust. A backend configured for mTLS does not just encrypt, it refuses connections that cannot present a valid certificate. Being on the network is no longer enough; you have to hold a key.

This does not make a compromised host harmless, and it's worth being precise about what it does and doesn't do. An attacker who compromises a host can use whatever certificate that host legitimately holds, to reach whatever that certificate was authorized to reach. What mTLS changes is the scope of a compromise. On a network based on location trust, one foothold reaches everything. With mTLS, one foothold reaches only what that specific identity was permitted to reach, and a device holding no valid certificate cannot open an authenticated connection to a protected service at all. The boundary moves from "inside the network" to "holds a valid identity," and the blast radius shrinks from everything to one identity's authorized reach.

What "identity" means here: When a person logs in, they prove identity with something they know or hold, a password, a second factor, a passkey. A machine can't do that, so it proves identity a different way; with a certificate. A certificate is a small signed document that says "this host is service.int.example," countersigned by an authority both sides trust. When a service presents its certificate, the other end can verify that signature and know it is talking to the real holder of that name, not an impostor. That is all a machine identity is, a name, bound to a cryptographic key, vouched for by a trusted authority. Where a user identity answers "which person is this," a machine identity answers "which host or service is this," and mTLS is simply both ends asking that question of each other before exchanging any traffic.


Why a private certificate authority

Certificates need an issuer both ends trust. For public websites that's a public CA like Let's Encrypt. For internal service identity, a public CA is the wrong tool:

  • Public CAs won't issue for internal names. Your internal services have names like service.int.example that don't exist in public DNS and that no public CA will ever certify. Internal identity needs an issuer that will.
  • You want short lifetimes and full control. A private CA lets you issue certificates that live weeks, not years, and rotate constantly, which shrinks the value of a stolen key. You set the policy.
  • The CA becomes your internal root of trust. Every host is configured to trust one CA, and from then on "do you hold a certificate this CA signed?" is the question that gates internal communication. That root of trust is yours to run, not rented.

A private CA is, in effect, the identity system for machines, the same role an IdP plays for users. Users authenticate to an identity provider; services authenticate to each other with certificates from the CA. Both answer the same underlying question: prove you are who you claim to be before I trust you.


Why step-ca

The CA in this environment is step-ca from Smallstep. The reasons it fit:

  • Self-hostable and lightweight. It runs as a single service on an internal host, with no dependency on anyone else's infrastructure. For a self-hosted, privacy-focused environment, the machine identity root shouldn't live in someone else's cloud any more than the user identity root should.
  • Built around short-lived certificates. Step-ca's whole design philosophy is issue-often, expire-fast, automate-renewal, which is exactly the posture that makes stolen certs low-value. It makes the secure path the easy path.
  • Standards-based and ACME-capable. It issues ordinary X.509 certificates and can speak ACME, so the knowledge and the certificates work with standard tooling (nginx, curl, anything that speaks TLS).
  • Right-sized. It gives you a real CA, root, intermediate, provisioners, issuance policy, without standing up an enterprise PKI suite that would be absurd at a small scale.