How to detect when the certificates used by nats-server will expire? #7684
|
While using NATS Server, I encountered an issue where the Let's Encrypt certificate was renewed correctly, but This caused clients connecting to the NATS Server to receive the error: Therefore, I would like to know if there is a way to determine the expiration dates of all certificates currently being used by the NATS Server. I tried running |
Replies: 4 comments 2 replies
|
I tested this locally and can reproduce the same issue. When connecting without If you set echo | openssl s_client -connect localhost:4222 2>/dev/null | openssl x509 -noout -enddate
> notAfter=Mar 8 09:34:31 2026 GMTEven with Alternative solutionAs an alternative, you can enable the HTTPS monitoring endpoint and run the same This avoids the protocol mismatch entirely and allows you to verify the TLS certificate without requiring |
|
I think we can probably add something into |
|
I developed a tool based on Golang to monitor and verify nats-server certificate information. package main
import (
"bufio"
"crypto/tls"
"encoding/json"
"flag"
"fmt"
"net"
"net/url"
"os"
"strings"
"time"
)
type ServerInfo struct {
ServerID string `json:"server_id"`
Version string `json:"version"`
TLSRequired bool `json:"tls_required"`
TLSVerify bool `json:"tls_verify"`
}
func main() {
var (
targetUrl string
insecure bool
timeout time.Duration
)
flag.BoolVar(&insecure, "k", false, "Skip certificate verification (Insecure)")
flag.DurationVar(&timeout, "timeout", 5*time.Second, "Connection timeout")
flag.Parse()
u, err := url.Parse(targetUrl)
if err != nil {
fatalf("URL parsing failed: %v", err)
}
address := u.Host
if !strings.Contains(address, ":") {
address += ":4222"
}
fmt.Printf("Connecting to: %s ...\n", address)
conn, err := net.DialTimeout("tcp", address, timeout)
if err != nil {
fatalf("TCP connection failed: %v", err)
}
defer conn.Close()
conn.SetReadDeadline(time.Now().Add(timeout))
reader := bufio.NewReader(conn)
infoLine, err := reader.ReadString('\n')
if err != nil {
fatalf("Failed to read INFO protocol: %v", err)
}
if !strings.HasPrefix(infoLine, "INFO") {
fatalf("Protocol error: Server did not return INFO command, perhaps this is not a NATS port? Content: %s", infoLine)
}
jsonPart := strings.TrimSpace(strings.TrimPrefix(infoLine, "INFO"))
var info ServerInfo
if err := json.Unmarshal([]byte(jsonPart), &info); err != nil {
fatalf("Failed to parse INFO JSON: %v", err)
}
fmt.Printf("Server Version: %s\n", info.Version)
fmt.Printf("TLS Required: %v\n", info.TLSRequired)
if !info.TLSRequired {
fmt.Println("Warning: 'tls_required' is not enabled on this server. Attempting to force TLS upgrade to check certificate...")
}
tlsConfig := &tls.Config{
InsecureSkipVerify: insecure,
}
tlsConn := tls.Client(conn, tlsConfig)
if err := tlsConn.Handshake(); err != nil {
fatalf("TLS handshake failed: %v\nHint: If the server is not configured with a certificate, this step will fail.", err)
}
state := tlsConn.ConnectionState()
certs := state.PeerCertificates
if len(certs) == 0 {
fatalf("No server certificates obtained")
}
serverCert := certs[0]
fmt.Println("\n----------- Certificate Information -----------")
fmt.Printf("Subject: %s\n", serverCert.Subject)
fmt.Printf("Issuer: %s\n", serverCert.Issuer)
fmt.Printf("DNS Names: %v\n", serverCert.DNSNames)
fmt.Printf("IP Addresses: %v\n", serverCert.IPAddresses)
fmt.Println("\n----------- Validity Period -----------")
fmt.Printf("Not Before: %s\n", serverCert.NotBefore.Local().Format(time.RFC3339))
fmt.Printf("Not After: %s\n", serverCert.NotAfter.Local().Format(time.RFC3339))
timeLeft := time.Until(serverCert.NotAfter)
daysLeft := int(timeLeft.Hours() / 24)
if timeLeft < 0 {
fmt.Printf("Status: [Expired] (Expired %d days ago)\n", -daysLeft)
} else {
fmt.Printf("Status: [Valid] (%d days remaining)\n", daysLeft)
}
}
func fatalf(format string, args ...interface{}) {
fmt.Printf("Error: "+format+"\n", args...)
os.Exit(1)
} |
|
Certificate expiration dates have been exposed through the varz monitoring end point. See #7709 |
I tested this locally and can reproduce the same issue.
When connecting without
handshake_first, the server logs:If you set
handshake_first: truein the TLS configuration(see: https://docs.nats.io/running-a-nats-service/configuration/securing_nats/tls#tls-first-handshake), the connection succeeds and returns a valid certificate.
Even with
handshake_firstenabled, the NATS server still logs a parser error: