When a TLS connection fails, the error you get from a browser, curl, or a Java application is usually vague. “Handshake failed.” “Certificate not trusted.” “Connection reset.”
openssl s_client is how you find out what is actually going on. It connects to a server like any other TLS client, then prints everything it sees: the certificate chain, the negotiated protocol and cipher, verification results, and the TLS extensions the server supports.
This guide walks through the s_client options I use most for troubleshooting, with example output and notes on how to read it.
The examples assume OpenSSL 3.x. Check your version with:
openssl version
OpenSSL 3.0.13 30 Jan 2024 (Library: OpenSSL 3.0.13 30 Jan 2024)
On macOS, /usr/bin/openssl is actually LibreSSL, which doesn’t support every option shown here. Install OpenSSL with Homebrew if you need the full feature set.
Table of Contents
Connect to a Server
The basic command:
openssl s_client -connect www.howtouselinux.com:443
The output is long, but it has four parts worth knowing.
1. The certificate chain the server sent:
Certificate chain
0 s:CN = www.howtouselinux.com
i:C = US, O = Let's Encrypt, CN = R11
1 s:C = US, O = Let's Encrypt, CN = R11
i:C = US, O = Internet Security Research Group, CN = ISRG Root X1
s: is the subject (who the certificate belongs to) and i: is the issuer (who signed it). Certificate 0 is the server’s own certificate. Each entry should be issued by the one below it.
2. The server certificate in PEM format:
See also: Mastering the Linux Command Line — Your Complete Free Training Guide
Server certificate
-----BEGIN CERTIFICATE-----
MIIFGDCCBACgAwIBAgISA4b0Yz00UKhHzPeZEB95HCHIMA0GCSqGSIb3DQEBCwUA
...
-----END CERTIFICATE-----
Only the server’s own certificate is printed here. The intermediate certificates are only listed by name. To print them too, use -showcerts, covered below.
3. The handshake summary:
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 2048 bit
4. The verification result:
Verify return code: 0 (ok)
0 (ok) means the chain was verified against your system’s trusted CA store. Any other number means verification failed, and the text tells you why.
After the handshake, s_client waits for input. You’re connected to the server, and anything you type is sent over the encrypted connection. Press Ctrl+C or Ctrl+D to exit.
Get a short summary with -brief
If you only want the essentials:
openssl s_client -connect www.howtouselinux.com:443 -brief </dev/null
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
Peer certificate: CN = www.howtouselinux.com
Hash used: SHA256
Signature type: RSA-PSS
Verification: OK
Server Temp Key: X25519, 253 bits
DONE
This is the fastest way to answer “which TLS version and cipher did we get, and did the certificate verify?”
Why the Examples Use echo or </dev/null
Because s_client waits for input after connecting, it doesn’t exit on its own. That’s a problem in scripts and pipelines.
Closing standard input makes it disconnect as soon as the handshake is finished. Either of these works:
echo | openssl s_client -connect www.howtouselinux.com:443
openssl s_client -connect www.howtouselinux.com:443 </dev/null
You’ll also see 2>/dev/null in many examples. s_client writes connection details such as depth= lines and DONE to standard error. Discarding them keeps the output clean when you pipe it into another command.
Set the Server Name (SNI)
Most servers host several sites on one IP address. During the handshake, the client tells the server which hostname it wants using an extension called SNI (Server Name Indication), and the server picks the matching certificate.
Since OpenSSL 1.1.1, s_client sends SNI automatically, using the hostname from -connect. You need -servername when:
- you connect to an IP address
- you connect to one name but want to test the certificate for another
For example, to test the certificate for www.howtouselinux.com on a specific server IP:
openssl s_client -connect 203.0.113.10:443 -servername www.howtouselinux.com
This is useful when you’re testing a new server before changing DNS, or checking one node behind a load balancer.
To see what a server returns when the client sends no SNI:
openssl s_client -connect www.howtouselinux.com:443 -noservername
If the certificate differs from the one you get with SNI, or the handshake fails, the server depends on SNI. Very old clients that don’t send SNI would get the wrong certificate or none at all. -noservername and -servername can’t be used together.
On OpenSSL 1.0.x, SNI isn’t sent automatically, so always add -servername there.
Test a Specific TLS Version
To check whether a server supports a particular protocol version, force it:
openssl s_client -connect www.howtouselinux.com:443 -tls1_3
openssl s_client -connect www.howtouselinux.com:443 -tls1_2
Or exclude versions and let the rest be negotiated:
openssl s_client -connect www.howtouselinux.com:443 -no_tls1_3
Available flags:
- Force one version:
-tls1_3,-tls1_2,-tls1_1,-tls1 - Disable a version:
-no_tls1_3,-no_tls1_2,-no_tls1_1,-no_tls1
Check the result in the SSL-Session section of the output, or with -brief. With -no_tls1_3, a modern server falls back to TLS 1.2:
SSL-Session:
Protocol : TLSv1.2
Cipher : ECDHE-RSA-AES128-GCM-SHA256
Notice that the cipher name changes format between versions. TLS 1.3 cipher suites look like TLS_AES_256_GCM_SHA384. TLS 1.2 ciphers look like ECDHE-RSA-AES128-GCM-SHA256. If you ever see a TLS 1.2-style cipher next to Protocol: TLSv1.3, something’s wrong with the output you’re looking at.
A note on SSLv2, SSLv3, TLS 1.0, and TLS 1.1
Older guides list -ssl2 and -ssl3. SSLv2 support was removed in OpenSSL 1.1.0, and most builds are compiled without SSLv3, so those flags usually return an unknown option error.
TLS 1.0 and 1.1 are a different problem. In OpenSSL 3.x, the default security level blocks them, so this fails before anything is sent to the server:
openssl s_client -connect www.howtouselinux.com:443 -tls1_1
40F7...:error:0A0000BF:SSL routines:tls_setup_handshake:no protocols available
no protocols available is a client-side error. Your OpenSSL refused to offer the protocol. It tells you nothing about the server.
To actually ask the server whether it still accepts TLS 1.1, lower the client security level for that test only:
openssl s_client -connect www.howtouselinux.com:443 -tls1_1 -cipher 'DEFAULT@SECLEVEL=0'
If the server has TLS 1.1 disabled, which it should, you’ll get a server-side rejection instead:
error:0A00042E:SSL routines:ssl3_read_bytes:tlsv1 alert protocol version
alert protocol version means the server received your request and refused that version. That’s the answer you wanted.
Test Specific Ciphers
The option depends on the TLS version:
- TLS 1.3:
-ciphersuites - TLS 1.2 and earlier:
-cipher
Mixing them up is a common reason a cipher test “does nothing”.
Test one TLS 1.3 cipher suite:
echo | openssl s_client -connect www.howtouselinux.com:443 -tls1_3 \
-ciphersuites TLS_AES_128_GCM_SHA256 2>/dev/null | grep New
New, TLSv1.3, Cipher is TLS_AES_128_GCM_SHA256
Test one TLS 1.2 cipher:
echo | openssl s_client -connect www.howtouselinux.com:443 -tls1_2 \
-cipher ECDHE-RSA-AES128-GCM-SHA256 2>/dev/null | grep New
New, TLSv1.2, Cipher is ECDHE-RSA-AES128-GCM-SHA256
To offer several, separate them with colons:
-cipher 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256'
If the server doesn’t support any of the ciphers you offered, the handshake fails:
error:0A000410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure
Despite the name, sslv3 alert doesn’t mean SSLv3 was used. It’s just how OpenSSL labels TLS alert messages.
Who picks the cipher? The client sends a list and the server chooses. Many servers are configured to use their own preference order (ssl_prefer_server_ciphers on in nginx, SSLHonorCipherOrder on in Apache), so the order of your list doesn’t guarantee which one you get. To test whether a specific cipher is supported, offer only that one, as in the examples above.
To see which ciphers a string expands to on your system:
openssl ciphers -v 'ECDHE+AESGCM'
ECDHE-ECDSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(256) Mac=AEAD
ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(256) Mac=AEAD
ECDHE-ECDSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(128) Mac=AEAD
ECDHE-RSA-AES128-GCM-SHA256 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(128) Mac=AEAD
Verify the Certificate Properly
This is the part many people get wrong. By default, s_client completes the connection even if certificate verification fails. It prints the problem in Verify return code and carries on.
It also doesn’t check that the certificate matches the hostname unless you ask. A valid certificate for a completely different site would still show 0 (ok).
For a real verification test, use both options:
openssl s_client -connect www.howtouselinux.com:443 \
-verify_return_error \
-verify_hostname www.howtouselinux.com </dev/null
-verify_return_erroraborts the handshake if verification fails, like a browser would.-verify_hostnamechecks that the certificate is valid for that name.
You can see different failures using the public test site badssl.com:
openssl s_client -connect expired.badssl.com:443 -verify_return_error </dev/null
verify error:num=10:certificate has expired
...
error:0A000086:SSL routines:tls_post_process_server_certificate:certificate verify failed
openssl s_client -connect wrong.host.badssl.com:443 \
-verify_return_error -verify_hostname wrong.host.badssl.com </dev/null
verify error:num=62:hostname mismatch
openssl s_client -connect self-signed.badssl.com:443 </dev/null 2>/dev/null | grep "Verify return code"
Verify return code: 18 (self-signed certificate)
Verify against a private CA
Internal services often use certificates from a company CA that isn’t in the system trust store. Point s_client at the CA file:
openssl s_client -connect internal-api.example.local:443 \
-CAfile /etc/pki/ca-trust/source/anchors/company-root-ca.pem \
-verify_return_error </dev/null
If this returns 0 (ok) and your application still fails, the application is probably using a different trust store. Java’s cacerts is a common example.
Show the Full Certificate Chain
openssl s_client -connect www.howtouselinux.com:443 -showcerts </dev/null
Without -showcerts, only the server’s own certificate is printed in PEM format. With it, every certificate the server sent is printed.
Two things to keep in mind:
- The list shows what the server sent, in the order it sent it. It isn’t the chain OpenSSL built during verification.
- The root CA is usually not included, and that’s normal. Clients already have root certificates in their trust store.
This makes -showcerts the right tool for one of the most common TLS problems: a missing intermediate certificate. If the server sends only its own certificate, browsers often still work, because they cache or fetch intermediates. But curl, Java, Python, and many other clients fail with errors like unable to get local issuer certificate.
A quick way to count the certificates sent:
openssl s_client -connect www.howtouselinux.com:443 -showcerts </dev/null 2>/dev/null \
| grep -c "BEGIN CERTIFICATE"
2
One certificate (the leaf) plus one intermediate is typical for Let’s Encrypt. If this returns 1 for a certificate issued by an intermediate CA, the server configuration is missing its chain file.
Save the Certificate to a File
Save the server’s certificate:
openssl s_client -connect www.howtouselinux.com:443 </dev/null 2>/dev/null \
| openssl x509 -out howtouselinux.pem
Passing the output through openssl x509 extracts just the first certificate and drops everything else.
To save the whole chain the server sent, use -showcerts and pull out every PEM block:
openssl s_client -connect www.howtouselinux.com:443 -showcerts </dev/null 2>/dev/null \
| sed -n '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/p' > chain.pem
sed -n works with both GNU sed on Linux and BSD sed on macOS. The long form --quiet only works with GNU sed.
Inspect the Certificate Details
Pipe the certificate into openssl x509 to read specific fields. All of these use the same first half:
echo | openssl s_client -connect www.howtouselinux.com:443 2>/dev/null | openssl x509 -noout ...
Who issued it
echo | openssl s_client -connect www.howtouselinux.com:443 2>/dev/null | openssl x509 -noout -issuer
issuer=C = US, O = Let's Encrypt, CN = R11
Who it was issued to
echo | openssl s_client -connect www.howtouselinux.com:443 2>/dev/null | openssl x509 -noout -subject
subject=CN = www.howtouselinux.com
Older OpenSSL versions print these fields in slash format, such as issuer= /C=US/O=Let's Encrypt/CN=R3. The information is the same.
Which hostnames it covers
Modern clients ignore the subject CN and check the Subject Alternative Name list instead. This is the field to check when you see a hostname mismatch:
echo | openssl s_client -connect www.howtouselinux.com:443 2>/dev/null \
| openssl x509 -noout -ext subjectAltName
X509v3 Subject Alternative Name:
DNS:howtouselinux.com, DNS:www.howtouselinux.com
-ext requires OpenSSL 1.1.1 or later.
When it’s valid
echo | openssl s_client -connect www.howtouselinux.com:443 2>/dev/null | openssl x509 -noout -dates
notBefore=Aug 12 04:21:37 2026 GMT
notAfter=Nov 10 04:21:36 2026 GMT
Several fields at once
echo | openssl s_client -connect www.howtouselinux.com:443 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates -ext subjectAltName
The fingerprint
echo | openssl s_client -connect www.howtouselinux.com:443 2>/dev/null \
| openssl x509 -noout -fingerprint -sha256
sha256 Fingerprint=3A:1F:9C:...:7B:E2
Specify -sha256 explicitly. SHA-1 fingerprints are still printed by default in some versions, and many tools and certificate pinning configurations expect SHA-256.
Everything, decoded
echo | openssl s_client -connect www.howtouselinux.com:443 2>/dev/null | openssl x509 -noout -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
04:a1:7f:...
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = US, O = Let's Encrypt, CN = R11
Validity
Not Before: Aug 12 04:21:37 2026 GMT
Not After : Nov 10 04:21:36 2026 GMT
Subject: CN = www.howtouselinux.com
...
Check When a Certificate Expires
For a remote server:
echo | openssl s_client -connect www.howtouselinux.com:443 2>/dev/null \
| openssl x509 -noout -enddate
notAfter=Nov 10 04:21:36 2026 GMT
For a certificate file on disk:
openssl x509 -noout -enddate -in /etc/ssl/certs/server.crt
For monitoring, -checkend is more useful than reading dates. It takes a number of seconds and tells you whether the certificate will expire within that window:
echo | openssl s_client -connect www.howtouselinux.com:443 2>/dev/null \
| openssl x509 -noout -checkend 2592000
Certificate will not expire
2592000 seconds is 30 days. The exit code is 0 if the certificate is still valid past that point and 1 if it isn’t, so it’s easy to use in a script or cron job:
#!/bin/bash
host="www.howtouselinux.com"
if ! echo | openssl s_client -connect "$host:443" 2>/dev/null \
| openssl x509 -noout -checkend 2592000 >/dev/null; then
echo "WARNING: certificate for $host expires within 30 days"
fi
Test STARTTLS Services
Some protocols start as plain text and then upgrade to TLS. Mail servers on port 587, IMAP on 143, and LDAP on 389 all work this way. If you point a normal s_client at them, the handshake fails because the server expects a protocol command first.
-starttls sends the right upgrade command before starting TLS:
openssl s_client -connect smtp.gmail.com:587 -starttls smtp
CONNECTED(00000003)
...
250 SMTPUTF8
After the handshake, you’re in an encrypted SMTP session and can type commands such as EHLO test.
Other examples:
openssl s_client -connect mail.example.com:143 -starttls imap
openssl s_client -connect ldap.example.com:389 -starttls ldap
openssl s_client -connect db.example.com:5432 -starttls postgres
Supported values include smtp, pop3, imap, ftp, xmpp, xmpp-server, irc, postgres, mysql, lmtp, nntp, sieve, and ldap. Run openssl s_client -help to see what your version supports.
Services that use TLS from the first byte don’t need -starttls. Just connect to the right port:
openssl s_client -connect ldap.example.com:636 -showcerts # LDAPS
openssl s_client -connect mail.example.com:993 # IMAPS
openssl s_client -connect mail.example.com:465 # SMTPS
Test Client Certificate Authentication (mTLS)
Some APIs and internal services require the client to present a certificate too. This is called mutual TLS, or mTLS.
Pass the client certificate and its private key:
openssl s_client -connect api.example.com:443 \
-cert client.crt \
-key client.key
If the certificate and key are in the same PEM file, -cert alone is enough, because s_client looks for the key in the certificate file when -key isn’t given:
openssl s_client -connect api.example.com:443 -cert client-combined.pem
To check whether a server asks for a client certificate at all, connect without one and look for this section in the output:
Acceptable client certificate CA names
C = US, O = Example Corp, CN = Example Client CA
This tells you which CA your client certificate must be signed by. If you present a certificate from a different CA, the server rejects it.
With TLS 1.3, a server may complete the handshake and only reject the missing certificate when you send data. You might see an error like tlsv13 alert certificate required after the connection first looks successful.
Debug the Handshake Further
Show TLS extensions
openssl s_client -connect www.howtouselinux.com:443 -tlsextdebug </dev/null
TLS server extension "renegotiation info" (id=65281), len=1
TLS server extension "EC point formats" (id=11), len=4
TLS server extension "session ticket" (id=35), len=0
TLS server extension "extended master secret" (id=23), len=0
This shows which extensions the server returned during the handshake. It’s useful when a client and server disagree about features such as session tickets or ALPN.
Check OCSP stapling
openssl s_client -connect www.howtouselinux.com:443 -status </dev/null 2>/dev/null \
| grep -A 3 "OCSP response"
A server with OCSP stapling enabled returns:
OCSP Response Status: successful (0x0)
A server without it returns:
OCSP response: no response sent
Test HTTP/2 negotiation (ALPN)
openssl s_client -connect www.howtouselinux.com:443 -alpn h2,http/1.1 </dev/null 2>/dev/null \
| grep ALPN
ALPN protocol: h2
If you get No ALPN negotiated, the server didn’t agree to HTTP/2 over TLS.
Common Errors and What They Mean
| Error | What it usually means |
|---|---|
no protocols available | Your OpenSSL refused to offer the requested version. This is a client-side problem, often OpenSSL 3 blocking TLS 1.0/1.1. |
tlsv1 alert protocol version | The server doesn’t support the TLS version you requested. |
sslv3 alert handshake failure | No common cipher, or the server rejected the handshake for another reason. |
wrong version number | The port isn’t speaking TLS. It may be plain HTTP, or it may need -starttls. |
unable to get local issuer certificate | The server didn’t send its intermediate certificate, or the CA isn’t trusted. |
certificate has expired | Check the dates with openssl x509 -noout -dates. |
hostname mismatch | The name isn’t in the certificate’s Subject Alternative Name list. |
self-signed certificate | Use -CAfile if the certificate is from a private or internal CA. |
connect: Connection refused | Nothing is listening on that port. It isn’t a TLS problem. |
Quick Reference
# Connect and show everything
openssl s_client -connect host:443
# Short summary
openssl s_client -connect host:443 -brief </dev/null
# Test by IP with a specific hostname (SNI)
openssl s_client -connect 203.0.113.10:443 -servername host
# Force or exclude TLS versions
openssl s_client -connect host:443 -tls1_2
openssl s_client -connect host:443 -no_tls1_3
# Test ciphers
openssl s_client -connect host:443 -tls1_3 -ciphersuites TLS_AES_128_GCM_SHA256
openssl s_client -connect host:443 -tls1_2 -cipher ECDHE-RSA-AES128-GCM-SHA256
# Strict verification
openssl s_client -connect host:443 -verify_return_error -verify_hostname host
# Show and save the chain
openssl s_client -connect host:443 -showcerts </dev/null
# Certificate details
echo | openssl s_client -connect host:443 2>/dev/null | openssl x509 -noout -subject -issuer -dates -ext subjectAltName
# Expiring within 30 days?
echo | openssl s_client -connect host:443 2>/dev/null | openssl x509 -noout -checkend 2592000
# STARTTLS
openssl s_client -connect host:587 -starttls smtp
# Client certificate
openssl s_client -connect host:443 -cert client.crt -key client.key
Related
- Exploring SSL Certificate Chain with Examples
- Understanding X509 Certificate with Openssl Command
- OpenSSL Command to Generate View Check Certificate
- Converting CER CRT DER PEM PFX Certificate with Openssl
- SSL vs TLS and how to check TLS version in Linux
- Understanding SSH Key RSA DSA ECDSA ED25519
- Understanding server certificates with Examples




[…] This article covers more examples about how to use openssl s_client comannd. […]