The Interaction Between `allowFallbackToPlaintext` and `tls=preferred` in go-sql-driver/mysql
When using tls=preferred in a MySQL DSN, the driver automatically enables allowFallbackToPlaintext and sets InsecureSkipVerify: true, allowing the connection to fall back to plaintext if the server does not support TLS.
The go-sql-driver/mysql repository implements nuanced TLS handling that balances security with backward compatibility. Understanding the interaction between allowFallbackToPlaintext and tls=preferred security settings helps developers configure connections that gracefully degrade when encryption is unavailable while avoiding accidental insecure configurations.
How tls=preferred Configures Automatic Fallback
In dsn.go, the driver parses the tls DSN parameter and handles the preferred case by creating a permissive TLS configuration and simultaneously enabling the fallback flag:
// dsn.go – TLS handling (lines 197-207)
case "preferred":
cfg.TLS = &tls.Config{InsecureSkipVerify: true}
cfg.AllowFallbackToPlaintext = true // automatically enabled
This means tls=preferred serves as a convenience shorthand that performs two distinct actions:
- Creates a
tls.ConfigwithInsecureSkipVerify: true, which skips certificate chain and host name verification. - Sets the internal
AllowFallbackToPlaintextfield totrue, granting permission to abandon TLS if the server lacks support.
The Handshake Fallback Mechanism
The actual fallback decision occurs during the connection handshake in packets.go. When the server responds with its capabilities, the driver checks for the clientSSL flag:
// packets.go – handshake processing (lines 219-226)
if capabilities&clientSSL == 0 && mc.cfg.TLS != nil {
if mc.cfg.AllowFallbackToPlaintext {
// Server does not support SSL, drop TLS and continue in plain text
mc.cfg.TLS = nil
} else {
return nil, capabilities, 0, "", ErrNoTLS
}
}
If the server does not advertise clientSSL and the driver has a TLS configuration, the AllowFallbackToPlaintext flag determines the outcome. When enabled, the driver sets mc.cfg.TLS = nil and proceeds with an unencrypted connection. When disabled, the driver returns ErrNoTLS and terminates the connection attempt.
Configuration Scenarios and Behavior Matrix
Understanding the distinct roles of these settings clarifies their interaction:
-
tls=preferredalone: The driver attempts TLS with certificate verification disabled. If the server lacks SSL support, the connection automatically falls back to plaintext becauseAllowFallbackToPlaintextis internally forced totrue. -
allowFallbackToPlaintext=truealone: This flag has no effect unless you also specify a TLS mode such astls=true,tls=skip-verify, or a custom TLS config name. The flag only controls fallback behavior when TLS is configured but unavailable. -
Both settings together: If you explicitly set
allowFallbackToPlaintext=falsewhile usingtls=preferred, the driver overwrites your explicit setting totrueduring DSN parsing. Thetls=preferredbranch takes precedence, making the explicitfalseineffective.
DSN Parameter Validation
The driver validates the allowFallbackToPlaintext parameter type in dsn.go using the readBool helper function from utils.go (around line 58):
// dsn.go – parameter parsing (lines 504-511)
case "allowFallbackToPlaintext":
var isBool bool
cfg.AllowFallbackToPlaintext, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
Any non-boolean value results in an immediate parsing error, ensuring type safety for this critical security flag. Test cases in dsn_test.go (lines 45-47 and 115-116) verify this validation logic.
Practical Code Examples
The following examples demonstrate practical DSN configurations and their security implications:
// Example 1: Preferred TLS with automatic fallback
db, err := sql.Open(
"mysql",
"user:pw@tcp(localhost:3306)/testdb?tls=preferred",
)
// TLS is attempted; if the server lacks SSL, connection proceeds in plaintext.
// Example 2: Explicit TLS with manual fallback control
db, err := sql.Open(
"mysql",
"user:pw@tcp(localhost:3306)/testdb?tls=true&allowFallbackToPlaintext=true",
)
// TLS is required; if the server does not support it, the driver falls back.
// Example 3: TLS required, no fallback allowed
db, err := sql.Open(
"mysql",
"user:pw@tcp(localhost:3306)/testdb?tls=skip-verify&allowFallbackToPlaintext=false",
)
// Connection fails with ErrNoTLS if the server does not advertise SSL.
Summary
tls=preferredautomatically enablesAllowFallbackToPlaintextand setsInsecureSkipVerify: trueaccording to the implementation indsn.go.- The fallback logic in
packets.gochecks theclientSSLcapability bit and only proceeds with plaintext ifAllowFallbackToPlaintextistrue. - Setting
allowFallbackToPlaintext=falseexplicitly in the DSN has no effect when combined withtls=preferredbecause the preferred branch forces the flag totrue. - To require TLS without fallback, use
tls=trueortls=skip-verifywithallowFallbackToPlaintext=false, nottls=preferred. - The driver validates that
allowFallbackToPlaintextcontains a valid boolean value during DSN parsing indsn.go.
Frequently Asked Questions
Does tls=preferred automatically enable allowFallbackToPlaintext?
Yes. According to the source code in dsn.go (lines 197-207), the tls=preferred case explicitly sets cfg.AllowFallbackToPlaintext = true in addition to creating a TLS configuration with InsecureSkipVerify: true. You cannot disable fallback while using tls=preferred.
What happens if I set allowFallbackToPlaintext=false with tls=preferred?
The driver ignores the explicit false value. During DSN parsing, the tls=preferred branch executes after general parameter parsing and overwrites the AllowFallbackToPlaintext field to true. To prevent fallback, use tls=true or tls=skip-verify instead of tls=preferred.
How does the driver handle servers without SSL support?
When the server does not advertise the clientSSL capability during handshake (as checked in packets.go lines 219-226), the driver examines the AllowFallbackToPlaintext flag. If enabled, it sets mc.cfg.TLS = nil and continues with an unencrypted connection. If disabled, it returns ErrNoTLS and terminates the connection.
Is tls=preferred secure for production environments?
No. The tls=preferred setting disables certificate verification via InsecureSkipVerify: true and permits silent fallback to unencrypted connections. For production systems, use tls=true with proper certificate verification or tls=custom with a configured tls.Config that validates server certificates, combined with allowFallbackToPlaintext=false to ensure encryption is mandatory.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →