LinuxXXII · Network TroubleshootingProbing
nc and openssl s_client - probing TCP and TLS
What you'll learn
- Use nc to test TCP and UDP connectivity
- Send and receive raw data with nc
- Use openssl s_client to probe TLS endpoints
- Verify chain and hostname explicitly, and read names from subjectAltName
- Diagnose application-protocol problems with these tools
Prerequisites
Verified against Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL 9.x · Rocky Linux 9.x · AlmaLinux 9.x · Linux kernel 6.1 LTS / 6.6 LTS · systemd 255+ · OpenSSH 8.7p1 (RHEL 9) / 9.6p1 (Ubuntu 24.04) · nftables 1.0.x · chrony 4.x · Pacemaker 2.1.x · Corosync 3.1.x · 2026-08-09
nc (netcat) and openssl s_client are the tools that answer
“can I reach this service, and if so, what does it say?”.
They probe specific ports without needing a full client.
nc basics
Modern Linux ships ncat (from the nmap project) rather than
the original netcat. The flags are largely compatible:
nc -vz host 80 # scan TCP port 80
nc -vz host 80 443 # scan multiple ports
nc -vzu host 53 # scan UDP port 53
nc -w 3 -vz host 80 # 3s timeout
nc -vz -w 3 host 80-90 # port range
nc host 80 # connect and read/write stdin/stdout
-v is verbose (output regardless of result). -z is
zero-I/O (just check if the port accepts connections). -u
is UDP.
Output:
Connection to host 80 port [tcp/http] succeeded!
Connection to host 81 port [tcp/81] failed: Connection refused
Connection to host 82 port [tcp/82] failed: Connection timed out
The three outcomes map to:
- succeeded: port is open, something is listening.
- Connection refused: target is up but nothing is listening on the port (target sent RST).
- Connection timed out: nothing replied at all (firewall drop, host down, network problem).
Send and receive
nc reads from stdin and writes to stdout once connected:
echo "GET / HTTP/1.0" | nc -w 5 example.com 80
Manually:
nc example.com 80
GET / HTTP/1.0
Host: example.com
HTTP/1.0 200 OK
...
q to exit (or Ctrl-D).
TCP and UDP servers
nc can also run as a listener:
nc -l -p 8080 # listen on TCP 8080
nc -u -l -p 5353 # listen on UDP 5353
nc -l -p 8080 > received.txt # capture incoming data
Useful for testing firewall rules (“can the remote host reach me on port 8080?”) and for ad-hoc data transfer.
TLS probing with openssl s_client
openssl s_client connects to a TLS endpoint and shows the
handshake details:
openssl s_client -connect example.com:443 # basic
openssl s_client -connect example.com:443 -servername example.com # SNI
openssl s_client -connect example.com:443 -showcerts # full cert chain
openssl s_client -connect example.com:443 -tls1_2 # force TLS 1.2
openssl s_client -connect example.com:443 -no_ign_eof # interactive
openssl s_client -connect example.com:443 </dev/null # one-shot, no input
Output:
CONNECTED(00000003)
---
Certificate chain
0 s:CN = example.com
i:CN = Example CA R3
...
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIfj...
-----END CERTIFICATE-----
---
No client certificate CA names sent
---
SSL handshake has read 12345 bytes and written 5678 bytes
Verification: OK
---
New, TLSv1/SSLv3, Cipher: TLS_AES_128_GCM_SHA256
...
What to look for:
- Certificate chain: every cert from server to root. If the chain is incomplete, the client cannot verify.
- Verification: OK: the chain was verified against the local trust store. Any other result (unable to get local issuer, self signed certificate) is a problem. It does not mean the hostname matched - see below.
- Cipher: the negotiated cipher. Check this matches your policy.
- Protocol: TLSv1.2 or TLSv1.3. Anything older is a security finding.
- Verify return code: 0 means OK. 21 means unable to verify (untrusted chain). 62 means hostname mismatch, and you will only ever see it if you asked for the check.
Test specific TLS features
# Verify the chain AND the hostname, and fail on a verify error
openssl s_client -connect example.com:443 -servername example.com \
-verify_hostname example.com -verify_return_error \
-CAfile /etc/ssl/certs/ca-certificates.crt </dev/null
# expect both "Verification: OK" and "Verify return code: 0 (ok)"
# Inspect the names the certificate is actually valid for
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates -ext subjectAltName
# Check the certificate expiry
echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -dates
# Check the negotiated cipher
openssl s_client -connect example.com:443 </dev/null 2>&1 | grep 'Cipher'
# Check the negotiated protocol
openssl s_client -connect example.com:443 </dev/null 2>&1 | grep 'Protocol'
# Verify with a specific CA bundle
openssl s_client -connect example.com:443 -CAfile /etc/ssl/certs/ca-certificates.crt </dev/null
Read the names from subjectAltName, not from the subject.
Browsers have required SAN since 2017 and ignore the CN
entirely, so a certificate whose CN looks right but whose SAN
list omits the name is rejected by every browser while
-subject shows exactly what you hoped to see. That is why
the inspection command above asks for -ext subjectAltName.
OpenSSL is more forgiving - -verify_hostname falls back to
the CN when a certificate carries no SAN at all - so a name
that passes s_client can still fail in a browser.
The verification result is worth spelling out:
# without the flags - trusted chain, wrong name, no complaint
Verification: OK
Verify return code: 0 (ok)
# with -verify_hostname and -verify_return_error
verify error:num=62:hostname mismatch
Verification error: hostname mismatch
Verify return code: 62 (hostname mismatch)
Same server, same certificate. Only the second command is a test.
STARTTLS for SMTP, IMAP, etc.
Some protocols encrypt on top of an existing plaintext connection:
openssl s_client -connect mail.example.com:587 -starttls smtp
openssl s_client -connect imap.example.com:143 -starttls imap
-starttls <protocol> does the protocol-specific STARTTLS
handshake after the TCP connection is open.
Diagnostic patterns
“The website is down”:
nc -vz example.com 80 # check TCP
nc -vz example.com 443 # check TLS
openssl s_client -connect example.com:443 </dev/null # TLS handshake
curl -I https://example.com # full request
“TLS handshake fails”:
openssl s_client -connect example.com:443 -servername example.com \
-verify_hostname example.com -verify_return_error </dev/null
# Look for: certificate verify failed, hostname mismatch, alert,
# handshake failure
Use the verifying form here. The bare form succeeds against a certificate issued for the wrong name, which is the failure you are most likely hunting.
“Service is listening but application fails”:
nc -vz host port # confirm port open
echo "PROTOCOL_COMMAND" | nc host port # send a probe
Knowledge check
Knowledge check · 5 questions
Q1. What does nc -vz return when nothing is listening on the port?
Q2. openssl s_client can speak HTTP.
Q3. Which of the following are valid openssl s_client uses? Select all that apply.
Q4. openssl s_client prints "Verification: OK" for a trusted certificate even when it was issued for a completely different hostname.
Q5. A rollout renames a service from api-old.example.com to api.example.com. Browsers now report a certificate error, but your check - `openssl s_client -connect api.example.com:443 -servername api.example.com` - prints "Verification: OK". What is the most likely explanation, and what should you run?
Passing score: 75%. Answers are checked in this browser.