Securing services with stunnel

Stunnel is a tool that allows you to seamlessly add TLS to most existing services. Stunnel listens on a port, and can either receive encrypted traffic and pass it to an unencrypted destination, or it can receive unencrypted traffic and forward that to an encrypted destination.

TLS is commonly used to protect clients from connecting to untrusted servers by verifying the certificate provided by the server. Conversely, servers can prevent unwanted clients from connecting by demanding they provide a valid cert. This means that a stunnel can add a strong layer of access control

Table of contents

  • Providing HTTPS to an existing web server
  • Adding certificate authentication
  • Stunnel as a TLS client
  • Stunnel client using pinned certificates
  • Mutual authentication example

Provide HTTPS to an existing server


sslVersion    = TLSv1.2
setuid        = nobody
setgid        = nobody

client        = no
accept        = 443
connect       =
cert          = server.pem
key           = server.key

To listen on port 443, stunnel needs to be launched as root which isn't recommended.

Adding certificate authentication

Stunnel can be used for access control by requiring clients present a certificate issued by a trusted authority in order to connect. The trusted CA is created and managed with easyrsa (see earlier tutorial

This is useful for adding an extra layer of security to things like management interfaces on a router or for providing a secure proxy to reach internal hosts from the outside


client        = no
accept        = 4443
connect       =
cert          = server.pem
key           = server.key
requireCert   = yes
CAFile        = /etc/stunnel/clientCA.pem
verifyChain   = yes 

Setting up CA

It's recommended but not mandatory you do this on a different machine

git clone 
cd easy-rsa/easyrsa3
# Create PKI
./easyrsa init-pki && ./easyrsa build-ca
# Transfer ca.crt to stunnel server
scp ca.crt root@stunnelServer:/etc/stunnel/clientCA.pem
# Issue client cert and browser bundle
./easyrsa build-client-full testClient
./easyrsa export-p12 testClient

Now import your P12 file (PKCS12 bundle) into your web browser and access your stunnel server. You should be prompted for your certificate.

To be doubly sure it actually requires a cert, try accessing over curl

Stunnel as a TLS client

Stunnel can acts as a TLS client too. This mean that stunnel listens for plain connections and forwards them to a TLS secured server.

In this example, stunnel will establish a secure connection to a DNS-over-TLS server. Stunnel will listen on l


client        = yes
accept        = localhost:53
connect       =
verifyChain   = yes
CAfile        = ca-bundle.crt
checkHost     =


options use-vc

Stunnel as a client with pinned certificates

Normally we verify the identity of our peer by checking it has a certificate with a matching common name and that certificate issuer is trusted.

Let's forget about CAs and simply check the remote certificate matches our local copy of that certificate.


client          = yes
accept          =
connect         =
verifyPeer      = yes
CAfile          =

Downloading certificates from servers

getServerCert() {
echo "" | openssl s_client -connect $1:443 -servername $1 | openssl x509 -out $1.crt

Mutual authentication example

We've seen how stunnel server can require clients (such as web browsers) to provide a certificate in-order to connect.

Now we'll see how to configure the stunnel client to provide a certificate; simply use the cert= option to provide a PEM containing the crt+key.

First, some explanation of the below example

  • We want to provide a secure channel for connecting to
  • The stunnel server listens on and forwards connections to, providing the client authenticates
  • The 'authenticatedclient' connects to the above, offering its certificate to the server.
  • Once the TLS session is established, the client will provide a proxy that listens on localhost:8080 providing access to
client          = no
accept          =
connect         =
cert            = server.crt
key             = server.key
requireCert     = yes
CAfile          = clientTrust.crt
verifyChain     = yes

client          = yes
accept          = localhost:8080
connect         =
verifyPeer      = yes
CAfile          = serverCert.crt
cert            = client.crt
key             = client.key