By Evan Elias | February 6, 2025
New versions of the Go programming language are released every six months, and occasionally these releases tighten and modernize the standard library’s defaults for encryption behavior. Although these new defaults improve security, they can be problematic when connecting to older server software, such as MySQL 5.7, which is no longer being updated.
TLS support in older MySQL and MariaDB
MySQL and MariaDB have both supported encrypted connections for many years. Originally this was an optional feature which required manual configuration of certificates for the server, but a decade ago MySQL 5.7 added automatic self-signed certs by default upon server initialization. MariaDB followed suit with similar functionality many years later in MariaDB 11.4, their latest LTS series.
MySQL 5.7 is still widely used, even though it reached its end-of-life date back in late 2023. Since new patch releases of MySQL 5.7 have ceased, its TLS support is frozen in time, which can cause incompatibilities with modern software. In MySQL 5.7, cipher suites with elliptic curve key exchange are not usable, at least in official server builds from Oracle. (However, MySQL 5.7 builds from Percona contain a fix for this issue.)
Aside from cipher suites, TLS version mismatches can also be a problem spot. TLS 1.2 is supported in MySQL 5.7.28+, but prior to that point release in Oct 2019, the maximum supported TLS version was TLS 1.1. And going back further in time to MySQL 5.6 and earlier, the maximum was only TLS 1.0.
Meanwhile, MariaDB 10.2+ (GA in May 2017) fully supports TLS 1.2 and elliptic curve cipher suites. Prior to that, MariaDB 10.1 does support TLS 1.2, but is affected by the same cipher suite issue as MySQL 5.7.
Golang and TLS defaults
Over the years, some changes have been made to the default TLS client configurations in Golang to improve security. Developers can override these defaults, but it requires customizing the client-side TLS config.
Cipher Suites
Go 1.22, released one year ago in Feb 2024, adjusted the list of default cipher suites to only include ones with elliptic curve key exchange. So in order to make a secure connection to MySQL 5.7 or MariaDB 10.1 (or anything older), you must manually configure some RSA key exchange cipher suites back in place. Otherwise, if you attempt to establish an encrypted connection using a default Go 1.22+ TLS configuration, you’ll get a tls: handshake failure
error.
You can fix this problem by using a custom tls.Config
which restores the old Go 1.21 cipher suite list:
tlsConfig := &tls.Config{
CipherSuites: []uint16{
// These 10 use elliptic curve key exchange and are the only defaults in Go 1.22+
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
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_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
// These 4 use RSA key exchange and were removed from the Go 1.22+ default list
tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
},
}
Alternatively, when using a default TLS configuration, for now you can re-enable these four cipher suites by setting env variable GODEBUG='tlsrsakex=1'
in your client program’s environment. (Again, note carefully that this must be set in your program’s environment, not the Go compiler’s environment.) This solution works for programs built using the standard library for Go 1.22 and up. However, it is very likely to be removed from an upcoming Go release, possibly starting in Go 1.26 (Feb 2026), as per the general policy for GODEBUG
tweaks.
Minimum TLS version
Back in March 2022, Go 1.18 changed the client-side default minimum TLS version to TLS 1.2, instead of the previous minimum of TLS 1.0. This default breaks compatibility with MySQL 5.7.27 or older, including previous release series like MySQL 5.6.
To solve this problem, you can set the TLS config struct’s MinVersion
field to tls.VersionTLS10
, in addition to overriding the cipher suites as described in the previous section.
Using a custom TLS config with the Go MySQL driver
With the popular go-sql-driver/mysql driver, the most common way to specify a custom TLS configuration is by registering it for use in DSNs. By calling the mysql.RegisterTLSConfig
function, you can assign a unique name to your custom TLS config, and then refer to it in a DSN by setting the tls
parameter to that name.
Alternatively, if you prefer to avoid registration and DSNs entirely, you can call the mysql.NewConfig
constructor to get a mysql.Config
struct value, and then directly set its TLS
field. To open a connection using this value, you must use mysql.NewConnector
and sql.OpenDB
.
What about “preferred” mode / plaintext fallback?
The driver supports a “plaintext fallback” TLS mode called tls=preferred
, as well as a separate boolean parameter called allowFallbackToPlaintext=true
. These can be a bit confusing, and unfortunately they aren’t very useful in MySQL 5.7+.
These options both enable the same behavior: if the server is configured to allow TLS connections, use TLS; otherwise, use plaintext. Setting tls=preferred
is actually just a shortcut for setting tls=skip-verify&allowFallbackToPlaintext=true
. The motivation for offering a separate allowFallbackToPlaintext
parameter is to control this plaintext fallback behavior alongside a custom TLS configuration.
Unfortunately, this fallback behavior only covers the situation of a server that isn’t configured to allow TLS configurations at all. Plaintext fallback doesn’t occur if there’s a mismatch with cipher suites or TLS version, so it doesn’t solve any of the issues discussed above. Furthermore, since MySQL 5.7+ and MariaDB 11.4+ ship with TLS support using an auto-generated self-signed cert, plaintext fallback generally won’t ever be triggered with these versions.
For this reason, using the pre-baked tls=preferred
with an Oracle-supplied build of MySQL 5.7 will always result in a tls: handshake failure
error when using Go 1.22+. Enabling tls=preferred
can be slightly more useful with MariaDB though, since MariaDB introduced auto-generated self-signed certs much later than MySQL.
Recommendations for Go developers
Internal use-cases
If your software only ever connects to in-house databases, such as an internal MySQL 5.7 server, the solution is straightforward: simply register a custom tls.Config
which overrides the defaults mentioned above, and use this in your database connections.
Once you upgrade to MySQL 8.0+, you can remove the custom TLS configuration and return to using one of the driver’s pre-supplied configurations (tls=true
, tls=skip-verify
, or tls=preferred
), unless you need to customize some other aspect of the TLS configuration such as client-side certs.
Externally-distributed software
If you develop software with external users, and need to support a range of different MySQL or MariaDB versions, the situation can be more complex. For maximum compatibility with older servers, you can use a custom TLS config that includes RSA key exchange cipher suites and a lower minimum TLS version; however, this configuration is decidedly less secure. A better approach can be to make these TLS config fields controlled by options in your software, but that comes at the cost of more complexity.
In Skeema, to avoid adding extra configuration complexity, we’ve tied the cipher suite and TLS version logic to our existing flavor abstraction. The very first time a server is connected to by skeema init
or skeema add-environment
, the server’s version information is introspected and stored as the flavor
in a .skeema
configuration file. On that first connection, since we don’t know the server’s version yet, by default we use a permissive TLS configuration allowing older cipher suites, older TLS versions, and plaintext fallback in case the server isn’t configured to use TLS at all. Subsequent commands/connections will then use the configured flavor
to determine whether or not the default Go TLS configuration needs to be adjusted when connecting to an EOL server version.
However, since this approach is a form of “trust on first use,” it can be undesirable in security-conscious environments. So we also permit power-users to circumvent that behavior with stricter controls. For example, if you know your server is running MySQL 8.0 and want to force TLS with modern defaults even on the very first skeema init
connection, you can override the relevant options on the command-line: skeema init --flavor=mysql:8.0 --ssl-mode=required --host=...
. This example configuration will use the Go 1.22+ defaults (no RSA key exchange; minimum TLS 1.2+) while also disabling plaintext fallback.
Skeema includes extensive TLS support, even with ancient versions of MySQL and MariaDB.
To leave a comment, please visit the GitHub Discussions page for this post.