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
- Pass a future Unix timestamp: time.Now().Add(ttl).Unix() as expiry
- Check the source token/access credentials actually contain a valid exp claim
- 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
- Always compute expiry as time.Now().Add(ttl).Unix()
- Verify source tokens carry a valid exp claim before deriving TURN credentials
- Centralize credential creation in one helper that validates expiry
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
- turn secret file others permissions must be set to 0
- one of key-file or keys must be provided
- turn secret is empty
- turn server has no usable credentials: set a non-empty secre
- invalid API key
AI-assisted analysis of livekit/livekit@ee45c3f0b1 (2026-09-02).
Data as JSON: /api/errors/8c5d9378293c1845.
Report an issue: GitHub.