Configuring HTTPS settings
Go has good default settings for its HTTPS server, but it’s possible to optimize and customize how the server behaves.
One change, which is almost always a good idea to make, is to restrict the elliptic curves that can potentially be used during the TLS handshake. Go supports a few elliptic curves, but as of Go 1.23 only tls.CurveP256
and tls.X25519
have assembly implementations. The others are very CPU intensive, so omitting them helps ensure that our server will remain performant under heavy loads.
To make this tweak, we can create a tls.Config
struct containing our non-default TLS settings, and add it to our http.Server
struct before we start the server.
I’ll demonstrate:
package main import ( "crypto/tls" // New import "database/sql" "flag" "html/template" "log/slog" "net/http" "os" "time" "snippetbox.alexedwards.net/internal/models" "github.com/alexedwards/scs/mysqlstore" "github.com/alexedwards/scs/v2" "github.com/go-playground/form/v4" _ "github.com/go-sql-driver/mysql" ) ... func main() { ... app := &application{ logger: logger, snippets: &models.SnippetModel{DB: db}, templateCache: templateCache, formDecoder: formDecoder, sessionManager: sessionManager, } // Initialize a tls.Config struct to hold the non-default TLS settings we // want the server to use. In this case the only thing that we're changing // is the curve preferences value, so that only elliptic curves with // assembly implementations are used. tlsConfig := &tls.Config{ CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256}, } // Set the server's TLSConfig field to use the tlsConfig variable we just // created. srv := &http.Server{ Addr: *addr, Handler: app.routes(), ErrorLog: slog.NewLogLogger(logger.Handler(), slog.LevelError), TLSConfig: tlsConfig, } logger.Info("starting server", "addr", srv.Addr) err = srv.ListenAndServeTLS("./tls/cert.pem", "./tls/key.pem") logger.Error(err.Error()) os.Exit(1) } ...
Additional information
TLS versions
By default, Go’s HTTPS server is configured to support TLS 1.2 and 1.3. You can customize this and change the minimum and maximum TLS versions using the tls.Config.MinVersion
and MaxVersion
fields and TLS versions constants in the crypto/tls
package.
For example, if you want the server to support TLS versions 1.0 to 1.2 only, you can use a configuration like so:
tlsConfig := &tls.Config{ MinVersion: tls.VersionTLS10, MaxVersion: tls.VersionTLS12, }
Cipher suites
The cipher suites that Go supports are also defined in the crypto/tls
package constants.
However some of these cipher suites (specifically, the cipher suites that do not support Perfect Forward Secrecy, or which use RC4, 3DES or CBC_SHA256) are considered weak and are not used by Go’s HTTPS server by default. As of Go 1.23, the cipher suites that Go’s HTTPS server uses by default are:
TLS_AES_128_GCM_SHA256 // TLS 1.3 connections only TLS_AES_256_GCM_SHA384 // TLS 1.3 connections only TLS_CHACHA20_POLY1305_SHA256 // TLS 1.3 connections only TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 // TLS 1.2 connections only TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 // TLS 1.2 connections only TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 // TLS 1.2 connections only TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 // TLS 1.2 connections only TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 // TLS 1.2 connections only TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 // TLS 1.2 connections only TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA // TLS 1.0, 1.1 and 1.2 connections TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA // TLS 1.0, 1.1 and 1.2 connections TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA // TLS 1.0, 1.1 and 1.2 connections TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA // TLS 1.0, 1.1 and 1.2 connections
If you want to change the cipher suites used by Go’s HTTPS server — either by including weak cipher suites that aren’t used by default, or removing certain ciphers which are used by default — you can do this via the tls.Config.CipherSuites
field.
For example, if you want to use the default cipher list but with the CBC ciphers omitted, you can configure your tls.Config
like this:
tlsConfig := &tls.Config{ CipherSuites: []uint16{ tls.TLS_AES_128_GCM_SHA256, tls.TLS_AES_256_GCM_SHA384, tls.TLS_CHACHA20_POLY1305_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, }, }
Go will automatically choose which of these cipher suites is actually used at runtime based on the cipher security, performance, and client/server hardware support.
It’s also important (and interesting) to note that if a TLS 1.3 connection is negotiated, the tls.Config.CipherSuites
will be ignored. The reason for this is that all the cipher suites that Go supports for TLS 1.3 connections are considered to be safe, so there isn’t much point in providing a mechanism to configure them.
Basically, using tls.Config.CipherSuites
to set a custom list of supported cipher suites will affect TLS 1.0-1.2 connections only. So, in the above example, it isn’t actually necessary to include the TLS 1.3 specific ciphers and it can be simplified to this:
tlsConfig := &tls.Config{ CipherSuites: []uint16{ tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, }, }