syncthing/syncthing · error

certificate has expired

Error message

certificate has expired

What it means

Raised by the self-signed HTTPS certificate sanity check in the REST API layer. After confirming the leaf certificate is Syncthing's own newer-style self-signed certificate (zero DNS SANs, or a single SAN equal to the issuer's CommonName), the code compares leaf.NotAfter to time.Now(); if the certificate validity has passed, it returns errors.New("certificate has expired"). It signals that the GUI's TLS certificate is dead and must be regenerated before modern clients will trust the endpoint.

Source

Thrown at lib/api/api.go:1893

	if leaf.Subject.String() != leaf.Issuer.String() || len(leaf.IPAddresses) != 0 {
		// The certificate is not self signed, or has IP attributes we don't
		// add, so we leave it alone.
		return nil
	}
	if len(leaf.DNSNames) > 1 {
		// The certificate has more DNS SANs attributes than we ever add, so
		// we leave it alone.
		return nil
	}
	if len(leaf.DNSNames) == 1 && leaf.DNSNames[0] != leaf.Issuer.CommonName {
		// The one SAN is different from the issuer, so it's not one of our
		// newer self signed certificates.
		return nil
	}

	if leaf.NotAfter.Before(time.Now()) {
		return errors.New("certificate has expired")
	}
	if leaf.NotAfter.Before(time.Now().Add(30 * 24 * time.Hour)) {
		return errors.New("certificate will soon expire")
	}

	// On macOS, check for certificates issued on or after July 1st, 2019,
	// with a longer validity time than 825 days.
	cutoff := time.Date(2019, 7, 1, 0, 0, 0, 0, time.UTC)
	if build.IsDarwin &&
		leaf.NotBefore.After(cutoff) &&
		leaf.NotAfter.Sub(leaf.NotBefore) > 825*24*time.Hour {
		return errors.New("certificate incompatible with macOS 10.15 (Catalina)")
	}

	return nil
}

func errorStringMap(errs map[string]error) map[string]*string {

View on GitHub (pinned to 058bcd7334)

Solutions

  1. Stop Syncthing, delete the expired certificate and key in the config directory (https-cert.pem/https-key.pem or certificate.pem/key.pem), and restart to generate a fresh self-signed certificate.
  2. Check the system clock (date, NTP status); if the clock is wrong, fix it before regenerating certificates.
  3. Alternatively replace the self-signed certificate with a proper one via the GUI HTTPS settings if clients must not re-accept a new cert.
  4. Re-accept the new certificate in the browser/API client afterwards, since the device identity may change if the API cert is also used for the device ID.
Defensive patterns

Strategy: validation

Validate before calling

// Check expiry before starting the GUI / making requests:
leaf, err := getLeafCert(certPath)
if err == nil && leaf.NotAfter.Before(time.Now()) {
    // regenerate certificate before use
}

Try / catch

if err := checkCertificate(cert); err != nil {
    if strings.Contains(err.Error(), "certificate has expired") {
        // regenerate cert (delete cert+key, restart) instead of failing
    }
}

Prevention

When it happens

Trigger: The certificate-check function runs against the GUI's certificate.pem/https-cert.pem leaf: the cert is recognized as Syncthing-issued (SAN rules match) and leaf.NotAfter is before time.Now(). Typically after a long downtime or a system clock moved past the validity period.

Common situations: Devices that were offline or retired for longer than the certificate lifetime (e.g. a year+); VMs restored from an old snapshot with an expired cert; RTC battery failure pushing the clock forward.

Understand the failure class

Related errors


AI-assisted analysis of syncthing/syncthing@058bcd7334 (2026-08-15). Data as JSON: /api/errors/fd0c861703097eed. Report an issue: GitHub.