stunnel TLS Proxy 5.82 on AWS User Guide
Overview
This guide covers launching and verifying stunnel TLS Proxy 5.82 on Ubuntu 24.04 from cloudimg on Amazon EC2. It is one version of the cloudimg Hardened WireGuard VPN & VPN, Proxy and Remote Access AMIs listing on AWS Marketplace; you pick it as the software version when you subscribe and launch. The other VPN, proxy and remote access servers in the listing are described on the listing's guide hub.
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), in which case 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 image ships two services, both pointing at the instance's own SSH server as an always-present backend you can test against straight away:
[ssh-tls-psk]on port 8443: TLS with pre-shared-key authentication. Only a client holding 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; SSH behind it still requires your SSH key.
Point either service (or a copy of it) at your own service to put TLS in front of it (see Step 9).
What cloudimg adds
- 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 author Michał Trojnara's signing key. The binary is a position-independent executable with full RELRO, a stack protector and
_FORTIFY_SOURCE=3, and 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 (it is not a certificate authority, so it can only ever vouch for this instance's names). stunnel cannot start until they exist.
- Keys never shown or copied elsewhere.
/root/stunnel-credentials.txtholds the PSK identity, fingerprints and endpoints, never a key. The key files are readable by root and thestunnelservice account only. - 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 and 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 (the server refuses TLS 1.0 and 1.1), every suite has forward secrecy and authenticated encryption (no RSA key exchange, no CBC suites, no PSK without a Diffie-Hellman exchange), and renegotiation is off.
- Hardened service. stunnel runs as an unprivileged
stunneluser whose only privilege is binding ports below 1024, with a read-only system, no access to home directories or devices, a restricted set of system calls and core dumps 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 and keyboard-interactive login and root login are disabled for SSH, which is also what both shipped services forward to.
Prerequisites
- An AWS account and permission to launch EC2 instances
- An EC2 key pair in the Region you launch in
- On the machine you will connect from: stunnel for the pre-shared-key tunnel (
sudo apt-get install stunnel4on Ubuntu or Debian,brew install stunnelon macOS) and OpenSSL for the certificate service (preinstalled on most systems)
Recommended instance type: m5.large. stunnel itself uses very little CPU and memory; 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 |
Step 1: Launch the instance
Option A: AWS Marketplace console
- Open the cloudimg Hardened WireGuard VPN & VPN, Proxy and Remote Access AMIs listing on AWS Marketplace and choose View purchase options, then Subscribe. One subscription covers every version in the listing.
- Once 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 (
m5.largerecommended; the software charge is $0.04 per vCPU per hour) and your key pair. - 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, leave the metadata defaults (the image already requires IMDSv2), then choose Launch.
Option B: AWS CLI
Run this 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, and 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: the AMI already makes IMDSv2 required.
Step 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, readable only by root. This command prints it 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=cloudimg-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://www.cloudimg.co.uk/guides/stunnel-tls-proxy-5-82-on-ubuntu-24-04-aws/
STUNNEL_VPC_IP is the instance's private address and STUNNEL_PUBLIC_IP / STUNNEL_PUBLIC_HOSTNAME its public address and name (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.
Step 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 8443 and 8444 and nowhere else. The two :53 sockets are Ubuntu's local DNS resolver, which listens on loopback only.
Step 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 it serves on 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 = cloudimg
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
Step 5: Copy the PSK file to your machine
The [ssh-tls-psk] service uses a pre-shared key: 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 this 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, so never paste it into chat, e-mail or a ticket, keep it 0600, and do not commit it to source control. stunnel refuses a PSK file that other users can read.
Step 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. It listens on your own 127.0.0.1:2222 and forwards everything over TLS, authenticated with the pre-shared key, to the instance's port 8443:
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 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 will ask you to accept the host key the first time, exactly as on port 22: it is the same SSH server. Stop the client with Ctrl+C when you are done (set foreground = no to run it in the background instead).
Once the tunnel works you can remove port 22 from the instance's security group and reach SSH only through 8443. Keep a way back in (for example AWS Systems Manager Session Manager, or a second security group you can attach) before you do.
Step 7: Use the certificate service
Port 8444 is the classic TLS termination pattern: any TLS client can connect, and stunnel proves the instance's identity with its certificate. Copy the certificate (it is public) and check its fingerprint against CERT_SHA256 from Step 2. 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, verifying the server against that certificate. SSH over the certificate service:
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).
Step 8: Confirm what is refused
A client with the wrong pre-shared key never completes the handshake, so it never reaches SSH. A client that offers only TLS 1.0 or 1.1 is refused by the server itself. 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 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 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.
Step 9: Put TLS in front of your own service
The services are defined in /etc/stunnel/stunnel.conf. As shipped:
[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-guardchecks the key files and the configuration and prints nothing when they are finesudo systemctl reload stunnelapplies it (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-pagershowsConfiguration successful(or the line 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. Keep stunnel-selftest working by leaving 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; it cannot write log files elsewhere on the read-only system.
Step 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 with their normal trust store and your domain name.
Step 11: Rotate the pre-shared key and certificate (optional)
To replace both, for example when someone who held the PSK file leaves, run on the instance:
sudo stunnel-rotate-secrets
It 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 it fails, stunnel stays stopped and journalctl -u stunnel-rotate-secrets says why. Every client holding the old PSK file is cut off immediately: copy the new file to them 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 one; install yours again afterwards.
Step 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 "cloudimg-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. That matters for anything that limits or blocks by source address: sshd's MaxStartups and PerSourceMaxStartups, fail2ban, or a database's host-based access rules would treat all clients 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/cloudimg-firstboot.log. No log contains a key.
How the secure start works
stunnel-firstboot.serviceruns once, on the instance's first boot. It reads the instance's addresses from the instance metadata service (IMDSv2), 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/(key files owner root, groupstunnel, mode0640).stunnel.servicestarts only when the ready marker exists, so it can never run without this instance's own keys. Before each start and reload,stunnel-guardchecks every key file the configuration names (root-owned, not a link, not readable by other users, PSKs at least 128 bits) and that the configuration does not change 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 it 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 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 8443 only. Remove port 8444 from the security group if you do not use the certificate service.
- Treat
psk.txtlike a private key: copy it only over SSH, keep it0600, and rotate withsudo stunnel-rotate-secretswhen anyone who held it should lose access. - Keep every
connecton a loopback or private address, and use a certificate from a certificate authority for production clients of the certificate service. - Keep the instance patched:
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 cloudimg 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 licence 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. cloudimg is not affiliated with or endorsed by the stunnel project. For help with this image, contact cloudimg support through the AWS Marketplace listing.