livekit/livekit · error · ErrExpired

expired

Error message

expired

What it means

ErrExpired is the sentinel returned by TURN credential helpers when expiry is zero or already in the past. ParseUsername rejects credentials encoding a zero expiry, and CreatePassword refuses to mint a password for a non-future expiry, since TURN short-lived credentials must carry a valid future timestamp.

Source

Thrown at pkg/service/turn.go:47

	"github.com/pkg/errors"

	"github.com/livekit/protocol/auth"
	"github.com/livekit/protocol/livekit"
	"github.com/livekit/protocol/logger"
	"github.com/livekit/protocol/logger/pionlogger"

	"github.com/livekit/livekit-server/pkg/config"
	"github.com/livekit/livekit-server/pkg/telemetry"
	"github.com/livekit/livekit-server/pkg/telemetry/prometheus"
)

const (
	LivekitRealm = "livekit"

	allocateRetries = 50
)

var ErrExpired = errors.New("expired")

// parsePeerCIDRs compiles a list of CIDR strings, failing with a field-specific
// error on any invalid entry so a malformed peer policy is never silently ignored.
func parsePeerCIDRs(field string, cidrs []string) ([]*net.IPNet, error) {
	parsed := make([]*net.IPNet, 0, len(cidrs))
	for _, cidr := range cidrs {
		_, ipnet, err := net.ParseCIDR(cidr)
		if err != nil {
			return nil, fmt.Errorf("invalid CIDR %q in %s: %w", cidr, field, err)
		}
		parsed = append(parsed, ipnet)
	}
	return parsed, nil
}

func NewTurnServer(conf *config.Config, authHandler turn.AuthHandler, standalone bool) (*turn.Server, error) {
	turnConf := conf.TURN
	if !turnConf.Enabled {

View on GitHub (pinned to ee45c3f0b1)

Solutions

  1. Pass a future Unix timestamp: time.Now().Add(ttl).Unix() as expiry
  2. Check the source token/access credentials actually contain a valid exp claim
  3. Treat errors.Is(err, turn.ErrExpired) as 'issue a fresh credential' and regenerate

Example fix

// before
pwd, err := handler.CreatePassword(apiKey, pID, 0)
// after
expiry := time.Now().Add(time.Hour).Unix()
pwd, err := handler.CreatePassword(apiKey, pID, expiry)
Defensive patterns

Strategy: try-catch

Validate before calling

func validExpiry(expiry int64) bool {
    return expiry != 0 && time.Now().Before(time.Unix(expiry, 0))
}
if !validExpiry(expiry) {
    return errors.New("refusing to create TURN credentials: expiry not in future")
}

Type guard

func isErrExpired(err error) bool { return errors.Is(err, turn.ErrExpired) }

Try / catch

if _, err := handler.CreatePassword(apiKey, pID, expiry); err != nil {
    if errors.Is(err, turn.ErrExpired) {
        // mint a fresh credential with a future expiry
    }
}

Prevention

When it happens

Trigger: Calling TURNAuthHandler.CreatePassword with expiry == 0 or time.Now() after time.Unix(expiry,0); ParseUsername decoding a username whose embedded expiry is 0; callers computing expiry from an unset AccessToken/notBefore-less token.

Common situations: Generating TURN credentials with a JWT/room token whose expiry field is absent; integer/timezone math producing 0; passing Unix seconds where the caller thought it was a duration.

Related errors


AI-assisted analysis of livekit/livekit@ee45c3f0b1 (2026-09-02). Data as JSON: /api/errors/8c5d9378293c1845. Report an issue: GitHub.