Skip to main content

SSH, Let’s Encrypt, and the Two Phases of TLS Trust

A certificate is proof that something happened earlier.

That may be the cleanest place to begin.

When a server presents a TLS certificate, the certificate is not performing domain validation at that moment. It is presenting a signed artifact created during an earlier process.

Roughly, the certificate says:

A certificate authority previously verified control of this hostname and signed a statement binding that hostname to this public key.

The client encounters that artifact later.

This distinction is important because it separates two processes that are easy to mentally combine:

  1. Certificate issuance
  2. Certificate usage

The client is not a participant in the Let’s Encrypt issuance process.

The client enters the picture later, when it connects to the server and verifies the certificate that was already issued.

If your initial mental model is SSH public-key authentication, this separation makes the relationship much easier to understand.


Begin with the SSH mental model

In a common SSH public-key authentication setup:

SSH client:
owns the private key

SSH server:
has the client's public key in authorized_keys

The public key may be stored in:

~/.ssh/authorized_keys

The server has already been configured to trust that specific public key.

During authentication, the server effectively asks:

Prove that you possess the private key
matching this public key.

The client performs a cryptographic operation using its private key and sends the resulting signature.

The server verifies that signature using the public key it already has.

Server:
has public key

Client:
has matching private key

Client:
signs connection-specific data

Server:
verifies the signature

The private key is never transmitted.

The client proves possession of the private key without revealing it.

For a more detailed explanation of why the signature cannot simply be recorded and replayed, see SSH Public-Key Authentication: Why the Signature Is Not Reusable.

The simplified idea is that the client signs fresh, connection-specific data. You can think of part of that data as nonce-like: it is tied to this authentication event rather than being a reusable password.

SSH authentication is therefore not:

Client sends private key.

It is also not:

Client sends the same signature every time.

It is:

Server and client establish connection-specific data.

Client signs that data with the private key.

Server verifies the signature with the public key.

No public certificate authority is required here because the trust relationship was directly configured.

The server already knows exactly which public keys it accepts.

authorized_keys:

These exact public keys are allowed to authenticate.

There are two different SSH relationships

It is useful to be precise here because SSH has two identity questions.

The first is:

Can this client log in to this server?

That is the authorized_keys relationship.

The server stores the client’s public key, and the client proves possession of the matching private key.

The second is:

Is this actually the server I intended to connect to?

That is commonly handled using SSH host keys and the client’s known_hosts file.

Client authentication:

Client proves identity to server.


Server authentication:

Server proves identity to client.

HTTPS is more directly comparable to the second relationship: the server is proving its identity to the client.

The important difference is how the client learns which server key to trust.

SSH commonly uses direct trust:

Trust this exact server key.

Public HTTPS commonly uses delegated trust:

Trust this server key because a certificate authority
I already trust signed a certificate for it.

SSH certificates also exist, including deployments that use an SSH certificate authority, but they are not required for the common authorized_keys and known_hosts mental model discussed here.


Why HTTPS needs another trust model

Now consider:

curl https://api.example.com/users

Assume this machine has never contacted api.example.com before.

Unlike an SSH server with an authorized_keys file, curl does not already have the exact public key for api.example.com pinned in a local configuration file.

Curl therefore needs to answer a question:

How do I know that this public key
is actually authorized for api.example.com?

The server could simply claim:

This is my public key.

Trust me.

But any server could make that claim.

A malicious server could generate its own key pair and say:

I am api.example.com.

The cryptography could still function perfectly. The attacker could prove possession of its own private key.

The missing piece would be the connection between that public key and the hostname.

That is what the certificate authority system provides.


What a certificate adds

A certificate contains public information, including:

The subject hostname or hostnames

A public key

Validity dates

Information about the issuer

A signature from the certificate issuer

Conceptually, the important statement is:

This public key is valid for api.example.com.

The certificate authority signs that statement.

That signature lets a client verify that the certificate was issued through a chain leading back to a certificate authority the client already trusts.

The server keeps the private key matching the public key in the certificate.

Certificate:
public
contains the server public key
identifies the hostname
signed by a certificate authority

Private key:
secret
remains on the server

The certificate alone is not enough.

Anyone can copy a public certificate.

The server must also prove during the TLS handshake that it possesses the matching private key.


Let’s Encrypt belongs to the issuance phase

Let’s Encrypt is a certificate authority.

Its role is to issue certificates after verifying control of a domain.

Suppose you operate an API at:

api.example.com

Something in your infrastructure acts as an ACME client.

That might be:

Certbot

Caddy

Traefik

an Nginx integration

a Kubernetes certificate controller

another ACME-compatible client

The ACME client asks Let’s Encrypt for a certificate:

Please issue a certificate for api.example.com.

Let’s Encrypt does not simply accept the claim.

It responds:

Prove that you control api.example.com.

The proof commonly uses an HTTP challenge or a DNS challenge.

With an HTTP challenge, a specific token must be available through a specific path associated with the hostname.

With a DNS challenge, a specific DNS record must be published.

The exact mechanism differs, but the purpose is the same:

Demonstrate control of the hostname
for which the certificate is being requested.

Once the challenge succeeds, Let’s Encrypt issues a certificate binding the hostname to a public key.


The players during certificate issuance

There are three functional players in the simplified issuance model:

1. Your server or ACME client

2. Your domain-control mechanism

3. Let’s Encrypt

The domain-control mechanism might be:

An HTTP challenge response

A DNS TXT record

Credentials allowing the ACME client to update DNS

The client that will eventually call the API is not involved.

There is no curl request to your API.

There is no browser session.

There is no API consumer.

There is no /users endpoint involved.

Certificate issuance is a server-side setup process.

The client is not merely inactive during this phase.

It is not part of this interaction at all.

That is the first major concept to lock in:

The client does not participate in obtaining the certificate.


What the server receives

After issuance, the server or reverse proxy has two important forms of key material.

A typical Let’s Encrypt directory might contain files such as:

fullchain.pem
privkey.pem

Conceptually:

fullchain.pem:
server certificate
intermediate certificate chain
public certificate material

privkey.pem:
server private key
secret material

The private key may have been generated by the ACME client before issuance rather than being created and transmitted by Let’s Encrypt.

That distinction matters.

Let’s Encrypt generally signs a certificate associated with the public key supplied through the certificate request. It does not need to receive or retain the server’s private key.

The server ends the issuance phase with:

A private key it controls

A public certificate signed by Let’s Encrypt

The client receives nothing during this process.

No client certificate is created.

No client private key is created.

No token is delivered to curl.

No per-request credential is issued.


The issuance phase is now complete

At this point, Let’s Encrypt has done its job.

The server has a certificate.

That certificate can later be presented to many clients over many TLS connections.

Certificate issuance:

Server / ACME client

Let's Encrypt

The result is stored and used later.

There may be future renewal operations because certificates expire, but those renewals are also separate issuance operations.

They are not part of each API request.


Curl enters during the usage phase

Now, later, someone runs:

curl https://api.example.com/users

This is a separate process in the general sense of the word: a separate activity with different participants and a different purpose.

The players are now:

1. curl

2. The API server or TLS-terminating proxy

3. curl's local CA trust store

Let’s Encrypt is not contacted during the request.

The server presents the certificate that Let’s Encrypt issued earlier.

Curl evaluates that certificate using trust information already available on the client machine.

Curl does not ask Let’s Encrypt:

Is this certificate still legitimate?

as a normal live request-response step in every connection.

Instead, curl validates the cryptographic certificate chain locally.

The certificate includes signatures that can be checked against public keys in the chain.

That chain eventually reaches a trusted root certificate already present in the client’s trust store.


Why Let’s Encrypt is trusted

Let’s Encrypt is not authoritative because every client contacts it during every connection.

It is trusted because client systems already contain trusted certificate-authority roots.

A simplified certificate chain looks like this:

Certificate for api.example.com

signed by:

Let's Encrypt intermediate certificate

signed by:

A trusted root certificate

Curl or the TLS library verifies the chain one signature at a time.

Does the api.example.com certificate have a valid
signature from the intermediate?

Does that intermediate certificate have a valid
signature leading to a trusted root?

Is that root in the client's trust store?

If the chain reaches a root trusted by the client, the chain is accepted, assuming the other checks also pass.

The client’s trust does not begin with your API server.

It begins with its local trust anchors.

Client trusts root CA.

Root CA authorizes intermediate CA.

Intermediate CA signs server certificate.

Client accepts server certificate.

This is delegated trust.


The client is a verifier, not an issuance participant

This is the distinction that can become muddy when approaching TLS from an SSH mental model.

During Let’s Encrypt issuance:

Client:
absent

During the later TLS connection:

Client:
verifies the result of issuance

The client does not receive a private key.

The client does not receive a special certificate from Let’s Encrypt.

The client does not complete the ACME challenge.

The client does not participate in proving domain control.

The client later receives the server’s public certificate as part of the TLS handshake.

That is all.

Issuance:

Server proves domain control to Let’s Encrypt.


Usage:

Server proves private-key possession to curl.

Curl verifies the previously issued certificate.

These are different proofs performed at different times for different audiences.


Two different proof relationships

It helps to isolate the two claims.

Claim one: domain control during issuance

The requester proves to Let’s Encrypt:

I control api.example.com sufficiently
to complete the required ACME challenge.

Let’s Encrypt then creates a signed artifact representing the result:

This public key is valid for api.example.com.

Claim two: private-key possession during TLS

Later, the server proves to curl:

I possess the private key matching
the public key in this certificate.

Curl also verifies:

The certificate is valid for api.example.com.

The certificate chains to a trusted root.

The certificate is currently valid.

The certificate's signatures are valid.

The first proof creates the certificate.

The second proof uses the certificate.


Certificate authority versus private-key proof

The certificate authority does not prove that the server currently possesses the private key.

The server proves that itself during TLS.

Similarly, proving possession of a private key does not independently prove that the key belongs to api.example.com.

The certificate provides that binding.

Certificate authority signature:

Binds hostname to public key.


TLS private-key proof:

Shows that the current server possesses
the matching private key.

Both are required.

Without private-key possession, someone could copy the public certificate and impersonate the server.

Without the certificate-authority signature, a malicious server could generate its own key pair and claim any hostname.


The full timeline

The complete sequence is easier to see when the two phases are placed on one timeline.

The two phases may be separated by days or weeks.

A certificate might be issued on Monday and then used for thousands or millions of TLS connections before it is renewed.

Let’s Encrypt does not join each of those connections.


The certificate is scoped to a hostname

Suppose the certificate includes:

api.example.com

That certificate can be used for requests such as:

curl https://api.example.com/users
curl https://api.example.com/orders
curl https://api.example.com/auth/login

The certificate is not scoped to:

/users

/orders

/auth/login

Those are HTTP paths.

The certificate is scoped to a hostname listed in the certificate, commonly through its Subject Alternative Name entries.

TLS authentication happens before the HTTP request path is processed.

TLS:

Verify api.example.com.


HTTP:

GET /users

The server does not need a new certificate for every route.

It also does not need a new certificate for every request.


A certificate for:

api.example.com

does not automatically cover:

www.example.com
admin.example.com
example.com

Those are separate hostnames.

A certificate can contain multiple hostnames:

example.com
www.example.com
api.example.com

Or a wildcard certificate might contain:

*.example.com

The exact hostname presented by the client must be covered by the certificate.

Paths do not matter to certificate hostname matching.

Hostnames do.


Certificate key versus TLS session keys

Another common source of confusion is the word “key.”

TLS involves more than one kind of key material.

The certificate’s private key is relatively long-lived:

Server certificate private key:

Stored on server or TLS proxy

Associated with the certificate public key

Used to prove server identity

Reused across many TLS handshakes until rotated

The traffic-encryption keys are temporary:

TLS session keys:

Established during a TLS connection

Used for symmetric encryption

Scoped to that connection or session

Not issued by Let's Encrypt

Let’s Encrypt is not generating a new private key for each API request.

It issues a certificate for the server’s public key.

Later, each TLS connection negotiates temporary symmetric keys used to encrypt the actual traffic.

Long-lived identity:

Certificate + certificate private key


Per-connection encryption:

Temporary TLS session keys

The certificate answers:

Who is this server?

The session keys answer:

How will this particular conversation be encrypted?

The curl request lifecycle

When you run:

curl https://api.example.com/users

the rough lifecycle is:

DNS:
Resolve api.example.com to an IP address.

TCP:
Connect to the IP address on port 443.

TLS:
Receive the server certificate.
Verify the certificate chain.
Verify the requested hostname.
Verify the server's private-key proof.
Establish temporary session keys.

HTTP:
Send GET /users through the encrypted connection.

HTTP response:
Receive the response through the encrypted connection.

The certificate issuance process happened before every step in this lifecycle.

Let’s Encrypt is not an additional network hop between curl and the API.

Not this:

curl -> Let's Encrypt -> API


This:

curl -> API

Let’s Encrypt’s signature is carried inside the certificate chain presented by the API.


Seeing the separation with curl

Curl can display details about the TLS connection:

curl -v https://api.example.com/users

The output varies by operating system, curl version, and TLS backend, but it may show information such as:

Host resolution

Connection to port 443

TLS protocol negotiation

Certificate subject

Certificate issuer

Certificate validity period

Certificate verification result

HTTP request and response

Conceptually, curl is performing checks like:

I requested api.example.com.

Does the certificate cover api.example.com?

Does the certificate chain to a root I trust?

Are the certificate signatures valid?

Is the certificate currently valid?

Did the server prove possession of the matching private key?

Only after those checks succeed does the HTTP exchange proceed over the established TLS connection.


What happens when validation fails

Suppose DNS points:

api.example.com

to a server that presents a certificate for:

different.example.net

Curl can still reach the IP address.

The TCP connection can still succeed.

The server can still send a certificate.

But the TLS verification should fail because the requested hostname is not covered by the certificate.

DNS says:

Connect to this IP.


TLS says:

The server answering at this IP has not provided
a valid identity for the hostname you requested.

DNS is a pointer.

It does not prove ownership or control of the destination server.

TLS separately verifies whether the server can present an acceptable identity for the requested hostname.


What the certificate actually proves

A standard domain-validated certificate does not prove:

The company is reputable.

The application is secure.

The API is free of vulnerabilities.

The operator legally owns a brand.

The content returned by the server is truthful.

It proves something narrower:

At issuance time, the requester demonstrated
the required level of control over the hostname,
and the certificate authority signed a certificate
binding that hostname to a public key.

Later, the server proves possession of the matching private key.

A valid certificate for a hostname therefore means:

The client established a cryptographically authenticated
connection to a server presenting an identity valid
for the hostname the client requested.

It does not evaluate whether the hostname itself is deceptive or whether the application is trustworthy.


How the trust model can be changed

The client’s trust store is part of the security boundary.

Curl may use an operating-system trust store, a bundled CA file, or a configured CA path depending on the platform and build.

A client can also be explicitly configured to trust an additional certificate authority.

For example:

curl --cacert internal-ca.pem https://internal.example.com

This says:

Use this CA certificate as a trust anchor
when verifying the server.

This is common for private infrastructure with an internal certificate authority.

It also demonstrates an important principle:

TLS verification is only as trustworthy as the verifier’s trust configuration.

If someone can maliciously change the client’s trust store, replace its CA bundle, modify curl, or disable certificate verification, they can change what the client considers valid.

That does not mean the client is part of Let’s Encrypt issuance.

It means the client controls the later verification policy.


The direct-trust alternative

The public CA system is not the only possible model.

You could create your own private certificate authority and configure curl to trust it:

curl --cacert my-private-ca.pem https://api.internal.example.com

Now the trust relationship is manually configured.

Public HTTPS:

Trust any valid server certificate chaining
to one of the client's trusted public roots.


Private CA:

Trust certificates chaining to this private root
that was manually installed or supplied.

This is closer to the common SSH model because trust is being directly provisioned rather than delegated through the public web PKI.

The scaling problem is different.

With SSH, manually installing exact public keys may be reasonable for a limited number of servers and users.

For public HTTPS, every arbitrary client cannot be expected to manually install the public key for every website before its first visit.

The CA system provides a scalable set of shared trust anchors.


SSH and TLS compared

Common SSH public-key login

Client:
has private key

Server:
has client public key in authorized_keys

Identity direction:
client proves identity to server

Trust installation:
direct and manual

Private key transmitted:
no

Reusable signature:
no

Public HTTPS using Let’s Encrypt

Server:
has certificate and matching private key

Client:
has trusted CA roots

Identity direction:
server proves identity to client

Trust installation:
delegated through certificate authorities

Private key transmitted:
no

Let’s Encrypt contacted per request:
no

The two-phase comparison

SSH setup:

Install client public key on server.


SSH usage:

Client signs session-specific data.
Server verifies the signature.


TLS issuance:

Server proves domain control to Let’s Encrypt.
Let's Encrypt signs server certificate.


TLS usage:

Server presents certificate to client.
Server proves possession of certificate private key.
Client verifies the certificate and proof.

The most important diagram

The diagram can be compressed into two lines:

Certificate issuance:
Server ↔ Let's Encrypt

Certificate usage:
Client ↔ Server

The client consumes the result of issuance.

It does not participate in issuance.


The compact mental model

SSH public-key authentication:

The server already has the client's public key.

The client proves it possesses the matching private key
by signing connection-specific data.

The private key is never sent.

The signature is not a reusable password.

Let’s Encrypt issuance:

The server proves control of a hostname.

Let's Encrypt signs a certificate binding
that hostname to the server's public key.

The client is not involved.

TLS usage:

The client connects later.

The server presents the previously issued certificate.

The client verifies the certificate using
its local certificate-authority trust store.

The server proves possession of the matching private key.

The connection establishes temporary session keys
for encrypting the actual HTTP traffic.

The single most important distinction is:

Let’s Encrypt belongs to the certificate issuance phase. Curl belongs to the later certificate usage and verification phase.

The client receives no private key, no special per-request credential, and no certificate from Let’s Encrypt during issuance.

It only later receives the server’s already-issued public certificate during the TLS handshake.

That is the separation that makes the entire system easier to reason about.

Comments

No comments yet. Be the first!