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
- Read the wrapped %v inner error to identify which cert entry is malformed
- Fix each pinned cert entry to the accepted format (e.g. 'rsa:HEX' / 'ecdsa:HEX' / 'x509:base64' style used by validatePinnedCerts)
- 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
- Copy pinned-cert formats exactly from the validated examples (scheme-prefixed fingerprints)
- Run nats-server with --signal reload or a config lint step before production deploys
- Keep TLS pin entries generated by a script, not hand-typed
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
- 'cert_file' and 'cert_store' may not both be configured
- expected 'cert_store' to be a valid non-empty string
- expected 'cert_match_by' to be a valid non-empty string
- expected 'cert_match' to be a valid non-empty string
- mqtt requires server name to be explicitly set
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/0bdb2c9e55639d94.
Report an issue: GitHub.