Error message
tls: first record does not look like a TLS handshake
gRPC, database drivers and other libraries wrap it in their own messages, so search logs for the core text. For HTTP replies net/http says "http: server gave HTTP response to HTTPS client" instead.
Go crypto/tls — the peer's first bytes are not a TLS record (tls.Dial, gRPC, database drivers, http.Client)
What it means
Go's TLS client sent a ClientHello and the first record it got back does not have the structure of a TLS handshake record, so it gives up immediately. The peer speaks something else on that port: plain HTTP (when Go cannot recognise it as such), a plaintext gRPC server, a database or mail protocol greeting. Go servers log the reverse situation, a client speaking plain text to a TLS port, differently.
Why it happens
TLS client against a plain-text port
commonA service port without TLS, often a sidecar or internal listener, dialed with tls.Dial or https://.
gRPC client with TLS credentials against a plaintext server
commonDevelopment servers frequently run without TLS while the client is built with TLS credentials (or vice versa, which fails on the server).
Implicit TLS against a STARTTLS protocol
occasionalSMTP on 587, PostgreSQL, MySQL, Redis with TLS on another port: the server greets in plain text first.
How to fix it
- 1.
Check what the port speaks
A TLS handshake with openssl, or a quick look at the banner, shows whether the port is TLS at all.
bash # Does the port speak TLS? openssl s_client -connect api.example.com:8080 -servername api.example.com -brief </dev/null timeout 3 nc api.example.com 8080 </dev/null | head -1 # a text banner = plain protocol - 2.
gRPC: use the credentials the server expects
TLS credentials for TLS servers; insecure credentials only for plaintext servers on trusted networks.
go // gRPC: match the server. Plaintext server -> insecure credentials (only on trusted networks); // TLS server -> TLS credentials. conn, err := grpc.NewClient("api.example.com:9090", grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{ServerName: "api.example.com"}))) - 3.
Use STARTTLS where the protocol requires it
net/smtp's StartTLS (and the equivalents in database drivers, e.g. sslmode for PostgreSQL) upgrade the connection in-band.
go // SMTP on 587 starts in plain text: use STARTTLS, not tls.Dial c, err := smtp.Dial("mail.example.com:587") if err == nil { err = c.StartTLS(&tls.Config{ServerName: "mail.example.com"}) }