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.19 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" "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{ errorLog: errorLog, infoLog: infoLog, 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, ErrorLog: errorLog, Handler: app.routes(), TLSConfig: tlsConfig, } infoLog.Printf("Starting server on %s", *addr) err = srv.ListenAndServeTLS("./tls/cert.pem", "./tls/key.pem") errorLog.Fatal(err) } ...
Additional information
TLS versions
TLS versions are also defined as constants in the crypto/tls
package, and Go’s HTTPS server supports TLS versions 1.0 to 1.3.
You can configure the minimum and maximum TLS versions via the tls.Config.MinVersion
and MaxVersion
fields. For instance, if you know that all computers in your user base support TLS 1.2, but not TLS 1.3, then you may wish to use a configuration like so:
tlsConfig := &tls.Config{ MinVersion: tls.VersionTLS12, MaxVersion: tls.VersionTLS12, }
Restricting cipher suites
The full range of cipher suites that Go supports are defined in the crypto/tls
package constants.
For some applications, it may be desirable to limit your HTTPS server to only support some of these cipher suites. For example, you might want to only support cipher suites which use ECDHE (forward secrecy) and not support weak cipher suites that use RC4, 3DES or CBC. You can do this via the tls.Config.CipherSuites
field like so:
tlsConfig := &tls.Config{ CipherSuites: []uint16{ tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_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, any CipherSuites
field in your tls.Config
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
to set a custom list of supported cipher suites will affect TLS 1.0-1.2 connections only.