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:

  1. Creates a tls.Config with InsecureSkipVerify: true, which skips certificate chain and host name verification.
  2. Sets the internal AllowFallbackToPlaintext field to true, 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=preferred alone: The driver attempts TLS with certificate verification disabled. If the server lacks SSL support, the connection automatically falls back to plaintext because AllowFallbackToPlaintext is internally forced to true.

  • allowFallbackToPlaintext=true alone: This flag has no effect unless you also specify a TLS mode such as tls=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=false while using tls=preferred, the driver overwrites your explicit setting to true during DSN parsing. The tls=preferred branch takes precedence, making the explicit false ineffective.

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=preferred automatically enables AllowFallbackToPlaintext and sets InsecureSkipVerify: true according to the implementation in dsn.go.
  • The fallback logic in packets.go checks the clientSSL capability bit and only proceeds with plaintext if AllowFallbackToPlaintext is true.
  • Setting allowFallbackToPlaintext=false explicitly in the DSN has no effect when combined with tls=preferred because the preferred branch forces the flag to true.
  • To require TLS without fallback, use tls=true or tls=skip-verify with allowFallbackToPlaintext=false, not tls=preferred.
  • The driver validates that allowFallbackToPlaintext contains a valid boolean value during DSN parsing in dsn.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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →