I’m trying to manuplate my traffic with caddy. But I found it doesn’t do what I want. According to the doc the connection to the upstream doesn’t send the SNI unless I set one but it seems it always sends one. I checked it with https://clienttest.ssllabs.com:8443/ssltest/viewMyClient.html and it says that the SNI is enabled.
You’re right, the documentation is wrong/misleading.
I dug through the code, and I think we misunderstood how the Go stdlib actually uses ServerName (i.e. tls_server_name in Caddy’s config).
So this doesn’t configure SNI at all (SNI is always sent), what it does is it controls what server name that the TLS handshake logic uses for validating that the certificate served by the upstream matches the requested hostname. By default, this will be the configured upstream dial address’ host part. Configuring tls_server_name will override that default.
Essentially the only situation where it’s useful is when you configure the dial address with something like an IP address, but your upstream is an HTTPS server which is serving an actual domain name. So say you configure Caddy with an upstream https://172.20.0.1 (because the server is in your private network) but the upstream server wants to serve a certificate with foo.example.com in its SAN, then you would need to configure tls_server_name to foo.example.com for the handshake to actually succeed (otherwise it would try to look for 172.20.0.1 in the cert, but wouldn’t find it).