{"record":{"id":"b7c1c0f2eb2629dc","repo":"hashicorp/nomad","slug":"can-t-specify-empty-port","errorCode":null,"errorMessage":"can't specify empty port","messagePattern":"can't specify empty port","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"nomad/structs/funcs.go","lineNumber":508,"sourceCode":"// maximum number of ports returned to MaxValidPort.\nfunc ParsePortRanges(spec string) ([]uint64, error) {\n\tcount := 0\n\tparts := strings.Split(spec, \",\")\n\n\t// Hot path the empty case\n\tif len(parts) == 1 && parts[0] == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tports := []uint64{}\n\tfor _, part := range parts {\n\t\tpart = strings.TrimSpace(part)\n\t\trangeParts := strings.Split(part, \"-\")\n\t\tl := len(rangeParts)\n\t\tswitch l {\n\t\tcase 1:\n\t\t\tif val := rangeParts[0]; val == \"\" {\n\t\t\t\treturn nil, fmt.Errorf(\"can't specify empty port\")\n\t\t\t} else {\n\t\t\t\tport, err := strconv.ParseUint(val, 10, 0)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tif port == 0 {\n\t\t\t\t\treturn nil, fmt.Errorf(\"port must be > 0\")\n\t\t\t\t}\n\t\t\t\tif port > MaxValidPort {\n\t\t\t\t\treturn nil, fmt.Errorf(\"port must be < %d but found %d\", MaxValidPort, port)\n\t\t\t\t}\n\t\t\t\tcount++\n\t\t\t\tif count > MaxValidPort {\n\t\t\t\t\treturn nil, fmt.Errorf(\"maximum of %d ports can be reserved\", MaxValidPort)\n\t\t\t\t}\n\t\t\t\tports = append(ports, port)\n\t\t\t}\n\t\tcase 2:","sourceCodeStart":490,"sourceCodeEnd":526,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/nomad/structs/funcs.go#L490-L526","documentation":"ParsePortRanges rejects a port-range expression that contains an empty token, i.e. a comma-separated segment that is empty or only whitespace before/after splitting on '-'. Nomad uses this when validating reserved host ports in task resources, so empty segments mean a malformed port specification.","triggerScenarios":"Calling ParsePortRanges with input like \"22,,80\" or a string with a stray comma, or a part that reduces to \"\" after TrimSpace; IsValidConfig / SetNode invoke it during node registration validation.","commonSituations":"Hand-written 'reserved' port lists in client config with a trailing comma ('22,80,'); templated config where an empty variable produced an empty element.","solutions":["Remove empty segments and stray/trailing commas from the port string (e.g. \"22,80\" not \"22,80,\")","Use range syntax only as N-M with N<=M; single ports as bare integers","Check client config 'reserved { ports = \"...\" }' and any generated config files for interpolation bugs","Validate programmatically with structs.ParsePortRanges(input) before registering the node"],"exampleFix":"// before\nreserved { ports = \"22,80,\" }\n// after\nreserved { ports = \"22,80\" }","handlingStrategy":"validation","validationCode":"// go: validate port string before node registration\nif _, err := structs.ParsePortRanges(portSpec); err != nil {\n    return fmt.Errorf(\"reserved ports invalid (%q): %v\", portSpec, err)\n}","typeGuard":null,"tryCatchPattern":"ports, err := structs.ParsePortRanges(cfg.Reserved.Ports)\nif err != nil {\n    if strings.Contains(err.Error(), \"empty port\") {\n        return fmt.Errorf(\"reserved.ports has an empty element: %q\", cfg.Reserved.Ports)\n    }\n    return err\n}","preventionTips":["Never emit trailing commas in generated config","Trim whitespace and filter empty segments when building port lists programmatically","Run ParsePortRanges on any templated value before applying"],"tags":["nomad","ports","validation","config"],"backgroundTag":"invalid-port-range","analyzedSha":"482b49bf1aec006f089bcfc7e632d8f6ac303e5e","analyzedAt":"2026-09-04T07:54:14.808Z","contentChangedAt":"2026-09-04T07:54:14.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}