TLS is good, arguably necessary, but managing PKI makes me feel bad. Is there no other way? This article will show how you can reap the benefits of mutual TLS quickly and easily without the mire of PKI.
People Don’t Like TLS
TLS is the S in HTTPS. It’s the thing that lets us (with a relatively straight face) communicate securely over networks, like the Internet or your lightly-segmented and crowded internal network. It’s a transport layer protocol that provides both confidentiality (an interloper can’t read the content of webpages you’re interacting with) and authenticity (you’re definitely logging in to the real Gmail Dot Com, we promise) for higher layer protocols like HTTP.
As a general rule (Yes, SSH and DNS exist too), if you’re transmitting information over a network, you should be using TLS. This practically means ensuring your websites and API endpoints all use HTTPS and configuring additional controls such as HSTS to prevent inadvertent connections using insecure protocols.
And yet, TLS is still regularly not deployed inside networks, between components of an application or environment. Why is TLS seen as unpleasant to deploy and maintain? Two reasons:
TLS Itself
TLS involves complex and unintuitive concepts. It’s bursting with fiddly configuration parameters. A small saving grace: You can largely learn it once and copy / paste your way to success.
Pick a secure cipher and configure that one specifically. Pick something modern with a cool name like ChaCha20-Poly1305
. Make sure you’re using the newest version of the TLS protocol you can.
Public Key Infrastructure (or PKI)
PKIs are serious business. PKIs are precarious. PKIs require perfect usage to be safe. They’re a single point of failure, a single point of compromise, you must wear the ceremonial robes, maybe someone made you buy an HSM, who are your security officers? Don’t forget the auditing, funny hats, intermediate CAs, passwords you only use twice a year but nonetheless mustn’t ever forget, unexpected expiries, completely non-functional revocation, CA cert bootstrapping across the entire organisation, the available tooling being things like easy-rsa and TinyCA and they’re not exactly friendly…

Figure 1: This is your brain on PKI.
I get it. Nobody wants to manage a PKI. As soon as you think too much about any one little part of the requirements it gets out of hand. Managing a PKI is without a doubt the worst part of TLS.
So, could we TLS … without PKI? We can use cryptography magic to let each endpoint prove its identity to the other with a shared secret, right? Diffie–Hellman key exchange authenticated with a pre-shared key?

Figure 2: Using openssl to make a fresh key. Don't use this one, specifically.
TLS-PSK is here to save the day! Generate a shared secret with sufficient entropy, copy and paste it out of band to both endpoints, and your job is done. And TLS-PSK was first standardised in 2005 - twenty years ago at the time of writing.
I guess we should double check: What software actually supports TLS-PSK?
OK. stunnel 5.09 - that’s from 2015, so all supported Debian releases you could be currently running.
And uhh… that’s about it. NGINX talked about it in 2017 but never shipped it.
Oh.
Observation: Certificate Authorities aren’t special
What is a Certificate Authority exactly? More or less, it’s a self-signed certificate and key pair with a bit flipped in the certificate that says it’s allowed to be used as a CA. That’s it.

Figure 3: This bit controls whether a ceritifcate can be used as a CA or not.
Observation: Making a new Certificate Authority is basically free
Making a self-signed certificate is easy, every Linux installation for decades has included either the OpenSSL or GnuTLS tooling to do so. If it’s also therefore trivial and free to make a new certificate authority, when we’re issuing our machine-to-machine certificates for TLS why don’t we…
- Make a new CA
- Issue two certificates exactly (one for the reverse proxy, one for the application) with 100-year expiries
- Delete the CA’s private key
Where does this get us?
- No more single point of compromise – you can be pretty sure nobody is going to be fraudulently issuing new certificates when the CA private key has been deleted.
- No more single point of failure – there’s no CA to forget the password for or lose in a drive crash.
- You can pin the fingerprints or serial numbers of each certificate to ensure that e.g. a load balancer will only ever talk to a backend and vice versa. A compromised backend can’t connect to another backend and impersonate the load balancer.
- You never need to revoke a certificate issued this way. Need to replace it? Just replace it! Revoking it was barely going to work anyway.
- The certificates made this way are not going to expire unexpectedly in your lifetime. Replace them whenever you feel like it by just updating both ends.
We’ve arrived where we wanted to be without needing TLS-PSK support: Mutually authenticated point-to-point TLS without the ceremonial robes and horrifying management overhead.

Pre-Empting Your Complaints
But I can’t revoke the certificate anymore!
Revocation doesn’t work very well in TLS already - this is why ACME certs from Let’s Encrypt have a 90-day lifetime and we had to invent OSCP stapling and certificate fingerprint blocklists. Were you really just going to revoke the certificate and not rebuild the affected infrastructure where the key was stolen from in the first place?
I’ll lose track of the issued certificates!
Store the certificate files in a predictable place, get your host configuration management database to index them. Also, if you need to do something with the certificates, you know where they are, right? They’re at both ends of the connection you’re securing.
I don’t have a secure out-of-band channel for provisioning the keys!
Technically with a small amount of additional effort you can generate the keys on-device, ship and sign a CSR, then distribute back the certificates. But how did you SSH into the servers exactly?
I have some other requirement that your proposal doesn’t address!
If you really need the features of a CA and PKI, you can still use one!
Ok But How?
I’ve got you. I made a script for this purpose ages ago, and you can have it:
https://git.sr.ht/~fincham/proxy-ca
Bonus Round: Finding your TLS configuration flaws for free
Really, the problem isn’t TLS configuration, the problem is you had to pay a company like Pulse Security consulting rates to tell you about TLS configuration in the first place. You can get most of the way there with good free tools like TestSSL:
Bonus Round: Configuring it in Apache
Here’s an example how you could configure an Apache backend in this scenario:
SSLEngine on
SSLProtocol TLSv1.2
SSLCipherSuite ECDHE-RSA-AES256-GCM-SHA384
SSLHonorCipherOrder on
SSLCompression off
SSLCertificateFile /srv/tls/foo-app-cert.pem
SSLCertificateKeyFile /srv/tls/foo-app-key.pem
SSLCACertificateFile /srv/tls/foo-ca-cert.pem
SSLVerifyClient require
SSLVerifyDepth 1
# The proxy will always have serial 0x1000, this prevents
# other backends from connecting using their own certs.
<If "%{SSL_CLIENT_M_SERIAL} != 1000">
Require all denied
</If>