fatedier/frp · error

non-TLS connection received on a TlsOnly server

Error message

non-TLS connection received on a TlsOnly server

What it means

CheckAndEnableTLSServerConnWithTimeout peeks the first byte of each inbound connection to classify it: 0x17 (frp's custom TLS marker byte) and 0x16 (standard TLS ClientHello) select TLS, anything else is plaintext. When tlsOnly is true (frps tlsOnly = true in the transport config) and the byte matches neither, the connection is rejected with this error instead of being served as plaintext.

Source

Thrown at pkg/util/net/tls.go:51

	var n int
	_ = c.SetReadDeadline(time.Now().Add(timeout))
	n, err = r.Read(buf)
	_ = c.SetReadDeadline(time.Time{})
	if err != nil {
		return
	}

	switch {
	case n == 1 && int(buf[0]) == FRPTLSHeadByte:
		out = tls.Server(c, tlsConfig)
		isTLS = true
		custom = true
	case n == 1 && int(buf[0]) == 0x16:
		out = tls.Server(sc, tlsConfig)
		isTLS = true
	default:
		if tlsOnly {
			err = fmt.Errorf("non-TLS connection received on a TlsOnly server")
			return
		}
		out = sc
	}
	return
}

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Enable TLS on every client: transport.tls.enable = true in frpc.toml (modern frpc enables TLS by default).
  2. Verify no legacy frpc (< v0.50 era defaults) or probe is hitting the bind port — check frps logs for the rejected remote address.
  3. If you must accept plaintext during migration, temporarily set frps tlsOnly = false, then re-enable after all clients are migrated.
  4. Point health checks at the dashboard/web port instead of the frp bind port.

Example fix

# frpc.toml — before
[transport]
tls.enable = false

# after (required when frps sets tlsOnly = true)
[transport]
tls.enable = true
Defensive patterns

Strategy: validation

Validate before calling

# client-side preflight: assert TLS is on before pointing frpc at a tlsOnly frps
grep -q '^tlsEnable = true\|^\[transport\]' frpc.ini || echo "WARNING: TLS not enabled; frps may run with tlsOnly"

Try / catch

conn, isTLS, _, err := netpkg.CheckAndEnableTLSServerConnWithTimeout(c, tlsCfg, true, timeout)
if err != nil {
    if strings.Contains(err.Error(), "non-TLS connection") {
        c.Close() // reject and log the peer address for cleanup
        log.Warnf("plaintext conn rejected from %s", c.RemoteAddr())
    }
    return
}

Prevention

When it happens

Trigger: A plain-TCP frpc (transport.tls.enable = false) connecting to an frps configured with tlsOnly; a health check or port scanner sending arbitrary bytes; an old frpc version that does not support TLS; protocol confusion from connecting an SSH/HTTP client to the frp bind port.

Common situations: Hardening frps with tlsOnly=true while some clients still have TLS disabled; mixed fleet with old frpc versions; monitoring probes hitting the bind port with a bare TCP handshake.

Understand the failure class

Related errors


AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15). Data as JSON: /api/errors/009b89a3c3ce9a96. Report an issue: GitHub.