nats-io/nats-server · error

mqtt: %v

Error message

mqtt: %v

What it means

MQTT config validation wraps the failure of validatePinnedCerts (which checks the format of tls_pinned_certs entries) with the 'mqtt: ' prefix. The MQTT option block references TLS pinned certs that fail validation, so the server refuses to start.

Source

Thrown at server/mqtt.go:739

			return errMQTTTokenMixWIthUsersNKeys
		}
	}
	if mo.AckWait < 0 {
		return errMQTTAckWaitMustBePositive
	}
	if mo.JSAPITimeout < 0 {
		return errMQTTJSAPITimeoutMustBePositive
	}
	// If strictly standalone and there is no JS enabled, then it won't work...
	// For leafnodes, we could either have remote(s) and it would be ok, or no
	// remote but accept from a remote side that has "hub" property set, which
	// then would ok too. So we fail only if we have no leafnode config at all.
	if !o.JetStream && o.Cluster.Port == 0 && o.Gateway.Port == 0 &&
		o.LeafNode.Port == 0 && len(o.LeafNode.Remotes) == 0 {
		return errMQTTStandaloneNeedsJetStream
	}
	if err := validatePinnedCerts(mo.TLSPinnedCerts); err != nil {
		return fmt.Errorf("mqtt: %v", err)
	}
	if mo.ConsumerReplicas > 0 && mo.StreamReplicas > 0 && mo.ConsumerReplicas > mo.StreamReplicas {
		return fmt.Errorf("mqtt: consumer_replicas (%v) cannot be higher than stream_replicas (%v)",
			mo.ConsumerReplicas, mo.StreamReplicas)
	}
	return nil
}

// Returns true if this connection is from a MQTT client.
// Lock held on entry.
func (c *client) isMqtt() bool {
	return c.mqtt != nil
}

// If this is an MQTT client, returns the session client ID,
// otherwise returns the empty string.
// Lock held on entry
func (c *client) getMQTTClientID() string {

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Read the wrapped %v inner error to identify which cert entry is malformed
  2. Fix each pinned cert entry to the accepted format (e.g. 'rsa:HEX' / 'ecdsa:HEX' / 'x509:base64' style used by validatePinnedCerts)
  3. Regenerate the fingerprint from the actual client certificate if the value was copied from elsewhere

Example fix

// before (nats-server.conf)
mqtt {
  tls_pinned_certs: ["abcdef1234"]
}
// after
mqtt {
  tls_pinned_certs: ["sha256:abcdef1234"]
}
Defensive patterns

Strategy: validation

Validate before calling

// before starting the server, validate options
if len(o.Mqtt.TLSPinnedCerts) > 0 {
	for _, c := range o.Mqtt.TLSPinnedCerts {
		// entries must use the accepted pin scheme, e.g. "sha256:<hex>"
		if !strings.Contains(c, ":") {
			return fmt.Errorf("mqtt pinned cert %q lacks scheme prefix", c)
		}
	}
}

Try / catch

if err := srv.ValidateMqttOpts(); err != nil {
	var inner error
	if strings.HasPrefix(err.Error(), "mqtt: ") {
		inner = errors.Unwrap(err) // inspect wrapped cert error
	}
	return fmt.Errorf("mqtt config invalid: %v", inner)
}

Prevention

When it happens

Trigger: Setting mqtt.tls_pinned_certs with an entry not matching the accepted cert-pin formats (e.g. missing curve/type prefix); calling ValidateMqttOpts on an Options struct with malformed TLSPinnedCerts.

Common situations: Copying a pinned-cert entry from documentation for a different scheme; hand-editing the mqtt config block; typos in the base64 or SHA fingerprint of the pinned certificate.

Related errors


AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02). Data as JSON: /api/errors/0bdb2c9e55639d94. Report an issue: GitHub.