Home / Alt manpages / openssl-s_server(1ssl)

  • openssl-s_server(1ssl)
  • OpenSSL command
  • linux

Test a TLS Endpoint Safely with openssl s_server

openssl s_server spins up a throwaway TLS listener in seconds, which makes it the fastest way to check a handshake without touching a real service. This guide builds a disposable listener, connects a client to confirm the negotiated protocol and cipher, and serves a test response. Examples use the OpenSSL 3 interface documented by the local openssl-s_server(1ssl) manpage. Allow about fifteen minutes.

You need a shell, OpenSSL, and permission to create temporary files and listen on a high TCP port. This is a diagnostic server, not a production one: it can print protocol details, return a status page, or serve files from its current directory, but it has none of the access controls, logging, certificate renewal or shutdown policy a real service needs.

1. Check which OpenSSL you will run

Start with read-only checks. They need no elevated privileges:

$ command -v openssl
/home/linuxbrew/.linuxbrew/bin/openssl
$ openssl version
OpenSSL 3.6.1 27 Jan 2026 (Library: OpenSSL 3.6.1 27 Jan 2026)
$ dpkg-query -W -f='${Package} ${Version}\n' openssl
openssl 3.0.13-0ubuntu3.15

On this machine the shell resolves a Homebrew binary, while the installed Ubuntu package and local manpage identify OpenSSL 3.0.13. Keep that distinction in mind when comparing output with another host, and check openssl s_server -help on the exact binary in your deployment before relying on a newer option.

Checkpoint

Confirm s_server lists the options you intend to use, especially -accept, -cert, -key, -naccept and -brief.

2. Create a disposable certificate

A TLS server needs a certificate and private key. Make a short-lived, self-signed pair in a temporary directory for local testing only:

$ TEST_DIR=$(mktemp -d /tmp/openssl-s-server.XXXXXX)
$ openssl req -x509 -newkey rsa:2048 -nodes \
    -keyout "$TEST_DIR/key.pem" \
    -out "$TEST_DIR/cert.pem" \
    -sha256 -days 1 -subj '/CN=localhost'
.....+...+........+....+.....+.......+........+....
$ chmod 600 "$TEST_DIR/key.pem"

The key is deliberately unencrypted so the test can start without an interactive prompt. Treat it as disposable: do not use this output for a public service, and do not put a real private key in a shared temporary directory. The one-day certificate is self-signed, so any client will report a trust warning unless you explicitly hand it over as a trust anchor.

Checkpoint

Verify both files exist and that the private key is not readable by other users:

$ test -s "$TEST_DIR/cert.pem" && test -s "$TEST_DIR/key.pem"
$ stat -c '%a %n' "$TEST_DIR/key.pem"
600 /tmp/openssl-s-server.XXXXXX/key.pem

3. Start one bounded TLS listener

Run the server on loopback and an unprivileged port; sudo is not needed for port 14433. -naccept 1 makes the test exit after a single connection:

$ openssl s_server \
    -accept 127.0.0.1:14433 \
    -cert "$TEST_DIR/cert.pem" \
    -key "$TEST_DIR/key.pem" \
    -naccept 1 -brief
Using default temp DH parameters
ACCEPT

The command stays in the foreground while it listens. Leave out both -port and -accept and the default is port 4433 on all interfaces, so naming loopback explicitly stops an accidental test service becoming reachable over the network. Stop a foreground server with Ctrl-C; if you background it during testing, record the process ID and kill it when you are done.

4. Connect and inspect the handshake

In a second terminal, connect with the matching OpenSSL client. -brief asks for a compact summary, and -servername sends the TLS server name that most real clients send:

$ openssl s_client -connect 127.0.0.1:14433 \
    -servername localhost -brief </dev/null
Connecting to 127.0.0.1
depth=0 CN=localhost
verify error:num=18:self-signed certificate
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
Peer certificate: CN=localhost
Verification error: self-signed certificate
DONE

The exact cipher, key exchange group and diagnostic formatting can vary with the OpenSSL build. What matters is CONNECTION ESTABLISHED, a protocol version, a cipher suite and the expected certificate subject. The self-signed verification message is expected here: it means the handshake completed but the client did not trust the test certificate.

When the client exits, the server hits its one-connection limit and stops. If it does not, check the server terminal for a bind error, then see whether another process already owns the port:

$ ss -ltn 'sport = :14433'
State  Recv-Q Send-Q Local Address:Port Peer Address:Port Process

5. Return a status page or a test file

For a browser or HTTP client, swap the plain listener for -www, which returns an OpenSSL status page containing session parameters:

$ openssl s_server -accept 127.0.0.1:14433 \
    -cert "$TEST_DIR/cert.pem" -key "$TEST_DIR/key.pem" -www
$ curl -k https://127.0.0.1:14433/

The -k flag tells curl to accept the self-signed certificate for this test only; without it, curl should reject a certificate that is not signed by a trusted CA. The special /renegcert and /reneg paths have server-specific diagnostic behaviour, so do not use them as an application endpoint.

To serve files relative to the current directory, use -WWW instead. This reads files named by the request, so run it from a directory containing only deliberate test content:

$ mkdir -p "$TEST_DIR/site"
$ printf '%s\n' 'temporary TLS test' > "$TEST_DIR/site/index.html"
$ cd "$TEST_DIR/site"
$ openssl s_server -accept 127.0.0.1:14433 \
    -cert "$TEST_DIR/cert.pem" -key "$TEST_DIR/key.pem" -WWW
$ curl -k https://127.0.0.1:14433/index.html

Warning

This is a service-disrupting and security-sensitive mode if it is ever exposed beyond loopback. Stop it with Ctrl-C, change back to your original directory, and only remove the temporary directory once you have checked it holds nothing you need. Never point -WWW at a home directory or a document tree containing secrets.

6. Add client certificate verification only when required

-verify DEPTH asks clients for a certificate but still lets one without a certificate continue; -Verify DEPTH requires it. Neither option supplies trust by itself: use -verifyCAfile, -verifyCApath or -verifyCAstore to name trusted CA certificates, and add -verify_return_error when a verification failure must close the connection rather than just being reported for debugging.

Do not bolt these flags onto the basic self-signed test and call it proof of mutual TLS. A meaningful mutual-TLS test needs a test CA, a client certificate signed by that CA, and a client configured to present it. Keep those test credentials well away from production credentials, and delete or revoke them according to your local process once you are done.

Done means

  • Binary checked. You confirmed the OpenSSL binary and version your shell selects.
  • Disposable cert made. A short-lived certificate exists with its private key protected.
  • Listener bound safely. s_server is on loopback and a high port, not the open default.
  • Handshake confirmed. A client completed a handshake and showed the negotiated protocol and cipher.
  • Warnings understood. You can tell an expected self-signed warning from a genuine failed handshake.
  • Cleaned up. The listener is stopped and test files are kept away from production data.