The TLS handshake is failing because the client and server can’t agree on a cipher suite, or one of them is presenting an invalid certificate.

Common Causes and Fixes

1. Cipher Suite Mismatch:

  • Diagnosis: On the client side, run openssl s_client -connect example.com:443 -cipher ECDHE-RSA-AES128-GCM-SHA256. Observe the "Cipher" line in the output. On the server, check its TLS configuration (e.g., in Nginx, Apache, or a load balancer) for the ssl_ciphers directive.
  • Fix: Ensure the server’s ssl_ciphers list includes at least one cipher suite supported by the client. For example, in Nginx, you might change ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; to include a broader set like ssl_ciphers HIGH:!aNULL:!MD5;.
  • Why it works: This forces a common cryptographic algorithm that both client and server understand, allowing them to proceed with encryption.

2. Expired Server Certificate:

  • Diagnosis: Use openssl s_client -connect example.com:443 and check the "Not Before" and "Not After" dates in the certificate details.
  • Fix: Renew the server certificate with a new one that is within its validity period. For Let’s Encrypt, this might involve running certbot renew.
  • Why it works: Certificates have a defined lifespan; once expired, they are no longer trusted by clients, preventing secure connections.

3. Server Certificate Not Trusted by Client:

  • Diagnosis: On the client, run openssl s_client -connect example.com:443. Look for "verify error" messages, such as "unable to get local issuer certificate" or "self signed certificate in certificate chain."
  • Fix: Ensure the client’s trust store (e.g., /etc/ssl/certs/ca-certificates.crt on Debian/Ubuntu, or update-ca-certificates) contains the root certificate of the Certificate Authority (CA) that issued the server’s certificate. If it’s a self-signed certificate, you’ll need to explicitly add it to the client’s trust store or configure the client to trust it.
  • Why it works: The client needs to be able to trace the server’s certificate back to a trusted root CA to verify its authenticity.

4. Incorrectly Configured Intermediate Certificates:

  • Diagnosis: Use an online SSL checker tool (like SSL Labs) or openssl s_client -connect example.com:443 and examine the certificate chain presented by the server. Ensure all intermediate certificates are present and in the correct order.
  • Fix: On the server, configure the web server or load balancer to serve the full certificate chain. For Nginx, this means concatenating the server certificate and its intermediate certificates into a single file specified by ssl_certificate. For example, cat your_domain.crt intermediate.crt > bundle.crt and then ssl_certificate bundle.crt;.
  • Why it works: Clients need the entire chain to verify the server’s certificate up to a trusted root CA. Missing intermediates break this chain of trust.

5. Weak TLS Version Enabled:

  • Diagnosis: Use openssl s_client -connect example.com:443 and observe the "Protocol" line. If it shows TLSv1 or TLSv1.1, it might be too old for some clients.
  • Fix: Configure the server to disable older TLS versions and prioritize newer, more secure ones. In Nginx, this is done with ssl_protocols TLSv1.2 TLSv1.3;.
  • Why it works: Older TLS versions have known vulnerabilities and are deprecated by modern clients and security best practices.

6. SNI (Server Name Indication) Not Supported or Misconfigured:

  • Diagnosis: If a server hosts multiple SSL certificates on a single IP address, and the client doesn’t send the correct Host header during the TLS handshake, the server might return the wrong certificate or no certificate at all. This is often seen with openssl s_client -connect example.com:443 where the certificate presented doesn’t match example.com.
  • Fix: Ensure the client is configured to send the correct Host header or that the server is configured to handle requests without SNI gracefully (e.g., by providing a default certificate). For openssl s_client, you can explicitly specify the hostname: openssl s_client -connect example.com:443 -servername example.com.
  • Why it works: SNI allows a server to present different certificates based on the hostname the client is trying to reach, crucial when multiple domains share an IP.

The next error you’ll likely encounter is a "Client Hello" packet being sent but no "Server Hello" received, indicating a network-level issue like a firewall blocking the connection or an incorrect port configuration.


The most surprising thing about TLS decryption in Wireshark is that it’s not actually decryption in the traditional sense; Wireshark doesn’t "break" the encryption, it uses your pre-shared secret key to re-encrypt the traffic in a way it understands.

Let’s see it in action. Imagine you’re running a web server and you want to inspect traffic to example.com.

First, you need the session key. This is typically found in your browser’s logs or can be generated. For Firefox, you’d set environment variables:

export SSLKEYLOGFILE=/tmp/ssl-keys.log

Now, start your browser and navigate to https://example.com. The browser will initiate a TLS handshake, and the session key will be written to /tmp/ssl-keys.log.

Next, start Wireshark and capture traffic on your network interface. Filter for HTTP or TLS traffic: http || tls.

Once you have captured some traffic, you need to tell Wireshark about the session key. Go to Edit -> Preferences -> Protocols -> TLS. Under (Pre)-Master-Secret log filename, enter the path to your log file: /tmp/ssl-keys.log.

Now, if you look at the captured packets, you’ll see the TLS handshake messages. When Wireshark encounters an encrypted TLS record, it will attempt to decrypt it using the key from your log file. The unencrypted HTTP requests and responses will now be visible, for example, GET / HTTP/1.1 and the corresponding HTTP/1.1 200 OK response.

Here’s a snapshot of what you might see in Wireshark after decryption:

Frame 1: TLSv1.3 Application Data
    [Expert Info (Chat/Sequence): TLSv1.3 Application Data]
    Epoch: 1
    Record Layer: Handshake Protocol: Client Hello
        Content Type: Handshake (22)
        Version: TLS 1.0 (0x0301)
        Length: 299
        Handshake Protocol: Client Hello
            Type: Client Hello (1)
            Length: 295
            Client Version: TLS 1.3 (0x0304)
            Random: 5b0e9f3b...
            Session ID Length: 0
            Cipher Suites: ...
            Extensions: ...

Frame 10: TLSv1.3 Application Data
    [Expert Info (Chat/Sequence): TLSv1.3 Application Data]
    Epoch: 1
    Record Layer: Handshake Protocol: Server Hello
        Content Type: Handshake (22)
        Version: TLS 1.2 (0x0303)
        Length: 78
        Handshake Protocol: Server Hello
            Type: Server Hello (2)
            Length: 74
            Server Version: TLS 1.2 (0x0303)
            Random: 1a2b3c4d...
            Session ID: ...
            Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)
            Compression Method: null (0x00)
            Extensions: ...

Frame 15: TCP Segment of a reassembled PDU
    Frame 15: TCP Segment of a reassembled PDU
    ...
    Application Data: GET / HTTP/1.1
        GET / HTTP/1.1\r\n
        Host: example.com\r\n
        User-Agent: Mozilla/5.0 ...\r\n
        Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n
        Accept-Language: en-US,en;q=0.5\r\n
        Connection: keep-alive\r\n
        Upgrade-Insecure-Requests: 1\r\n
        \r\n

The system solves the problem of inspecting encrypted network traffic without requiring the server administrator to reconfigure the server for debugging or using man-in-the-middle proxies that can break certain security features. Wireshark, when configured with the session key, acts as a passive observer that can reconstruct the plaintext.

The exact levers you control are the capture interface, the capture filters, and the SSLKEYLOGFILE environment variable. The key is that the SSLKEYLOGFILE must be populated before the TLS connection is established and must contain the session master secret (or pre-master secret, depending on TLS version) for that specific session. If you restart your browser or establish a new connection, a new key will be generated and logged.

The subtle but critical point is that Wireshark needs the session key that was negotiated between the client and server for that specific connection. It doesn’t have a universal "master key" to decrypt all TLS traffic. This key is generated during the handshake and is used for symmetric encryption for the duration of the session. Without this specific key, Wireshark can show you the handshake, but the application data will remain an unreadable mess of encrypted bytes.

The next concept you’ll run into is analyzing the certificate chain and understanding how trust is established across different Certificate Authorities.

Want structured learning?

Take the full Tls-ssl course →