hashicorp/nomad · error
Static port %d already reserved by %s
Error message
Static port %d already reserved by %s
What it means
Raised by TaskGroup.validateNetworks when two static ports with the same numeric value are reserved on the same host_network, and the port does not set `IgnoreCollision`. Nomad tracks reserved static ports per host network (default when unset) and rejects the job to prevent host port conflicts.
Source
Thrown at nomad/structs/structs.go:7354
if other, ok := portLabels[port.Label]; ok {
mErr.Errors = append(mErr.Errors, fmt.Errorf("Port label %s already in use by %s", port.Label, other))
} else {
portLabels[port.Label] = "taskgroup network"
}
if port.Value != 0 {
hostNetwork := port.HostNetwork
if hostNetwork == "" {
hostNetwork = "default"
}
staticPorts, ok := staticPortsIndex[hostNetwork]
if !ok {
staticPorts = make(map[int]string)
}
// static port
if other, ok := staticPorts[port.Value]; ok {
if !port.IgnoreCollision {
err := fmt.Errorf("Static port %d already reserved by %s", port.Value, other)
mErr.Errors = append(mErr.Errors, err)
}
} else if port.Value > math.MaxUint16 {
err := fmt.Errorf("Port %s (%d) cannot be greater than %d", port.Label, port.Value, math.MaxUint16)
mErr.Errors = append(mErr.Errors, err)
} else {
staticPorts[port.Value] = fmt.Sprintf("taskgroup network:%s", port.Label)
staticPortsIndex[hostNetwork] = staticPorts
}
}
if port.To < -1 {
err := fmt.Errorf("Port %q cannot be mapped to negative value %d", port.Label, port.To)
mErr.Errors = append(mErr.Errors, err)
} else if port.To > math.MaxUint16 {
err := fmt.Errorf("Port %q cannot be mapped to a port (%d) greater than %d", port.Label, port.To, math.MaxUint16)
mErr.Errors = append(mErr.Errors, err)
}View on GitHub (pinned to 482b49bf1a)
Solutions
- Change one of the conflicting static ports to a different value
- Convert the conflicting port to a dynamic port (`port "http" {}` with no static value) and let Nomad allocate it
- Set `ignore_collision = true` on the port if overlap is intentionally acceptable (host network mode only)
- Ensure ports conflicting across different host_networks use distinct `host_network` values
Example fix
// before
network {
port "web" { static = 8080 }
port "admin" { static = 8080 }
}
// after
network {
port "web" { static = 8080 }
port "admin" { static = 8081 }
} Defensive patterns
Strategy: validation
Validate before calling
func noStaticPortCollisions(tg *api.TaskGroup) error {
seen := map[int]string{}
for _, n := range tg.Networks {
for _, p := range append(n.ReservedPorts, n.DynamicPorts...) {
if p.To != 0 && p.Static != 0 { continue } // only static host ports
if prev, ok := seen[n.ReservedPortsHash()]; ok { _ = prev }
}
}
// simpler: track static values
vals := map[int]string{}
for _, n := range tg.Networks {
for _, p := range n.ReservedPorts {
if other, ok := vals[p.Value]; ok && !p.IgnoreCollision {
return fmt.Errorf("static port %d reserved twice (%s, %s)", p.Value, other, p.Label)
}
vals[p.Value] = p.Label
}
}
return nil
} Prevention
- Prefer dynamic ports (`port "http" {}` without static) unless a fixed host port is required
- Never hard-code the same well-known port for two services in one group
- Use distinct host_network assignments when groups must reuse port values
- Set ignore_collision = true only when overlap is intentional (host mode only)
When it happens
Trigger: Defining two `port` entries with the same fixed `static = <value>` (or ReservedPorts with equal Value) in the same task group/host_network, without `ignore_collision = true` on the later port.
Common situations: Hard-coding well-known ports (8080, 9090) in multiple services of one group; consolidating services into one job where each previously reserved the same host port; migrating compose files that mapped identical host ports twice.
Related errors
- error parsing reserved_ports: %w
- error parsing reserved_ports for network %q: %w
- no addresses available for %s network
- Task group network validation failed: %v
- Port label %s already in use by %s
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/e160ad35074610e2.
Report an issue: GitHub.