Skip to content

Last updated: 2026-06-25

stunnel TLS/SSL Configuration Guide

This guide provides recommended TLS/SSL settings for stunnel, a generic TLS wrapper that adds encryption to network services that do not natively support it. stunnel uses OpenSSL and can run in two directions: as a server it terminates TLS in front of a plaintext backend, and as a client it wraps a plaintext outbound connection in TLS. Configuration lives in a single file (/etc/stunnel/stunnel.conf) made up of global options followed by one or more [service] sections.

Prerequisites

  • stunnel 5.x (5.78 or later recommended)
  • OpenSSL 1.1.1 or later (required for the ciphersuites directive and TLS 1.3)
  • A valid TLS certificate and private key

Verify the installed version and that it is linked against a current OpenSSL:

stunnel -version

The sslVersionMin and sslVersionMax directives require stunnel built against OpenSSL 1.1.0 or later, and ciphersuites requires OpenSSL 1.1.1 or later.

Server Mode (TLS Termination)

In server mode stunnel listens for TLS connections and forwards the decrypted plaintext to a local backend service. The example below terminates TLS on port 8443 and connects to a plaintext backend on 127.0.0.1:8080.

[https-frontend]
accept = 8443
connect = 127.0.0.1:8080
cert = /etc/stunnel/ssl/server.crt
key = /etc/stunnel/ssl/server.key
  • accept - the address and port to listen on for incoming TLS connections, in [host:]port form. With no host it listens on all IPv4 addresses.
  • connect - the plaintext backend to forward to. With no host it defaults to localhost.
  • cert - the server certificate chain file (PEM).
  • key - the matching private key. Defaults to the cert file if omitted; keep it readable only by the stunnel user (chmod 600).

Protocol Versions

Restrict the service to TLS 1.2 and TLS 1.3 using sslVersionMin. Leaving sslVersionMax at its default (all) keeps TLS 1.3 enabled:

sslVersionMin = TLSv1.2

The older sslVersion = TLSv1.2 directive forced a single fixed version and is superseded by the min/max pair on OpenSSL 1.1.0 and later. Use sslVersionMin rather than disabling individual protocols through options.

Cipher Suites

stunnel splits cipher control by protocol. ciphers selects the TLS 1.2 (and below) cipher list, while ciphersuites selects the TLS 1.3 cipher suites:

ciphers = ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305
ciphersuites = TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256
  • ciphers sets the TLS 1.2 cipher list and has no effect on TLS 1.3.
  • ciphersuites sets the TLS 1.3 cipher suites and requires OpenSSL 1.1.1 or later.

All six TLS 1.2 ciphers use ECDHE for forward secrecy and AEAD encryption (GCM or ChaCha20-Poly1305). ECDSA variants are listed first to prefer ECDSA certificates where available, with RSA variants following for compatibility.

OpenSSL Options Hardening

The options directive sets OpenSSL library options without the SSL_OP_ prefix. stunnel already applies NO_SSLv2 and NO_SSLv3 by default; add NO_RENEGOTIATION to refuse client-initiated renegotiation on TLS 1.2:

options = NO_RENEGOTIATION

Multiple options lines may be supplied, and a leading dash clears an option (for example -NO_RENEGOTIATION). TLS 1.3 removes renegotiation entirely, so this option only affects TLS 1.2 sessions.

Process Hardening

These are global options (placed before the first [service] section) that drop privileges and confine the daemon. stunnel can bind its listening port as root and then run as an unprivileged user:

setuid = stunnel
setgid = stunnel
chroot = /var/lib/stunnel
pid = /run/stunnel.pid
  • setuid / setgid - the Unix user and group stunnel runs as after startup.
  • chroot - a directory to confine the process to. When set, CApath, CRLpath, pid, and exec paths are interpreted relative to this jail.
  • pid - the pid file location. With chroot set this path is relative to the jail; an empty value writes no pid file.

Dual-Stack (IPv4 and IPv6)

A single accept binds to one address family only. To listen on both IPv4 and IPv6, define two service sections that share the same backend. Use :::PORT to bind IPv6:

[https-frontend-v4]
accept = 0.0.0.0:8443
connect = 127.0.0.1:8080
cert = /etc/stunnel/ssl/server.crt
key = /etc/stunnel/ssl/server.key

[https-frontend-v6]
accept = :::8443
connect = 127.0.0.1:8080
cert = /etc/stunnel/ssl/server.crt
key = /etc/stunnel/ssl/server.key

The 0.0.0.0:8443 listener covers all IPv4 addresses and the :::8443 listener covers all IPv6 addresses. Both forward to the same plaintext backend.

Mutual TLS

Standard TLS authenticates only the server. Mutual TLS adds client authentication: stunnel requests and validates a certificate from the connecting peer. The modern directives are verifyChain and verifyPeer, which replace the obsolete numeric verify levels:

verifyPeer = yes
verifyChain = yes
CAfile = /etc/stunnel/ssl/client-ca.crt
  • verifyChain - verify the peer certificate chain starting from the root CA. The CA certificates loaded via CAfile anchor the chain.
  • verifyPeer - verify the end-entity (leaf) peer certificate.
  • CAfile - the file of trusted CA certificates used by verifyChain and verifyPeer.

For reference, the legacy verify directive took a numeric level: 0 requests and ignores the chain, 1 verifies the chain only if present, 2 requires and verifies the chain, 3 additionally checks the leaf against a locally installed certificate, and 4 verifies only the leaf against a local certificate. Prefer verifyChain and verifyPeer on current releases.

Complete Configuration

# Global options (privilege drop and confinement)
setuid = stunnel
setgid = stunnel
chroot = /var/lib/stunnel
pid = /run/stunnel.pid

# IPv4 listener
[https-frontend-v4]
accept = 0.0.0.0:8443
connect = 127.0.0.1:8080
cert = /etc/stunnel/ssl/server.crt
key = /etc/stunnel/ssl/server.key

# Protocol versions
sslVersionMin = TLSv1.2

# TLS 1.2 cipher list
ciphers = ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305

# TLS 1.3 cipher suites
ciphersuites = TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256

# OpenSSL options hardening
options = NO_RENEGOTIATION

# Mutual TLS (optional)
verifyPeer = yes
verifyChain = yes
CAfile = /etc/stunnel/ssl/client-ca.crt

# IPv6 listener (same backend and settings)
[https-frontend-v6]
accept = :::8443
connect = 127.0.0.1:8080
cert = /etc/stunnel/ssl/server.crt
key = /etc/stunnel/ssl/server.key
sslVersionMin = TLSv1.2
ciphers = ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305
ciphersuites = TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256
options = NO_RENEGOTIATION

Client Configuration

In client mode stunnel accepts a plaintext connection from a local application and forwards it over TLS to a remote endpoint. Set client = yes, listen on a local plaintext port, and connect to the remote TLS service. Always verify the remote server certificate so the wrapped connection is authenticated:

[db-client]
client = yes
accept = 127.0.0.1:5432
connect = db.example.com:5432
sslVersionMin = TLSv1.2
ciphers = ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305
ciphersuites = TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256
verifyChain = yes
CAfile = /etc/stunnel/ssl/ca.crt

The local application connects in plaintext to 127.0.0.1:5432, and stunnel encrypts the traffic to db.example.com:5432. verifyChain = yes with a CAfile ensures stunnel rejects a remote server whose certificate does not chain to a trusted CA. To present a client certificate for mutual TLS, add cert and key to this section.

Security Notes

The cipher suite and protocol configuration in this guide addresses the following known TLS vulnerabilities:

  • POODLE (CVE-2014-3566, 2014): SSL 3.0 is excluded; stunnel disables SSLv2 and SSLv3 by default, and sslVersionMin = TLSv1.2 enforces a TLS 1.2 floor.
  • BEAST (CVE-2011-3389, 2011): Mitigated by requiring TLS 1.2 as the minimum; AEAD-only ciphers eliminate the CBC padding oracle.
  • CRIME (CVE-2012-4929, 2012): TLS compression is off by default in OpenSSL 1.1.0+; do not enable it.
  • Lucky13 (2013): AEAD-only cipher list eliminates CBC padding timing side-channels entirely.
  • FREAK (CVE-2015-0204, 2015): EXPORT-grade ciphers are excluded from the cipher string.
  • LOGJAM (CVE-2015-4000, 2015): Short-key DHE is excluded; only ECDHE key exchange is recommended.
  • Sweet32 (CVE-2016-2183, 2016): 3DES is excluded from the recommended cipher string (AEAD suites only), so it is never negotiated.
  • ROBOT (2017): Static RSA key exchange is excluded; only ECDHE is recommended.
  • Renegotiation injection (CVE-2009-3555, 2009): options = NO_RENEGOTIATION refuses client-initiated renegotiation on TLS 1.2; TLS 1.3 removes renegotiation entirely.
  • Downgrade attacks: TLS_FALLBACK_SCSV prevents protocol version rollback.

The following are not addressable through TLS configuration alone:

  • Heartbleed (CVE-2014-0160, 2014): A memory disclosure bug in OpenSSL 1.0.1 through 1.0.1f. Fixed in OpenSSL 1.0.1g (April 7, 2014). Addressed by patching OpenSSL, not by TLS configuration.
  • BREACH (CVE-2013-3587, 2013): Exploits HTTP-level response compression. Out of scope for a generic TLS wrapper; mitigated at the application layer.
  • DROWN (CVE-2016-0800, 2016): Requires SSLv2 to be enabled on any server sharing the same private key. Ensure SSLv2 is disabled on all services that use the same certificate and key pair.

Verification

Test the configuration syntax and start the daemon:

stunnel /etc/stunnel/stunnel.conf

On systemd systems, reload through the service unit:

systemctl restart stunnel

Verify TLS is working on the listening port:

openssl s_client -connect localhost:8443 -tls1_2
openssl s_client -connect localhost:8443 -tls1_3

Check the negotiated protocol and cipher:

echo | openssl s_client -connect localhost:8443 2>/dev/null | grep -E 'Protocol|Cipher'

Confirm that older protocols are refused:

openssl s_client -connect localhost:8443 -tls1_1

Confirm the IPv6 listener is reachable:

openssl s_client -connect [::1]:8443 -tls1_3

Related Guides

View all Infrastructure guides →

Configured TLS? Now Monitor It.

Generator Labs alerts you before certificates expire, get revoked, or fail chain validation, across HTTPS, SMTPS, IMAPS, LDAPS, and more.

Certificate Monitoring →