In a multi-tier CDN architecture, traffic flows through two layers: the edge CDN that faces customers, and a regional CDN that sits closer to the origin. The regional layer handles heavier caching, origin offload, and aggregation across multiple edge nodes. It is an internal infrastructure component, customers should never reach it directly.
The problem is that the regional CDN listens on a network port. If a customer discovers its address (through a DNS leak, a misconfigured record, or simple network scanning), they can bypass the edge entirely and send raw traffic straight to it. This sidesteps rate limiting, authentication, and any access controls applied at the edge.
Mutual TLS (mTLS) closes this gap at the TLS handshake level. The regional CDN requires every connecting peer to present a valid certificate signed by a CA it trusts. The edge CDN has that certificate. Customers do not. A direct connection from a customer fails before a single HTTP byte is exchanged, not because of a firewall rule or an IP allowlist that can be routed around, but because the customer cannot produce the correct cryptographic credential.
This tutorial walks through the full setup: building the certificate authority, issuing certificates to both CDN tiers, configuring the regional CDN (ATS) to require client certificates, and configuring the edge CDN (ATS) to present one when connecting upstream.
Architecture Overview
Before touching a single command, understand what you are building:
[Customer] ──HTTPS──→ [Edge CDN - ATS]
│
mTLS (client cert)
│
↓
[Regional CDN - ATS]
│
HTTP
│
↓
[Origin]
- Edge CDN: terminates HTTPS for customers, caches responses, and forwards cache misses to the regional CDN. It presents a client certificate in every upstream request.
- Regional CDN: accepts connections only from peers that hold a certificate signed by the internal CA. It terminates TLS from the edge, caches responses, and fetches from the origin on miss.
- Customer: can reach the edge CDN. Cannot establish a TLS connection to the regional CDN because they have no valid client certificate.
Both ATS instances run on Ubuntu. You will configure the regional CDN first (server side), then the edge CDN (client side).
Prerequisites
You need:
- Two Ubuntu 22.04 LTS servers (or later), both with Apache Traffic Server installed
regional-cdnserver: the internal upstream tieredge-cdnserver: the customer-facing tier
- ATS installed on both. If not done yet, follow Install and Configure Apache Traffic Server as a Caching Reverse Proxy on Ubuntu
- TLS termination already configured on both nodes. For a refresher, see Configure SSL/TLS Termination in Apache Traffic Server
opensslavailable on both servers (ships with Ubuntu by default)sudoaccess on both servers- A hostname or IP for the regional CDN reachable from the edge CDN, in this tutorial we use
regional-cdn.internal.example.com
Step 1: Create the Internal Certificate Authority
The CA is the root of trust. You will sign both the regional CDN’s server certificate and the edge CDN’s client certificate from it. Only certificates signed by this CA will be accepted by the regional CDN.
Run these commands on a secure admin machine or on the regional CDN server. Never put the CA private key on the edge CDN or any customer-facing node.
Create a directory to hold the PKI material:
sudo mkdir -p /etc/trafficserver/ssl/pki
cd /etc/trafficserver/ssl/pki
Generate the CA private key:
sudo openssl genrsa -out internal-ca.key 4096
Generate the self-signed CA certificate:
sudo openssl req -new -x509 -days 3650 \
-key internal-ca.key \
-out internal-ca.pem \
-subj "/C=US/O=MyCompany/OU=CDN-Infrastructure/CN=CDN-Internal-CA"
Lock down the CA key immediately, it should only be readable by root:
sudo chmod 400 /etc/trafficserver/ssl/pki/internal-ca.key
Step 2: Generate the Regional CDN Server Certificate
The regional CDN presents this certificate to the edge CDN during the TLS handshake. The edge CDN verifies it against the CA.
Still in the PKI directory on your regional CDN server:
sudo openssl genrsa -out regional-server.key 2048
Create the Certificate Signing Request:
sudo openssl req -new \
-key regional-server.key \
-out regional-server.csr \
-subj "/C=US/O=MyCompany/OU=CDN-Infrastructure/CN=regional-cdn.internal.example.com"
Sign it with the internal CA:
sudo openssl x509 -req -days 825 \
-in regional-server.csr \
-CA internal-ca.pem \
-CAkey internal-ca.key \
-CAcreateserial \
-out regional-server.pem
Verify the certificate was signed correctly:
openssl verify -CAfile internal-ca.pem regional-server.pem
Expected output: regional-server.pem: OK
Set correct permissions:
sudo chmod 640 regional-server.pem
sudo chmod 600 regional-server.key
Step 3: Generate the Edge CDN Client Certificate
This is the certificate the edge CDN presents to the regional CDN to prove its identity. Generate this on the edge CDN server, but sign it using the CA on the regional server (or your admin machine).
On the edge CDN server, generate the key and CSR:
sudo mkdir -p /etc/trafficserver/ssl
sudo openssl genrsa -out /etc/trafficserver/ssl/edge-client.key 2048
sudo openssl req -new \
-key /etc/trafficserver/ssl/edge-client.key \
-out /tmp/edge-client.csr \
-subj "/C=US/O=MyCompany/OU=CDN-Infrastructure/CN=edge-cdn-node1"
Use a CN that identifies the edge node. If you have multiple edge nodes, each gets its own certificate so you can revoke individual nodes without affecting others.
Copy the CSR to your CA machine (regional CDN or admin server) and sign it:
# On the CA/regional machine, replace the path with where you received the CSR
sudo openssl x509 -req -days 365 \
-in /tmp/edge-client.csr \
-CA /etc/trafficserver/ssl/pki/internal-ca.pem \
-CAkey /etc/trafficserver/ssl/pki/internal-ca.key \
-CAcreateserial \
-out /tmp/edge-client.pem
Client certificates should have shorter lifetimes than the CA. 365 days is reasonable for infrastructure certificates managed with automation. For higher security environments, use 90 days and automate rotation.
Copy the signed certificate back to the edge CDN server:
# From the CA machine, securely transfer the signed certificate
scp /tmp/edge-client.pem user@edge-cdn:/etc/trafficserver/ssl/edge-client.pem
Also copy the CA certificate to the edge CDN so it can verify the regional CDN’s server certificate:
scp /etc/trafficserver/ssl/pki/internal-ca.pem user@edge-cdn:/etc/trafficserver/ssl/internal-ca.pem
On the edge CDN, set permissions:
sudo chmod 640 /etc/trafficserver/ssl/edge-client.pem
sudo chmod 600 /etc/trafficserver/ssl/edge-client.key
Step 4: Configure the Regional CDN to Require Client Certificates
This is the enforcement point. The regional CDN will reject any TLS connection that does not include a valid client certificate signed by the internal CA.
On the regional CDN server:
Register the server certificate
Open ssl_multicert.config:
sudo nano /etc/trafficserver/ssl_multicert.config
Add or update the entry to include the CA certificate for client verification:
dest_ip=* ssl_cert_name=/etc/trafficserver/ssl/pki/regional-server.pem ssl_key_name=/etc/trafficserver/ssl/pki/regional-server.key ssl_ca_name=/etc/trafficserver/ssl/pki/internal-ca.pem
The ssl_ca_name field tells ATS which CA to use when verifying the client’s certificate. Only clients presenting a certificate signed by internal-ca.pem will pass.
Enable client certificate enforcement
Open records.config:
sudo nano /etc/trafficserver/records.config
Add these lines:
CONFIG proxy.config.ssl.client.certification_level INT 2
CONFIG proxy.config.ssl.CA.cert.filename STRING internal-ca.pem
CONFIG proxy.config.ssl.CA.cert.path STRING /etc/trafficserver/ssl/pki
CONFIG proxy.config.http.server_ports STRING 443:ssl
CONFIG proxy.config.ssl.server.cert.path STRING /etc/trafficserver/ssl/pki
CONFIG proxy.config.ssl.server.private_key.path STRING /etc/trafficserver/ssl/pki
The critical setting is proxy.config.ssl.client.certification_level INT 2:
0= no client certificate required1= request client certificate but allow connection without one2= require a valid client certificate; reject connections that cannot provide one
Restart ATS, TLS configuration is loaded at startup, a config reload is not enough:
sudo systemctl restart trafficserver
Verify it is running and listening:
sudo systemctl status trafficserver
sudo ss -tlnp | grep 443
Step 5: Configure the Edge CDN to Present a Client Certificate
The edge CDN needs to know that when it connects to the regional CDN as an upstream origin, it must present the client certificate you issued in Step 3.
On the edge CDN server:
ATS uses ssl_server_name.config (also called sni.yaml in ATS 9.2+) to configure per-destination TLS settings. This lets you specify different client certificates for different upstream hosts.
First check your ATS version:
traffic_server --version
Use ssl_server_name.config for ATS 9.0 and 9.1
sudo nano /etc/trafficserver/ssl_server_name.config
Add an entry for the regional CDN hostname:
fqdn=regional-cdn.internal.example.com client_cert=/etc/trafficserver/ssl/edge-client.pem client_key=/etc/trafficserver/ssl/edge-client.key ca=/etc/trafficserver/ssl/internal-ca.pem
Use sni.yaml for ATS 9.2 and later
sudo nano /etc/trafficserver/sni.yaml
Add:
sni:
- fqdn: "regional-cdn.internal.example.com"
client_cert: "/etc/trafficserver/ssl/edge-client.pem"
client_key: "/etc/trafficserver/ssl/edge-client.key"
ca: "/etc/trafficserver/ssl/internal-ca.pem"
What each field does:
fqdn: matches the upstream hostname in your remap rulesclient_cert: the certificate the edge CDN presents to the regional CDNclient_key: the private key for that certificateca: the CA certificate used to verify the regional CDN’s server certificate
Update remap.config to point to the regional CDN
Open the remap file:
sudo nano /etc/trafficserver/remap.config
Your remap rules should forward cache misses to the regional CDN over HTTPS:
map https://www.example.com/ https://regional-cdn.internal.example.com/
map http://www.example.com/ https://www.example.com/
Using https:// in the backend URL is what triggers ATS to initiate a TLS connection to the regional CDN, which is when it will use the client certificate you configured.
Also update records.config on the edge CDN to enable SSL for outgoing connections:
sudo nano /etc/trafficserver/records.config
CONFIG proxy.config.ssl.client.verify.server.policy STRING ENFORCED
CONFIG proxy.config.ssl.client.verify.server.properties STRING ALL
These settings tell ATS to strictly verify the regional CDN’s server certificate when initiating upstream TLS connections. ENFORCED means ATS refuses to connect if verification fails.
Reload the configuration:
sudo traffic_ctl config reload
Step 6: Test the Setup
Verify the edge CDN can reach the regional CDN
From the edge CDN server, test that the mTLS handshake works with the client certificate:
curl -v \
--cert /etc/trafficserver/ssl/edge-client.pem \
--key /etc/trafficserver/ssl/edge-client.key \
--cacert /etc/trafficserver/ssl/internal-ca.pem \
https://regional-cdn.internal.example.com/
You should receive a normal HTTP response. In the verbose output, look for:
* TLSv1.3 (OUT), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
Both sides present certificates, that is the mTLS handshake completing.
Verify that direct customer access is blocked
From any machine outside the CDN infrastructure (or test with --cert omitted), try connecting to the regional CDN without a client certificate:
curl -v \
--cacert /etc/trafficserver/ssl/internal-ca.pem \
https://regional-cdn.internal.example.com/
Expected result, a TLS handshake failure:
curl: (35) OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to regional-cdn.internal.example.com:443
The connection is dropped during the TLS handshake. No HTTP response is returned. The regional CDN never sees the request.
Test end-to-end through the edge CDN
From a customer machine, send a request through the edge CDN:
curl -I https://www.example.com/
This should return a normal 200 OK. The edge CDN handles the customer’s HTTPS connection, presents its client certificate to the regional CDN transparently, receives the response, caches it, and returns it to the customer.
Common Mistakes and Troubleshooting
Regional CDN still accepts connections without a client certificate
Confirm proxy.config.ssl.client.certification_level is 2, not 1. Then verify the service was fully restarted with systemctl restart trafficserver. A config reload (traffic_ctl config reload) does not re-read TLS settings, a restart is required.
Edge CDN cannot connect to regional CDN: “certificate verify failed”
The edge CDN is trying to verify the regional CDN’s server certificate but does not trust the CA. Make sure internal-ca.pem was copied to the edge server and is referenced in the SNI configuration under ca:. Check the path is correct and the file is readable.
Edge CDN connects but regional CDN rejects it: “no client certificate”
The edge CDN is not sending the client certificate. Verify that the SNI configuration file (ssl_server_name.config or sni.yaml) has an entry with the exact FQDN matching the upstream host in your remap rules. The hostname must match exactly regional-cdn.internal.example.com in remap and regional-cdn.internal.example.com in the SNI config.
Reload after confirming: sudo traffic_ctl config reload.
ATS error log shows certificate path errors
Check the ATS error log for specifics:
sudo tail -50 /var/log/trafficserver/error.log
Common messages:
SSL_ERROR_RX_RECORD_TOO_LONG, ATS is connecting to a port that is not speaking TLScertificate verify failed, CA mismatch or certificate not yet validno shared cipher, the TLS cipher suites on both ends are incompatible
Customer traffic is reaching the regional CDN despite mTLS
If customers are reaching the regional CDN without certificates, check that there is no additional listener on a non-TLS port (port 80 or an admin port) accessible from outside. mTLS only applies to connections that go through the TLS handshake. Lock down all ports on the regional CDN:
sudo ufw default deny incoming
sudo ufw allow from <edge-cdn-ip> to any port 443
sudo ufw enable
mTLS and firewall rules complement each other, the firewall blocks IP-level access, mTLS provides cryptographic enforcement above it.
Best Practices
Use mTLS alongside network controls, not instead of them. mTLS is a strong cryptographic control, but defense in depth means layering it with firewall rules that restrict which IP addresses can reach the regional CDN at all. If an attacker cannot reach the port, they cannot even attempt the handshake.
Issue one client certificate per edge node. If you have five edge CDN nodes, each gets a unique certificate with a descriptive CN like edge-cdn-us-east-1. If one node is compromised, you revoke only that certificate without disrupting the other four nodes. A single shared certificate for all edge nodes means a compromise forces you to rotate everywhere simultaneously.
Keep client certificate lifetimes short and automate rotation. A 90-day client certificate is far safer than a 365-day one. Pair short lifetimes with automated rotation using tools like HashiCorp Vault’s PKI secrets engine can issue and renew certificates programmatically without manual steps.
Store the CA private key offline. The internal-ca.key file is the most sensitive piece in this entire setup. Anyone who holds it can issue certificates that the regional CDN will trust. After signing your initial certificates, move the CA key to an offline storage device or a hardware security module (HSM). The regional CDN only needs internal-ca.pem (the public certificate) at runtime.
Log and monitor TLS handshake failures on the regional CDN. Every failed handshake is a data point — it might be a misconfigured edge node, a certificate about to expire, or someone actively probing your regional tier. Enable detailed TLS logging in ATS:
CONFIG proxy.config.ssl.handshake_timeout_in INT 30
And monitor the ATS error log for repeated SSL_ERROR entries:
sudo tail -f /var/log/trafficserver/error.log | grep SSL_ERROR
Plan for certificate expiry. When the edge CDN’s client certificate expires, its connections to the regional CDN will fail immediately. The regional CDN enforces certificate validity during the handshake, and an expired certificate is a failed certificate. Set calendar reminders or monitoring alerts 30 days before any certificate expires. The command to check expiry is:
openssl x509 -enddate -noout -in /etc/trafficserver/ssl/edge-client.pem
Conclusion
You have built a two-tier CDN setup where the regional CDN enforces mutual TLS on every incoming connection. The edge CDN presents a signed client certificate when connecting upstream, and the regional CDN rejects any peer that cannot produce one. Customers who discover the regional CDN’s address and attempt a direct connection get a TLS handshake failure, there is nothing to respond to and no HTTP request is ever processed.
This pattern is not limited to CDN tiers. The same approach applies anywhere you want cryptographic enforcement of which services can talk to each other: internal APIs that should only be called by specific microservices, database proxies that only accept connections from authorized application servers, or administrative endpoints that only accept connections from your ops tooling.
Logical next steps from here:
- Automate certificate issuance with HashiCorp Vault PKI. Vault can act as your internal CA, issue short-lived certificates on demand, and integrate directly with your deployment pipelines. This eliminates the manual CSR-and-sign workflow shown above
- Add certificate revocation. if an edge node is decommissioned or compromised before its certificate expires, you need to revoke the certificate. ATS supports Certificate Revocation Lists (CRLs) via
proxy.config.ssl.server.cert_chain.filename; pair this with Vault’s CRL endpoint for a complete lifecycle management solution - Monitor the hit rate on the regional CDN. Once mTLS is in place and direct access is blocked, watch
proxy.process.http.cache_hit_freshon the regional CDN to confirm the edge nodes are actually caching and that origin load is dropping as expected