From charlesreid1

Stunnel Client

Running an stunnel client requires installing stunnel and setting up a configuration file just like if you were setting up an Stunnel/Server, except swapping the accept and connect ports, since we want the client to accept local traffic (e.g., on port 8443) and send it on to the server that it connects to with SSL (e.g., on port 443).


The stunnel server will be listening on port 443. We want to connect to the external server on 443, and forward the traffic to a local port 8443.

Stunnel Config File

If we want to establish a connection on port 443 (externally) to forward on to port 8443 (locally), we can use the following config file:

output  = /var/log/stunnel4/stunnel.log
cert    = /usr/local/etc/stunnel/stunnel.pem
key     = /usr/local/etc/stunnel/stunnel.pem
pid     = /var/run/stunnel4/
client  = yes
accept  =
connect =

A couple of things to note:

  • Server certificate must be the same certificate as is on the stunnel server.
  • /var/log/stunnel4 and /var/run/stunnel4 must both exist.
$ sudo mkdir -p /var/{run,log}/stunnel4

Why does the certificate on the client need to be the same one as is on the server? That's how you verify the identity of the server - if you can receive their public key over a trusted, published, public channel, then you can exchange encrypted communications with them. Use a trusted, encrypted channel like SSH or a USB key if you have physical access to the server.

If you can't establish a trusted connection with the server, there's no way to verify its identity.

On Linux you can put your certificates and your config file in /etc/stunnel.

For Homebrew users it is /usr/local/etc/homebrew.

Starting Stunnel Client

To start stunnel, you'll run the stunnel binary. This will attempt to silently establish a connection in the background to the specified stunnel server.

Stunnel may fail with an error message. For example, the following is an example of a failure due to a nonexistent log directory:

$ sudo stunnel
[ ] Clients allowed=2375
[ ] Cron thread initialized
[ ] errno: (*__error())
[.] Reading configuration from file /usr/local/etc/stunnel/stunnel.conf
[.] UTF-8 byte order mark not detected
[ ] Compression disabled
[ ] Snagged 64 random bytes from /Volumes/noospace/Users/charles/.rnd
[ ] Wrote 1024 new random bytes to /Volumes/noospace/Users/charles/.rnd
[ ] PRNG seeded successfully
[ ] Initializing service [https]
[ ] Loading certificate from file: /usr/local/etc/stunnel/stunnel.fullchain.pem
[ ] Certificate loaded from file: /usr/local/etc/stunnel/stunnel.fullchain.pem
[ ] Loading private key from file: /usr/local/etc/stunnel/stunnel.key.pem
[ ] Private key loaded from file: /usr/local/etc/stunnel/stunnel.key.pem
[ ] Private key check succeeded
[:] Service [https] needs authentication to prevent MITM attacks
[ ] SSL options: 0x03000004 (+0x03000000, -0x00000000)
[.] Configuration successful
[ ] Listening file descriptor created (FD=6)
[ ] Service [https] (FD=6) bound to
[!] Cannot open log file: /var/log/stunnel4/stunnel.log
[ ] Closing service [https]
[ ] Service [https] closed (FD=6)
[ ] Service [https] closed

This error in particular can be fixed with mkdir /var/{log,run}/stunnel4.

Stunnel may also fail silently, only printing an error message into the stunnel log file. For example, the following log file shows an error due to a nonexistent /var/run directory, which did not result in a printed error message from stunnel:

2017.03.28 12:47:41 LOG5[ui]: Threading:PTHREAD Sockets:POLL,IPv6 TLS:ENGINE,OCSP
2017.03.28 12:47:41 LOG5[ui]: Reading configuration from file /usr/local/etc/stunnel/stunnel.conf
2017.03.28 12:47:41 LOG5[ui]: UTF-8 byte order mark not detected
2017.03.28 12:47:41 LOG4[ui]: Service [https] needs authentication to prevent MITM attacks
2017.03.28 12:47:41 LOG5[ui]: Configuration successful
2017.03.28 12:47:41 LOG3[main]: Cannot create pid file /var/run/stunnel4/
2017.03.28 12:47:41 LOG3[main]: create: No such file or directory (2)

Testing Stunnel Client

To test out the stunnel client, first run stunnel on the server:

[remote] $ stunnel

run a Python web server on the remote machine on port 8443:

[remote] $ echo "<h2>Hallo wkerld!</h2>" > index.html
[remote] $ python -m SimpleHTTPServer 8443

Now on the client machine, run stunnel:

[client] $ stunnel

now you should be able to access the web server on localhost:8443, without having to specify the address of the server.