# The Interaction Between `allowFallbackToPlaintext` and `tls=preferred` in go-sql-driver/mysql

> Understand how allowFallbackToPlaintext interacts with tls=preferred in go-sql-driver/mysql. Learn about automatic plaintext fallbacks when TLS is not supported.

- Repository: [Go SQL Drivers/mysql](https://github.com/go-sql-driver/mysql)
- Tags: internals
- Published: 2026-03-02

---

**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`](https://github.com/go-sql-driver/mysql/blob/main/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:

```go
// 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`](https://github.com/go-sql-driver/mysql/blob/main/packets.go). When the server responds with its capabilities, the driver checks for the `clientSSL` flag:

```go
// 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`](https://github.com/go-sql-driver/mysql/blob/main/dsn.go) using the `readBool` helper function from [`utils.go`](https://github.com/go-sql-driver/mysql/blob/main/utils.go) (around line 58):

```go
// 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`](https://github.com/go-sql-driver/mysql/blob/main/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:

```go
// 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.

```

```go
// 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.

```

```go
// 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`](https://github.com/go-sql-driver/mysql/blob/main/dsn.go).
- The fallback logic in [`packets.go`](https://github.com/go-sql-driver/mysql/blob/main/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`](https://github.com/go-sql-driver/mysql/blob/main/dsn.go).

## Frequently Asked Questions

### Does `tls=preferred` automatically enable `allowFallbackToPlaintext`?

Yes. According to the source code in [`dsn.go`](https://github.com/go-sql-driver/mysql/blob/main/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`](https://github.com/go-sql-driver/mysql/blob/main/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.