mTLS

Security

Mutual TLS: a TLS handshake in which the server also asks the client for a certificate, so both ends prove their identity with an X.509 certificate instead of only the server. CDNs use it edge-to-origin, so only the CDN can fetch from the origin, and viewer-to-edge to authenticate API clients.

Also known as mutual TLS, client certificate authentication, mutual authentication.

10 min read Updated Aug 30, 2026

Full Explanation

Mutual TLS (mTLS, also called client certificate authentication) is TLS in which both ends authenticate, not only the server. The server asks the client for a certificate and verifies it, while the client still verifies the server. It is not a separate protocol, and not a layer bolted on top of TLS. It is the ordinary handshake with the server's optional client-authentication request switched on (RFC 9846, Section 4.4.2). It answers one question: who is calling. It says nothing about what that caller is allowed to do. CDNs use it on two legs. One leg is edge to origin, so knowing the origin IP is not enough to get a response. The other leg is viewer to edge, so the CDN can authenticate API clients and devices before admitting a request. That makes it a standard building block of zero-trust designs: "mTLS is often used in a Zero Trust security framework" (Cloudflare).

How it works

Ordinary TLS authenticates one side: "The server side of the channel is always authenticated; the client side is optionally authenticated" (RFC 9846, Section 1). mTLS switches on that optional half. Everything happens inside the handshake, before any application data flows. It costs one certificate and one signature: the client's Certificate and CertificateVerify messages travel in the same flight as its Finished message, so client authentication adds no round trip (RFC 9846, Section 2, Figure 1).

  • The server requests a certificate with a CertificateRequest message: "A server which is authenticating with a certificate MAY optionally request a certificate from the client" (RFC 9846, Section 4.4.2). It can name the authorities it will accept in the certificate_authorities extension (Section 4.3.4).
  • The client answers with its own Certificate message. "The client MUST send a Certificate message if and only if the server has requested certificate-based client authentication via a CertificateRequest message" (Section 4.5.1). The client follows with CertificateVerify. This message is "explicit proof that an endpoint possesses the private key corresponding to its certificate" (Section 4.5.2). A copied certificate file is useless on its own. Holding the private key is what proves identity.
  • The server checks the presented chain against the CA certificates it trusts. What happens when that fails is the server's decision, not the protocol's. If some aspect of the chain is unacceptable, for example "it was not signed by a known, trusted CA", the server "MAY at its discretion either continue the handshake (considering the client unauthenticated) or abort the handshake". It may also answer a missing certificate with a certificate_required alert (Section 4.5.1.3). Enforcement is configuration, not something TLS guarantees for you. On nginx, ssl_verify_client optional "requests the client certificate and verifies it if the certificate is present" and leaves the outcome in the $ssl_client_verify variable for the application to act on, while ssl_verify_client on rejects the connection itself (nginx docs).

The certificates are ordinary X.509 v3 certificates from the Internet PKI profile (RFC 5280, Section 1). They normally carry the extended key usage for "TLS WWW client authentication" (Section 4.2.1.12). They are private identities rather than public web certificates. No public CA will issue a certificate that asserts this connection is your CDN. So "The organization implementing mTLS acts as its own certificate authority" (Cloudflare), and the verifying side is configured with that private CA instead of the browser root store. In TLS 1.3, "All handshake messages after the ServerHello are now encrypted" (RFC 9846, Section 1.3). So unlike TLS 1.2, the client certificate is not sent in the clear for anyone on the path to read.

Why it matters for a CDN

A CDN inserts itself between the viewer and the origin. This means the origin's real address is the weakest point in the design. The edge-to-origin hop already runs over TLS. But with only server authentication, the origin has no way to tell the CDN apart from anyone else who has found its IP. As Cloudflare puts it, without authenticated pulls, even for an origin behind the CDN, "attackers with your origin's IP address will still receive a response from your origin for HTTPS requests" (Cloudflare docs). IP allowlists are the usual alternative. They break as soon as the CDN adds a network range or the origin moves.

mTLS moves the check from where a request came from to what it can prove. The origin completes the handshake only for a caller presenting a certificate that chains to a CA the origin trusts. So "This additional layer of authentication ensures that any HTTPS requests outside of Cloudflare will not receive a response from your origin" (Cloudflare docs). The same mechanism runs in the other direction on the viewer side. The edge terminates TLS anyway, so it can validate client certificates there and spare the origin the work. CloudFront "performs this certificate validation at AWS edge locations, offloading the authentication complexity from your origin servers while maintaining CloudFront's global performance benefits" (AWS docs). That is how a CDN authenticates API clients, mobile apps and IoT devices that have no login. "Use mTLS when you need to verify the identity of API clients, such as mobile applications, IoT devices, or services that connect to your API" (Cloudflare docs).

What CDNs do

Every major CDN offers mTLS on the origin leg. It is opt-in everywhere: you enable it and separately configure the origin to request and verify the certificate. The viewer leg is a distinct feature with its own configuration.

  • Cloudflare: Authenticated Origin Pulls (AOP). "With Authenticated Origin Pulls, Cloudflare performs standard TLS handshakes between a client device and Cloudflare, but a client-authenticated TLS handshake between Cloudflare and your origin" (Cloudflare docs). It comes in three independent forms: global AOP with a Cloudflare-provided certificate, and zone-level or per-hostname AOP with a certificate you upload. On the viewer side, API Shield does client mTLS. "All Cloudflare plans can set up mTLS with a Cloudflare-managed certificate authority (CA). Enterprise customers can upload up to five non-Cloudflare CAs" (Cloudflare docs).
  • AWS CloudFront: origin mTLS and viewer mTLS. "Origin mTLS enables CloudFront to authenticate itself to your origin servers using client certificates" (AWS docs). It is configured per origin. The client certificate must be "A client certificate stored in AWS Certificate Manager in the US East (N. Virginia) Region (us-east-1)" (AWS docs). Viewer mTLS is the mirror image. CloudFront validates client certificates against an account-level trust store of CA certificates. It runs in required mode (the default), optional mode, or passthrough mode. Passthrough mode forwards the certificate to the origin as HTTP headers without validating it (AWS docs). Trust stores accept AWS Private CA and third-party private CAs (AWS docs).
  • Fastly: client certificate on the origin host. Fastly's instruction is blunt: "To ensure TLS connections to your origin come from Fastly and aren't random, anonymous requests, set your origin to verify the client using a client certificate." You paste the certificate and private key in PEM form into the host configuration. Then "configure your backend to require client certificates and verify them against the CA cert they were signed with". Fastly "also supports securing your connection to origin with mutual TLS (mTLS)" (Fastly docs).

Watch out for

  • Turning it on at the CDN enforces nothing. The origin has to ask for the certificate. "CloudFront will not provide the client certificate if the server does not request it, allowing the connection to proceed normally" (AWS docs). TLS itself lets a server that dislikes the chain carry on with the client simply treated as unauthenticated (RFC 9846, Section 4.5.1.3). A half-finished rollout looks healthy and protects nothing.
  • Certificate lifecycle is the real cost, and expiry is an outage. An expired or revoked client certificate does not degrade the origin leg: it stops it. Cloudflare offers an optional alert for zone-level AOP. Once you configure it, "AOP certificate expiration notifications are sent 30 days and 14 days before the certificate expiry" (Cloudflare docs). Nothing warns you by default.
  • The CDN presents the certificate; your origin has to judge it. "CloudFront does not perform validation of the client certificate's validity or revocation status—this is the responsibility of your origin server". The origin "must be configured to validate the client certificate against its trust store, check certificate expiration, and perform revocation checks (such as CRL or OCSP validation)" (AWS docs). Trusting a CA without checking revocation means a leaked client key stays valid until it expires.
  • A vendor-shared certificate proves less than you think. The certificate used by Cloudflare's global AOP "is not exclusive to your account. It only guarantees that a request is coming from the Cloudflare network" (Cloudflare docs). So any other Cloudflare customer's traffic can satisfy it. "Unlike global AOP, which uses a Cloudflare-provided certificate shared across all accounts, zone-level AOP uses your own certificate for stricter security" (Cloudflare docs).
  • mTLS authenticates; it does not authorize. A valid certificate says the caller holds a trusted key, not that the request is safe. An attacker who obtains a trusted key, or who controls one legitimate client, can send whatever it likes. Keep request inspection, such as a WAF, and per-route authorization in place.
  • Your private CA is the single point of failure. Anyone able to sign a certificate that chains to the CA your origin trusts passes the check. Protect the CA key, scope the trust store to exactly the CAs you need, and be able to re-issue.
  • It does not compose with everything. CloudFront origin mTLS "is only available on Business, Premium plans or Pay as you go pricing plans", not the Free plan. It is unsupported with gRPC traffic, WebSocket connections, VPC origins, Lambda@Edge origin-request and origin-response triggers, and embedded POPs (AWS docs). On the viewer side, passthrough mode hands validation to the origin. But "No trust store is required and no caching occurs" (AWS docs). That is an mTLS choice that silently turns off the cache.

Best practice

  • Use a certificate bound to your own zone or account, not one a vendor shares across customers: zone-level or per-hostname AOP on Cloudflare, your own ACM certificate on CloudFront.
  • Roll it out in the order the vendors document. Verify first and enforce second. First, install the CA on the origin. Then set verification to permissive: nginx ssl_verify_client optional, or Apache SSLVerifyClient optional, where "the client may present a valid Certificate" (Apache docs). Next, confirm from origin logs that the CDN's certificate really arrives. Then switch to enforcing: ssl_verify_client on, or SSLVerifyClient require, where "the client has to present a valid Certificate". Only after that, "use curl to send requests directly to your origin IPs, verifying that the requests fail due to certificate validation being enforced" (Cloudflare docs). Enforcing before verifying takes the origin offline. Verifying without enforcing leaves it open.
  • Make the origin validate the whole certificate, not just the chain: expiry and revocation (CRL or OCSP) too, because the CDN does not do it for you.
  • Keep client certificate lifetimes short, rotate on a schedule you have actually rehearsed, and configure expiry alerts so a renewal miss is a notification rather than an outage.
  • Treat mTLS as one layer. Keep the origin unreachable except through the CDN, and keep the controls that judge the request rather than the caller.

Examples

This Nginx origin requires mTLS from the CDN:

server {
    listen 443 ssl;
    server_name origin.example.com;

    ssl_certificate     /etc/ssl/server.crt;
    ssl_certificate_key /etc/ssl/server.key;

    # Require client certificate
    ssl_client_certificate /etc/ssl/cloudflare-ca.pem;
    ssl_verify_client on;

    # Reject if no valid client cert
    if ($ssl_client_verify != SUCCESS) {
        return 403;
    }
}

Test mTLS with curl:

# Connect with client certificate
curl --cert client.crt --key client.key \
     --cacert ca.crt \
     https://origin.example.com/health

# Without client cert: connection refused
curl https://origin.example.com/health
# curl: (56) SSL peer rejected your certificate

Frequently Asked Questions

Mutual TLS: a TLS handshake in which the server also asks the client for a certificate, so both ends prove their identity with an X.509 certificate instead of only the server. CDNs use it edge-to-origin, so only the CDN can fetch from the origin, and viewer-to-edge to authenticate API clients.

This Nginx origin requires mTLS from the CDN:

server {
    listen 443 ssl;
    server_name origin.example.com;

    ssl_certificate     /etc/ssl/server.crt;
    ssl_certificate_key /etc/ssl/server.key;

    # Require client certificate
    ssl_client_certificate /etc/ssl/cloudflare-ca.pem;
    ssl_verify_client on;

    # Reject if no valid client cert
    if ($ssl_client_verify != SUCCESS) {
        return 403;
    }
}

Test mTLS with curl:

# Connect with client certificate
curl --cert client.crt --key client.key \
     --cacert ca.crt \
     https://origin.example.com/health

# Without client cert: connection refused
curl https://origin.example.com/health
# curl: (56) SSL peer rejected your certificate

Yes. mTLS is also known as mutual TLS, client certificate authentication, mutual authentication. Mutual TLS: a TLS handshake in which the server also asks the client for a certificate, so both ends prove their identity with an X.509 certificate instead of only the server. CDNs use it edge-to-origin, so only the CDN can fetch from the origin, and viewer-to-edge to authenticate API clients.