Deployment guide

stunnel TLS Proxy 5.82

Overview

stunnel is a proxy that adds TLS encryption to any TCP service without changing the service itself. Each stunnel service accepts connections on one port and forwards them to a fixed backend (connect). In server mode, it terminates TLS and hands the decrypted stream to a plaintext service. In client mode, it does the reverse. stunnel can authenticate clients with certificates or with pre-shared keys (PSK), and a client that does not hold the key cannot even complete the TLS handshake. stunnel is a headless network service with no web console.

This SecureAnyCloud image runs stunnel TLS Proxy 5.82 on Ubuntu 24.04 on Amazon EC2. It is one version of the SecureAnyCloud Hardened WireGuard VPN & VPN, Proxy and Remote Access AMIs listing on AWS Marketplace. You pick the software version when you subscribe and launch. The listing's guide hub describes the other VPN, proxy and remote access servers in the listing.

What you get:

  • [ssh-tls-psk] on port 8443: TLS with pre-shared-key authentication. Only a client that holds this instance's PSK identity and key completes the handshake.
  • [ssh-tls-cert] on port 8444: certificate TLS termination. stunnel presents this instance's certificate, and SSH behind it still requires your SSH key.
  • Both services point at the instance's own SSH server, an always-present backend you can test against straight away. To put TLS in front of your own service, point either service, or a copy of it, at that service (see Step 9).
  • The current upstream release, verified twice. Ubuntu 24.04 only carries stunnel 5.72 in its community-maintained universe component, which predates the security fixes in 5.80 and 5.81. This image ships stunnel 5.82, built from the unmodified stunnel.org release after its SHA-256 checksum was checked and its OpenPGP signature was verified against the signing key of the author, Michał Trojnara. The binary is a position-independent executable with full RELRO, a stack protector and _FORTIFY_SOURCE=3. The compiler and build tools are removed again before the image is captured.
  • A unique pre-shared key and certificate per instance, generated on first boot. No key is baked into the image. On first boot, the instance generates its own PSK identity and 256-bit key, and its own RSA-2048 key and end-entity certificate. The certificate is not a certificate authority, so it can only ever vouch for this instance's names. stunnel cannot start until the keys exist.
  • Keys are never shown or copied elsewhere. /root/stunnel-credentials.txt holds the PSK identity, fingerprints and endpoints, never a key. The key files are readable only by root and the stunnel service account.
  • First boot proves both services. Before it marks the instance ready, first boot checks with a real stunnel client that the right PSK reaches SSH on 8443, that a wrong PSK, no PSK and plaintext are refused, and that 8444 presents a certificate that verifies for this instance. If any check fails, stunnel stays stopped.
  • Modern TLS only. TLS 1.2 is the minimum, and the server refuses TLS 1.0 and 1.1. Every suite has forward secrecy and authenticated encryption, with no RSA key exchange, no CBC suites and no PSK without a Diffie-Hellman exchange. Renegotiation is off.
  • Hardened service. stunnel runs as an unprivileged stunnel user whose only privilege is binding ports below 1024. The system is read-only, home directories and devices are inaccessible, system calls are restricted and core dumps are disabled. A guard refuses to start stunnel with a key file that is a link, readable by other users or group-writable, or with a short pre-shared key.
  • No route to the instance metadata service. stunnel is blocked from the EC2 instance metadata service at the system level (IPv4 and IPv6), so no service you configure can be turned into a way to read the instance's credentials.
  • Key-only SSH. Password login, keyboard-interactive login and root login are disabled for SSH, which is also where both shipped services forward.
  • 24/7 SecureAnyCloud support

Prerequisites

  • An AWS account and permission to launch EC2 instances
  • An EC2 key pair in the Region you launch in
  • On the machine you connect from, stunnel for the pre-shared-key tunnel (sudo apt-get install stunnel4 on Ubuntu or Debian, brew install stunnel on macOS) and OpenSSL for the certificate service (preinstalled on most systems)

Recommended instance type: m5.large. stunnel itself uses very little CPU and memory, so t3.micro or larger is enough for light traffic.

Connecting to your instance

Version Operating system SSH user

stunnel TLS Proxy 5.82 on Ubuntu 24.04 Ubuntu 24.04 LTS ubuntu

Port Protocol Purpose Who should reach it

22 TCP SSH administration (and to copy the PSK file the first time) Your administrators

8443 TCP [ssh-tls-psk]: TLS with pre-shared-key authentication, forwarded to SSH on this instance The machines that hold the PSK

8444 TCP [ssh-tls-cert]: TLS with this instance's certificate, forwarded to SSH on this instance The machines you connect from

1

Launch the instance

Option A: AWS Marketplace console

  • Open the SecureAnyCloud Hardened WireGuard VPN & VPN, Proxy and Remote Access AMIs listing on AWS Marketplace, choose View purchase options, then choose Subscribe. One subscription covers every version in the listing.
  • When the subscription is active, choose Launch your software.
  • Choose One-click launch from AWS Marketplace, or Launch from EC2 Console for the full launch wizard. In Version, choose stunnel TLS Proxy 5.82 on Ubuntu 24.04, then choose your Region.
  • Pick an instance type and your key pair. m5.large is recommended, and the software charge is $0.04 per vCPU per hour.
  • Check the security group. The listing's default group allows SSH (22) and the two stunnel ports (8443 and 8444). Restrict all three to the addresses you will connect from.
  • Choose your VPC and subnet and leave the metadata defaults, because the image already requires IMDSv2. Then choose Launch.

Option B: AWS CLI

Run the following command on your own workstation. It creates a security group that allows SSH and both stunnel ports from your own address range only, then launches the instance. Replace the AMI ID with the one shown for your Region on the Marketplace launch page. Replace the VPC, subnet, key pair and CIDR range with your own.

SG=$(aws ec2 create-security-group --group-name stunnel \
  --description "stunnel TLS proxy" --vpc-id vpc-0123456789abcdef0 \
  --query GroupId --output text) for p in 22 8443 8444; do aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port "$p" --cidr <your-mgmt-cidr> done aws ec2 run-instances \
  --image-id ami-0123456789abcdef0 \
  --instance-type m5.large \
  --key-name my-key-pair \
  --security-group-ids "$SG" \
  --subnet-id subnet-0123456789abcdef0 \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=stunnel}]'

You do not need --metadata-options, because the AMI already requires IMDSv2.

2

Connect and read the instance details

Connect as ubuntu with the key pair you chose.

ssh -i ~/.ssh/my-key-pair.pem ubuntu@<public-ip>

First boot finishes within a minute of launch. It generates this instance's pre-shared key and certificate, starts stunnel, checks both services and then writes /root/stunnel-credentials.txt, which only root can read. The following command prints the file without the PSK file's fingerprint line.

sudo grep -v '^PSK_KEY_SHA256=' /root/stunnel-credentials.txt
# stunnel 5.82 - written by stunnel-firstboot (firstboot). Every value below is unique to this instance; nothing was baked
# into the image. The pre-shared key itself is in /etc/stunnel/psk.txt (IDENTITY:KEY, root:stunnel 0640): anyone holding
# it can open the [ssh-tls-psk] tunnel, so copy it only over SSH and never paste it into chat, e-mail or a ticket.
# PSK_IDENTITY=sac-3f24615c PSK_FILE=/etc/stunnel/psk.txt CERT_FILE=/etc/stunnel/certs/stunnel.crt CERT_SHA256=34:9A:EF:64:07:B9:E7:8E:4E:C6:46:E9:77:9F:85:0E:7E:AD:FA:FA:B3:9F:8E:0F:B6:1E:CC:E7:58:91:FC:33 CERT_NAMES=IP:172.31.20.15,IP:203.0.113.10,DNS:ec2-203-0-113-10.compute-1.amazonaws.com,IP:127.0.0.1 STUNNEL_PSK_PORT=8443 STUNNEL_CERT_PORT=8444 STUNNEL_VPC_IP=172.31.20.15 STUNNEL_PUBLIC_IP=203.0.113.10 STUNNEL_PUBLIC_HOSTNAME=ec2-203-0-113-10.compute-1.amazonaws.com STUNNEL_TARGET=127.0.0.1:22
#
# Copy the PSK file to your machine (run THERE; it travels inside SSH and is never shown on screen):
# ssh -i <your-key.pem> ubuntu@<instance-address> 'sudo cat /etc/stunnel/psk.txt' > psk.txt && chmod 600 psk.txt
# sha256sum psk.txt
# must print the PSK_KEY_SHA256 above
# Then run a stunnel client with PSKsecrets = psk.txt (see the guide) and:
ssh -p 2222 ubuntu@127.0.0.1
# Self-test:
sudo stunnel-selftest New PSK + certificate:
sudo stunnel-rotate-secrets Guide: https://secureanycloud.com/stacks/stunnel-tls-proxy-5-82/

STUNNEL_VPC_IP is the instance's private address. STUNNEL_PUBLIC_IP and STUNNEL_PUBLIC_HOSTNAME are its public address and name, and are none in a subnet without public addresses. The certificate is valid for exactly the names in CERT_NAMES. To see the PSK file's fingerprint, run sudo grep '^PSK_KEY_SHA256=' /root/stunnel-credentials.txt. The fingerprints are not secret, but psk.txt and the TLS key are.

3

Confirm stunnel is running

systemctl is-active stunnel stunnel-firstboot
sudo ss -Hltn | awk '{print $4}' | sort
active active 0.0.0.0:22 0.0.0.0:8443 0.0.0.0:8444 127.0.0.53%lo:53 127.0.0.54:53 [::]:22

stunnel listens on ports 8443 and 8444 and nowhere else. The two :53 sockets belong to Ubuntu's local DNS resolver, which listens on loopback only.

4

Run the self-test

The image includes a self-test that exercises both services end to end with a real stunnel client, without printing any key.

sudo stunnel-selftest
ok psk.txt and the certificate match /root/stunnel-credentials.txt (2ae122605819... / 34:9A:EF:64:07:B9...) ok the right PSK on 172.31.20.15:8443 reaches sshd (SSH-2.0-OpenSSH_9.6p1) ok a wrong PSK is refused on 8443 ok a TLS client without a PSK is refused on 8443 ok 8444 presents this instance's certificate (verified, IP 172.31.20.15) and reaches sshd ok a plaintext client on 8443 never sees sshd ok a plaintext client on 8444 never sees sshd SELFTEST_OK

The certificate that stunnel serves on port 8444 is an end-entity certificate for this instance's names only.

sudo openssl x509 -in /etc/stunnel/certs/stunnel.crt -noout -subject -fingerprint -sha256 -ext subjectAltName,basicConstraints,extendedKeyUsage
subject=CN = ec2-203-0-113-10.compute-1.amazonaws.com, O = sac sha256 Fingerprint=34:9A:EF:64:07:B9:E7:8E:4E:C6:46:E9:77:9F:85:0E:7E:AD:FA:FA:B3:9F:8E:0F:B6:1E:CC:E7:58:91:FC:33 X509v3 Subject Alternative Name: IP Address:172.31.20.15, IP Address:203.0.113.10, DNS:ec2-203-0-113-10.compute-1.amazonaws.com, IP Address:127.0.0.1 X509v3 Basic Constraints: critical CA:FALSE X509v3 Extended Key Usage: TLS Web Server Authentication
5

Copy the PSK file to your machine

The [ssh-tls-psk] service uses a pre-shared key, so the machine you connect from needs the same identity and key. Copy the file over SSH, which encrypts it in transit and never shows it on screen. Run the following command on your own machine, not on the instance.

ssh -i ~/.ssh/my-key-pair.pem ubuntu@<public-ip> 'sudo cat /etc/stunnel/psk.txt' > psk.txt chmod 600 psk.txt sha256sum psk.txt

On macOS, use shasum -a 256 psk.txt. The fingerprint must match the PSK_KEY_SHA256 line in /root/stunnel-credentials.txt on the instance. Anyone who holds the file can open the tunnel. Never paste it into chat, email or a ticket, keep it at 0600 and do not commit it to source control. stunnel refuses a PSK file that other users can read.

6

Connect through the pre-shared-key tunnel

On your machine, create a stunnel client configuration, for example stunnel-client.conf next to psk.txt. The client listens on your own 127.0.0.1:2222 and forwards everything over TLS to the instance's port 8443, authenticated with the pre-shared key.

foreground = yes pid = [ssh-psk] client = yes accept = 127.0.0.1:2222 connect = 203.0.113.10:8443 PSKsecrets = /home/you/psk.txt sslVersionMin = TLSv1.2

Replace 203.0.113.10 with the instance's public address, and replace the PSKsecrets path with the absolute path of your copy. Start the client in one terminal and connect through it from another.

# on your machine; stunnel-client.conf connects to <public-ip>:8443 stunnel stunnel-client.conf ssh -i ~/.ssh/my-key-pair.pem -p 2222 ubuntu@127.0.0.1

SSH asks you to accept the host key the first time, exactly as on port 22, because it is the same SSH server. Stop the client with Ctrl+C when you are done. To run the client in the background instead, set foreground = no.

Once the tunnel works, you can remove port 22 from the instance's security group and reach SSH only through port 8443. Before you do, make sure you keep a way back in, for example AWS Systems Manager Session Manager or a second security group that you can attach.

7

Use the certificate service

Port 8444 uses the classic TLS termination pattern. Any TLS client can connect, and stunnel proves the instance's identity with its certificate. Copy the certificate, which is public, and check its fingerprint against CERT_SHA256 from Step 2. Run the following on your machine.

ssh -i ~/.ssh/my-key-pair.pem ubuntu@<public-ip> 'sudo cat /etc/stunnel/certs/stunnel.crt' > stunnel-instance.crt openssl x509 -in stunnel-instance.crt -noout -fingerprint -sha256

Then connect to SSH over the certificate service, verifying the server against that certificate.

ssh -i ~/.ssh/my-key-pair.pem \
  -o ProxyCommand="openssl s_client -quiet -verify_quiet -connect <public-ip>:8444 -CAfile stunnel-instance.crt -verify_return_error -verify_ip <public-ip>" \
  ubuntu@<public-ip>

Do not add this self-signed certificate to your system's trust store. Compare its fingerprint as above, or replace it with a certificate from a certificate authority (Step 10).

8

Confirm what is refused

A client with the wrong pre-shared key never completes the handshake, so it never reaches SSH. The server itself refuses a client that offers only TLS 1.0 or 1.1. Run the following on your machine. The -cipher option lets your own OpenSSL offer the old versions at all.

openssl s_client -connect <public-ip>:8444 -tls1_1 -cipher 'DEFAULT@SECLEVEL=0' </dev/null 2>&1 | grep -i alert
openssl s_client -connect <public-ip>:8443 -tls1_2 </dev/null 2>&1 | grep -E 'Cipher is|alert'

The first command prints a tlsv1 alert protocol version line from the server. The second shows that a client without the pre-shared key gets no session on port 8443 (Cipher is (NONE) and a handshake alert). The self-test in Step 4 checks the wrong-key and plaintext cases on the instance itself.

9

Put TLS in front of your own service

The services are defined in /etc/stunnel/stunnel.conf. The shipped configuration looks like this.

[ssh-tls-psk] accept = 0.0.0.0:8443 connect = 127.0.0.1:22 PSKsecrets = /etc/stunnel/psk.txt [ssh-tls-cert] accept = 0.0.0.0:8444 connect = 127.0.0.1:22 cert = /etc/stunnel/certs/stunnel.crt key = /etc/stunnel/certs/stunnel.key

To protect a different service, for example a PostgreSQL server listening on 127.0.0.1:5432, edit the file with sudoedit /etc/stunnel/stunnel.conf. Either change connect in one of the services, or copy a block under a new name ([postgres-tls]) with its own accept port and connect. Then check and apply the change.

  • sudo stunnel-guard checks the key files and the configuration and prints nothing when they are fine.
  • sudo systemctl reload stunnel applies the change. stunnel re-reads its configuration in place. The guard runs first, and a refused change leaves the running configuration untouched.
  • sudo journalctl -u stunnel -n 20 --no-pager shows Configuration successful, or the line that stunnel objects to.

Open the new port in the instance's security group for your clients only. Keep the backend on a loopback or private address, so the service itself is never exposed to the network. To keep stunnel-selftest working, leave the two shipped services in place, or ignore its result once you have removed them. Global options at the top of the file (TLS 1.2 minimum, server cipher preference, no renegotiation) apply to every service.

stunnel runs as the stunnel user, so a new PSK file or key file must be owned by root, group stunnel, mode 0640 (sudo chown root:stunnel FILE && sudo chmod 0640 FILE). The guard refuses anything looser. stunnel logs to the journal and cannot write log files elsewhere on the read-only system.

10

Use a certificate from a certificate authority (optional)

For production use of the certificate service, replace the self-signed certificate with one issued for your domain name. Copy the certificate chain to /etc/stunnel/certs/stunnel.crt (root:root 0644) and its private key to /etc/stunnel/certs/stunnel.key (root:stunnel 0640). Run sudo stunnel-guard, then sudo systemctl reload stunnel. Clients can then verify the server against their normal trust store and your domain name.

11

Rotate the pre-shared key and certificate (optional)

To replace both, for example when someone who held the PSK file leaves, run the following on the instance.

sudo stunnel-rotate-secrets

The command generates a new PSK identity and key and a new key and certificate, restarts stunnel, runs the self-test and updates /root/stunnel-credentials.txt. The rotation runs as a system job, so a dropped SSH connection cannot leave it half-done. If the rotation fails, stunnel stays stopped, and journalctl -u stunnel-rotate-secrets shows why.

Every client that holds the old PSK file is cut off immediately. Copy the new file to those clients as in Step 5, and compare the new certificate fingerprint as in Step 7. If you replaced the certificate with your own (Step 10), the rotation replaces it with a new self-signed certificate, so install yours again afterward.

12

Logs and client addresses

stunnel writes every connection, with the client's real address, and any TLS error to the journal.

sudo journalctl -u stunnel -n 8 --no-pager -o cat
2026.10.31 21:01:35 LOG5[14]: Service [ssh-tls-psk] accepted connection from 198.51.100.7:42102 2026.10.31 21:01:35 LOG5[14]: Key configured for PSK identity "sac-3f24615c" 2026.10.31 21:01:35 LOG5[14]: s_connect: connected 127.0.0.1:22 2026.10.31 21:01:35 LOG5[14]: Service [ssh-tls-psk] connected remote server from 127.0.0.1:38610 2026.10.31 21:01:35 LOG5[14]: Connection closed: 2995 byte(s) sent to TLS, 2983 byte(s) sent to socket 2026.10.31 21:01:35 LOG5[15]: Service [ssh-tls-cert] accepted connection from 198.51.100.7:39782 2026.10.31 21:01:35 LOG5[15]: s_connect: connected 127.0.0.1:22 2026.10.31 21:01:35 LOG5[15]: Service [ssh-tls-cert] connected remote server from 127.0.0.1:38624

The backend behind stunnel sees every connection as coming from 127.0.0.1, not from the real client. This matters for anything that limits or blocks by source address. The sshd settings MaxStartups and PerSourceMaxStartups, fail2ban and a database's host-based access rules all treat every client as one local client. Use stunnel's journal for the real addresses, and apply per-address limits at the security group or in front of stunnel. First boot also logs to /var/log/sac/sac-firstboot.log. No log contains a key.

How the secure start works

  • stunnel-firstboot.service runs once, on the instance's first boot. It reads the instance's addresses from the instance metadata service (IMDSv2). It then generates the PSK identity and a 256-bit key with the kernel's random number generator into /etc/stunnel/psk.txt, and an RSA-2048 key and end-entity certificate for the instance's addresses into /etc/stunnel/certs/. The key files are owned by root, group stunnel, mode 0640.
  • stunnel.service starts only when the ready marker exists, so it can never run without this instance's own keys. Before each start and reload, stunnel-guard checks every key file that the configuration names. A key file must be root-owned, not a link and not readable by other users, and PSKs must be at least 128 bits. The guard also checks that the configuration does not change the user or root directory itself.
  • First boot then runs the self-test with a real stunnel client. The right PSK reaches SSH on 8443, a wrong PSK, no PSK and plaintext are refused, and 8444 presents a certificate that verifies for this instance. Only then does first boot write /root/stunnel-credentials.txt. If any step fails, stunnel is stopped and the ready marker is removed, so the next boot starts over.

Security recommendations

  • Restrict the ports. Limit ports 22, 8443 and 8444 in the security group to the addresses you connect from. When the PSK tunnel works, consider closing port 22 and using port 8443 only. Remove port 8444 from the security group if you do not use the certificate service.
  • Protect the PSK file. Treat psk.txt like a private key. Copy it only over SSH, keep it at 0600 and rotate it with sudo stunnel-rotate-secrets when anyone who held it should lose access.
  • Keep backends private. Keep every connect on a loopback or private address, and use a certificate from a certificate authority for production clients of the certificate service.
  • Keep the instance patched. Run sudo apt-get update && sudo apt-get upgrade. Unattended security updates are enabled for the operating system. stunnel itself is a source build, so take new SecureAnyCloud versions of this image for stunnel updates.

Support and licensing

stunnel is open-source software by Michał Trojnara, released under the GNU General Public License version 2 or later with a special exception that permits linking with OpenSSL. The license texts, the build provenance and the complete source code of the version in this image (the signed release tarball and its signature) are in /usr/local/share/doc/stunnel/. The same source is available from stunnel.org. SecureAnyCloud is not affiliated with or endorsed by the stunnel project. For help with this image, contact SecureAnyCloud support through the AWS Marketplace listing.

Deploy on AWS

Launch stunnel TLS Proxy 5.82 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.

View on Marketplace

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on AWS