skip to content

TLS Configuration

Both ends of a tls.Config: what the zero value already verifies, how RootCAs and ServerName change it, and what ClientCAs plus ClientAuth add. Interviewers ask which field you touched and why.

part ofGo (Golang)overview, primer and where to startread it →
on this pageshow

explore

questions

18

How do you serve HTTPS from a Go net/http server, and where does a tls.Config go?

level: juniorimportance: must knowfreq 58%

answer

  1. two file paths and one method call
  2. the convenience function takes no options
  3. options live in a struct field, not an argument
  4. build the server yourself to reach it
  5. http.Server.TLSConfig plus ListenAndServeTLS

basics

~20 s

Call ListenAndServeTLS with a certificate file and a key file. The package-level http.ListenAndServeTLS takes no tls.Config, so to set TLS options you build an http.Server, fill its TLSConfig field, and call that server's ListenAndServeTLS method.

solid answer

~40 s

The one-liner is `http.ListenAndServeTLS(addr, certFile, keyFile, handler)`: it serves HTTPS using a PEM certificate chain and its private key. It gives you nowhere to put TLS options, so real services build the server explicitly — `srv := &http.Server{Addr: ":8443", Handler: mux, TLSConfig: &tls.Config{MinVersion: tls.VersionTLS12}}` — and then call `srv.ListenAndServeTLS("cert.pem", "key.pem")`. The certificate file holds the server's own leaf certificate first, followed by any intermediate certificates the peer needs to chain to a trusted root; the key file holds the matching private key. If you already loaded the pair yourself with `tls.LoadX509KeyPair` into `TLSConfig.Certificates`, or set a `GetCertificate` callback that picks a certificate per requested server name, you pass two empty strings instead of filenames. `srv.ServeTLS(l, cert, key)` is the same thing over a listener you created.

code

go · 9 lines
go
srv := &http.Server{
	Addr:    ":8443",
	Handler: mux,
	TLSConfig: &tls.Config{
		MinVersion: tls.VersionTLS12,
	},
}
// cert.pem holds the leaf certificate first, then any intermediates
log.Fatal(srv.ListenAndServeTLS("cert.pem", "key.pem"))

go deeper

for a junior

Be ready to write the four-line version from memory: build an http.Server, set Addr, Handler and TLSConfig, call ListenAndServeTLS with the certificate and key paths. Know which argument is which file.

for a middle

Explain why the package-level helper is a dead end for configuration, what the clone at startup means for late edits, and when empty filenames are legal because Certificates or GetCertificate already supplies the certificate.

for a senior

Show that you check the shipped chain, not just that the handshake works locally. Missing intermediates, a certificate loaded from a secret store, and reloading on rotation are the operational parts interviewers listen for.

for a principal

Frame it as what the service template hands every team by default: which fields are pre-filled, which are configurable, and how certificates reach the process at all so that no team has to invent their own answer.

## The two ways to start an HTTPS listener Go's `net/http` gives you a package-level convenience function and a method on `http.Server`, and the difference between them is the whole point of this question. ```go // convenience: no place to configure TLS http.ListenAndServeTLS(":8443", "cert.pem", "key.pem", mux) ``` That call creates an `http.Server` internally, and you never get a reference to it. It is fine for a demo. It is not fine for a service that has to state a minimum protocol version, because the only place TLS options live is the server's `TLSConfig` field. ```go srv := &http.Server{ Addr: ":8443", Handler: mux, TLSConfig: &tls.Config{ MinVersion: tls.VersionTLS12, }, } log.Fatal(srv.ListenAndServeTLS("cert.pem", "key.pem")) ``` `TLSConfig` is a `*tls.Config` from `crypto/tls`. Everything TLS-related on the serving side is a field on that struct — the version floor, the cipher-suite list for older protocol versions, the certificates, the callbacks. `http.Server` itself has no TLS fields beyond that one pointer. ## What the two files are `certFile` is a PEM file containing the server's certificate. If the certificate was issued by an intermediate CA rather than directly by a root, the intermediates must be concatenated into the same file, leaf first, in issuing order. Go's own TLS client does not go and fetch missing intermediates, and neither do many other clients, so a chain that verifies in your browser can still fail from a Go program if you shipped only the leaf. `keyFile` is the PEM private key that matches the leaf certificate. If the key is in the same file as the certificate, you may pass the same path for both arguments. ## Loading the certificate yourself Sometimes the files are not files: the certificate comes from a secret store, or you need one certificate per hostname. Then you fill the config instead: ```go cert, err := tls.LoadX509KeyPair("cert.pem", "key.pem") if err != nil { return err } srv.TLSConfig = &tls.Config{ MinVersion: tls.VersionTLS12, Certificates: []tls.Certificate{cert}, } return srv.ListenAndServeTLS("", "") ``` Empty filenames are legal precisely when the config can already produce a certificate — `Certificates` is non-empty, or `GetCertificate` is set. `GetCertificate` is a callback that receives the client's hello and returns the certificate to present, which is how one listener serves several hostnames. ## Serving over a listener you own `ListenAndServeTLS` creates the TCP listener for you from `srv.Addr`. When you need the listener first — to bind port 0 and discover the port, to serve on a Unix socket, to hand the listener in from a supervisor — use `srv.ServeTLS(l, certFile, keyFile)` with an already-created `net.Listener`. The certificate arguments behave identically. ## Details worth knowing early `net/http` does not use your `*tls.Config` object directly: when the server starts it takes a clone and adds the ALPN protocol names it supports to `NextProtos`, which is how HTTP/2 gets negotiated. Two consequences follow. First, mutating `srv.TLSConfig` after the server has started changes nothing for that listener — set it before you start. Second, if you set `NextProtos` yourself, you are overriding what the server would have advertised, so you can accidentally turn HTTP/2 off. A `*tls.Config` is meant to be shared and read concurrently by every connection. Treat it as immutable once the server is running; if you need a variant, use its `Clone` method rather than editing the live one. Finally, an HTTPS server is an ordinary `http.Server` — the same handlers, the same mux, the same everything. Serving HTTPS is a matter of which start method you call and what you put in one struct field, not of a different server type. Inside a handler, `r.TLS` is a `*tls.ConnectionState` (nil for a plaintext request), which is how a handler learns what was negotiated for the request it is serving.

  • When may you pass empty strings for the certificate and key filenames?
    When the server's `TLSConfig` can already produce a certificate on its own: `Certificates` is non-empty, or `GetCertificate` is set. That is the normal path when the key pair comes from a secret store rather than from disk, or when one listener serves several hostnames and picks the certificate per client hello.
  • What besides the server's own certificate belongs in the certificate PEM file?
    Any intermediate CA certificates needed to chain the leaf to a trusted root, concatenated after the leaf in issuing order. Go's TLS client does not fetch missing intermediates, so a leaf-only file produces verification failures for callers even though the same certificate looks fine in a browser that cached the intermediate.
  • Why does editing srv.TLSConfig after the server is already serving have no effect?
    `net/http` clones the config when the listener starts — partly so it can append its own ALPN protocol names for HTTP/2 — and the running listener uses that clone. Set everything before you start. It is also the safe rule generally: a `*tls.Config` is read concurrently by every connection, so mutating a live one is a data race.

The package-level helper is a pre-locked door: it works, but you never hold the lock. Building the http.Server yourself is what gives you the lock to specify.

saying these in an interview costs you the question

  • Thinks http.ListenAndServeTLS accepts a tls.Config argument
  • Ships a PEM file with the leaf certificate and no intermediates
  • Believes assigning TLSConfig after startup reconfigures the listener
  • Confuses the key file with the certificate chain file
  • Thinks HTTPS needs a different server type than http.Server
open as a page

Which tls.Config fields make a Go TLS server require and verify a client certificate?

level: juniorimportance: must knowfreq 42%

basics

~10 s

Two fields on the server's tls.Config: ClientCAs, an x509.CertPool of the CAs you trust to issue client certificates, and ClientAuth set to tls.RequireAndVerifyClientCert. ClientCAs alone asks for nothing and verifies nothing.

open as a page

What does tls.Config.InsecureSkipVerify = true actually turn off in a Go TLS client?

level: juniorimportance: must knowfreq 58%

basics

~20 s

It turns off both checks a Go TLS client makes on the peer's certificate: the chain is no longer verified against any trust root, and the name in the certificate is no longer matched. Any certificate from anyone is accepted.

open as a page

Since Go 1.24, which key exchange does crypto/tls prefer by default, and why is it a hybrid?

level: juniorimportance: should knowfreq 30%

basics

~10 s

Go 1.24 made X25519MLKEM768 the preferred TLS 1.3 key exchange whenever tls.Config.CurvePreferences is left nil. It is hybrid: classical X25519 runs alongside post-quantum ML-KEM-768, so the session key holds if either half survives.

open as a page

What does tls.Config.MinVersion control, and what applies when you leave it at zero?

level: middleimportance: should knowfreq 50%

basics

~20 s

tls.Config.MinVersion is the oldest TLS protocol version Go will negotiate; older peers are refused during the handshake. Zero means the crypto/tls package default, TLS 1.2 in current Go. Set it explicitly, because that default has been raised across releases.

open as a page

How do you set TLS options for outbound HTTPS calls from a Go http.Client?

level: middleimportance: should knowfreq 44%

basics

~20 s

Give the client its own http.Transport and set that transport's TLSClientConfig field to a *tls.Config. http.Client has no TLS settings of its own, and editing http.DefaultTransport changes TLS behaviour for every caller in the process.

open as a page

What happens to Go's post-quantum TLS default when a service sets tls.Config.CurvePreferences explicitly?

level: middleimportance: should knowfreq 24%

basics

~20 s

CurvePreferences is an allow-list, not an addition. A non-nil list negotiates only the groups it names, in that order, so any list written before Go 1.24 silently removes X25519MLKEM768. Include tls.X25519MLKEM768 explicitly, or leave the field nil.

open as a page

How do you read the client certificate identity from r.TLS inside a Go HTTP handler?

level: middleimportance: should knowfreq 36%

basics

~20 s

In a handler, r.TLS is a *tls.ConnectionState and is nil on plaintext connections. The client's own certificate is r.TLS.PeerCertificates[0], and r.TLS.VerifiedChains is non-empty only when the server actually verified it. Take the name from the certificate's SANs.

open as a page

Which tls.Config fields replace InsecureSkipVerify when a Go client must trust a private CA?

level: middleimportance: should knowfreq 44%

basics

~20 s

Two fields do the job: RootCAs, the pool of certificate authorities the chain must reach, and ServerName, the name the certificate must carry. Set both and verification succeeds against a private CA with the check still on.

open as a page

Your Go server sets tls.Config.CipherSuites, yet clients negotiate a suite you excluded. Why?

level: seniorimportance: should knowfreq 33%

basics

~10 s

tls.Config.CipherSuites applies only to TLS 1.0 through 1.2. Go does not let you configure TLS 1.3 cipher suites, so a TLS 1.3 handshake ignores the list entirely. Constrain the protocol version instead.

open as a page

After a Go 1.24 upgrade, handshakes to one partner endpoint hang. How do you confirm the post-quantum group is the cause?

level: seniorimportance: should knowfreq 18%

basics

~20 s

Dial the same endpoint twice, once with Go's defaults and once with CurvePreferences pinned to classical curves. If only the classical dial completes, the larger X25519MLKEM768 ClientHello is the trigger. Fix it per destination, not fleet-wide.

open as a page

Why do requests with no client certificate succeed on a Go TLS server that checks r.TLS.PeerCertificates?

level: seniorimportance: should knowfreq 31%

basics

~20 s

Almost always because ClientAuth is not RequireAndVerifyClientCert. Under NoClientCert, RequestClientCert or VerifyClientCertIfGiven the handshake completes with no certificate, the peer chain is empty, and a guard written as if there is a certificate then check it never runs.

open as a page

After a base-image change, a Go log shipper's tls.Dial fails with 'x509: certificate signed by unknown authority'. What is the real fix, and how do you keep the skip flag out of the repo?

level: seniorimportance: should knowfreq 38%

basics

~20 s

That error means no trust root reaches the collector's certificate, usually because the new image ships no CA bundle. Supply the roots, then guard it with a test that requires a mismatched name to fail.

open as a page

Should a fleet force, allow, or exclude Go's X25519MLKEM768 key exchange, and what does each choice commit you to?

level: principalimportance: should knowfreq 14%

basics

~20 s

Allow it everywhere by leaving tls.Config.CurvePreferences nil; force it only where you own both ends; exclude it only as a scoped, expiring exception. Forcing commits every peer to a post-quantum-capable toolchain — a cross-team promise, not a config change.

open as a page

How do you use Go's crypto/mlkem package to agree a shared secret outside TLS?

level: middleimportance: nice to knowfreq 12%

basics

~20 s

The receiver calls mlkem.GenerateKey768 and publishes dk.EncapsulationKey().Bytes(). The sender parses it with mlkem.NewEncapsulationKey768 and calls Encapsulate, which returns a 32-byte shared key and a ciphertext. Sending only that ciphertext lets the receiver Decapsulate the same 32 bytes.

open as a page

When should a Go TLS client set GetClientCertificate instead of tls.Config.Certificates?

level: middleimportance: nice to knowfreq 22%

basics

~20 s

Set GetClientCertificate when the certificate can change or depends on who is asking. It is a callback run at every handshake where the server requests a certificate, it receives the server's request details, and it takes precedence: Certificates is ignored once it is set.

open as a page

When does a tls.Config.VerifyPeerCertificate callback run in Go, and what is it handed?

level: seniorimportance: nice to knowfreq 26%

basics

~20 s

It runs after the standard certificate verification, on both clients and servers, receiving the peer's raw DER certificates and any chains that verification built. Returning an error aborts the handshake. It adds rules; it does not replace the built-in ones.

open as a page

You own the Go service template's TLS baseline: how do you choose the MinVersion every generated service ships with, and handle the partner that cannot meet it?

level: principalimportance: nice to knowfreq 24%

basics

~20 s

Ship the strictest floor your real clients meet, measured rather than assumed: usually TLS 1.3 between internal services, TLS 1.2 on a public listener. Make it a configurable default, not a constant, and give every exception an owner and an expiry.

open as a page