netbirdio/netbird · error
--expiration must be a positive duration (e.g., 720h, 365d,
Error message
--expiration must be a positive duration (e.g., 720h, 365d, 8760h)
What it means
Validation inside create-root-key's RunE (rootkey.go:26): the --expiration flag, parsed by cobra via time.ParseDuration, was <= 0. The flag defaults to 0 and is marked required, so simply omitting it trips cobra's required-flag error first; this specific message means an explicit non-positive value was passed. Note Go durations have no 'd' unit — the '365d' example in the message itself would fail earlier at flag parsing with 'time: unknown unit "d" in "365d"'.
Source
Thrown at client/cmd/signer/rootkey.go:27
"github.com/netbirdio/netbird/client/internal/updater/reposign"
)
var (
privKeyFile string
pubKeyFile string
rootExpiration time.Duration
)
var createRootKeyCmd = &cobra.Command{
Use: "create-root-key",
Short: "Create a new root key pair",
Long: `Create a new root key pair and specify an expiration time for it.`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
// Validate expiration
if rootExpiration <= 0 {
return fmt.Errorf("--expiration must be a positive duration (e.g., 720h, 365d, 8760h)")
}
// Run main logic
if err := handleGenerateRootKey(cmd, privKeyFile, pubKeyFile, rootExpiration); err != nil {
return fmt.Errorf("failed to generate root key: %w", err)
}
return nil
},
}
func init() {
rootCmd.AddCommand(createRootKeyCmd)
createRootKeyCmd.Flags().StringVar(&privKeyFile, "priv-key-file", "", "Path to output private key file")
createRootKeyCmd.Flags().StringVar(&pubKeyFile, "pub-key-file", "", "Path to output public key file")
createRootKeyCmd.Flags().DurationVar(&rootExpiration, "expiration", 0, "Expiration time for the root key (e.g., 720h,)")
if err := createRootKeyCmd.MarkFlagRequired("priv-key-file"); err != nil {
panic(err)View on GitHub (pinned to 93e97f4bf1)
Solutions
- Pass a positive duration in Go units — hours is most practical: --expiration 8760h for 1 year, 720h for 30 days
- Do not use d suffixes; convert days to hours (days * 24) in scripts
- If scripting the value, guard it: [ "$DAYS" -gt 0 ] && FLAG="--expiration $((DAYS * 24))h"
Example fix
# before signer create-root-key --priv-key-file root.pem --pub-key-file root-public.pem --expiration 365d # (cobra: invalid argument "365d" for "--expiration" flag: time: unknown unit) # after signer create-root-key --priv-key-file root.pem --pub-key-file root-public.pem --expiration 8760h
Defensive patterns
Strategy: validation
Validate before calling
func parseExpirationFlag(raw string) (time.Duration, error) {
d, err := time.ParseDuration(raw)
if err != nil {
return 0, fmt.Errorf("%q is not a Go duration (use hours, e.g. 8760h): %w", raw, err)
}
if d <= 0 {
return 0, fmt.Errorf("expiration must be positive, got %s", d)
}
return d, nil
} Type guard
func isValidExpiration(s string) bool {
d, err := time.ParseDuration(s)
return err == nil && d > 0
} Prevention
- Use hour units exclusively (8760h = 1 year); Go has no 'd' suffix
- Derive the flag in scripts from a validated day count: --expiration $((days*24))h
- Remember cobra already rejects unparsable values, so this error only surfaces explicit zero/negative inputs
When it happens
Trigger: Running create-root-key with --expiration 0, --expiration 0s, or a negative value such as --expiration -24h; values like 365d or 30d never reach this check because cobra rejects the unit during flag parsing.
Common situations: Copy/pasting the error message's own '365d' example; scripting that computes the flag from a variable that evaluated to zero; assuming day units like Kubernetes durations.
Related errors
- at least one --artifact-pub-key-file must be provided
- duration must not be negative
- invalid duration format: %v
- --expiration must be a positive duration (e.g., 720h, 365d,
- invalid key ID: %w
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/22b91586a8687dab.
Report an issue: GitHub.