OpenSSL Cheatsheet
s_client and s_server
Use this OpenSSL reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
Connect to a TLS service
s_client is a raw TLS client. It prints the handshake, the chain, and then
hands you the socket.
openssl s_client -connect example.com:443 -servername example.comRedirect stdin from /dev/null so it exits instead of waiting for you to type:
openssl s_client -connect example.com:443 -servername example.com </dev/null openssl s_client -connect example.com:443 -servername example.com \ -brief </dev/null
-servername is not implied by -connect. Without SNI a shared host
returns its default certificate, which is why you sometimes see a completely
unrelated domain in the output.
Reading the output
| Line | Means |
|---|---|
Certificate chain | What the server sent, 0 s: is the leaf |
Verify return code: 0 (ok) | Chain validated against your trust store |
Verify return code: 21 | unable to verify the first certificate, chain incomplete |
Verify return code: 20 | unable to get local issuer certificate |
Verify return code: 10 | Certificate expired |
Protocol : TLSv1.3 | Negotiated version |
Cipher : TLS_AES_256_GCM_SHA384 | Negotiated suite |
Server Temp Key: X25519, 253 bits | Key exchange group |
Peer signing digest / Peer signature type | Cert key type in use |
No client certificate CA names sent | Server is not asking for mTLS |
Early data was not sent | No TLS 1.3 0-RTT |
# just the negotiated parameters openssl s_client -connect example.com:443 -servername example.com \ </dev/null 2>/dev/null | grep -E 'Protocol|Cipher|Verify' # every certificate the server sent, PEM included openssl s_client -connect example.com:443 -servername example.com \ -showcerts </dev/null 2>/dev/null > chain.txt
Extract the leaf and inspect it in one pipe:
openssl s_client -connect example.com:443 -servername example.com \ </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates -ext subjectAltName
Pin a version, group, or cipher
This is how you prove a server still speaks something it should not, or does not speak something it should.
openssl s_client -connect example.com:443 -servername example.com -tls1_3 openssl s_client -connect example.com:443 -servername example.com -tls1_2 openssl s_client -connect example.com:443 -servername example.com -no_tls1_3 # TLS 1.2 and below take -cipher, TLS 1.3 takes -ciphersuites openssl s_client -connect example.com:443 -tls1_2 -cipher 'ECDHE-RSA-AES128-GCM-SHA256' openssl s_client -connect example.com:443 -tls1_3 \ -ciphersuites 'TLS_CHACHA20_POLY1305_SHA256' openssl s_client -connect example.com:443 -groups X25519 openssl s_client -connect example.com:443 -alpn h2 -servername example.com openssl s_client -connect example.com:443 -status -servername example.com
A rejected version comes back as a handshake alert, not a clean message:
40C7A1B901000000:error:0A000410:SSL routines:ssl3_read_bytes:
ssl/tls alert handshake failure:...:SSL alert number 40OpenSSL 3.x builds usually disable TLS 1.0 and 1.1 outright. -tls1 then
fails locally even when the server supports it. Add
-cipher 'DEFAULT@SECLEVEL=0' or test with a distro build that still allows it.
STARTTLS
Mail and database protocols start in plaintext and upgrade. -starttls runs
the right upgrade command for you.
| Value | Usual port |
|---|---|
smtp | 25, 587 |
lmtp | 24 |
pop3 | 110 |
imap | 143 |
ftp | 21 |
xmpp, xmpp-server | 5222, 5269 |
ldap | 389 |
mysql | 3306 |
postgres | 5432 |
nntp, sieve, irc, telnet | 119, 4190, 194, 23 |
openssl s_client -starttls smtp -connect mail.example.com:587 \ -servername mail.example.com openssl s_client -starttls imap -connect mail.example.com:143 openssl s_client -starttls postgres -connect db.example.com:5432
Ports 465 (SMTPS), 993 (IMAPS), and 995 (POP3S) are TLS from the first byte, so
they take a plain -connect with no -starttls.
Throwaway server with s_server
s_server gives you a TLS endpoint to point a client at, with no nginx and no
config file.
# defaults to *:4433 openssl s_server -cert cert.pem -key key.pem -accept 4433 # answer GET / with a handshake status page, handy in a browser openssl s_server -cert cert.pem -key key.pem -accept 4433 -www # serve files out of the current directory openssl s_server -cert cert.pem -key key.pem -accept 4433 -WWW # one connection then exit, quiet, TLS 1.3 only openssl s_server -cert cert.pem -key key.pem -accept 4433 \ -naccept 1 -quiet -tls1_3
Require a client certificate, which is the fastest way to test mTLS:
openssl s_server -cert server.crt -key server.key \ -CAfile ca.crt -Verify 1 -accept 4433 openssl s_client -connect localhost:4433 \ -cert client.crt -key client.key -CAfile ca.crt
-Verify (capital V) fails the handshake without a valid client cert.
-verify (lowercase) asks for one but continues anyway.
Gotchas
- s_client hangs after the handshake. It is holding the socket open for
you to type. </dev/null or -no_ign_eof ends it.
- -servername and -connect are independent. Setting only -connect
IP:443 sends no SNI and often returns the wrong certificate.
- HTTP over s_client needs CRLF. Typing a request by hand usually fails
because your terminal sends bare LF. Use
printf 'GET / HTTP/1.1\r\nHost: example.com\r\n\r\n' | openssl s_client ....
- Verify return code: 21 on a working browser means the server omitted an
intermediate and the browser is caching it. Fix the server's chain file.
- Fetch every certificate individually from -showcerts output with
awk '/BEGIN CERT/,/END CERT/', since a plain x509 pipe reads only the
first block.
- s_server reads server.pem by default and will not tell you clearly that
it did. Always pass -cert and -key.