{"record":{"id":"b5af90285c0a9a8b","repo":"AlistGo/alist","slug":"the-number-of-exposed-ports-and-listened-ports-doe","errorCode":null,"errorMessage":"the number of exposed ports and listened ports does not match","messagePattern":"the number of exposed ports and listened ports does not match","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/ftp.go","lineNumber":237,"sourceCode":"\t\t\t}\n\t\t}\n\t}\n\tfor i, mapper := range pasvPortMappers {\n\t\tvar err error\n\t\texposed, listened, mapped := strings.Cut(mapper, \":\")\n\t\tfor {\n\t\t\tif mapped {\n\t\t\t\tvar es, ls, el, ll int\n\t\t\t\tes, el, err = convertToPorts(exposed)\n\t\t\t\tif err != nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tls, ll, err = convertToPorts(listened)\n\t\t\t\tif err != nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tif el != ll {\n\t\t\t\t\terr = errors.New(\"the number of exposed ports and listened ports does not match\")\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tgroups[i].ExposedStart = es\n\t\t\t\tgroups[i].ListenedStart = ls\n\t\t\t\tgroups[i].Length = el\n\t\t\t\ttotalLength += el\n\t\t\t} else {\n\t\t\t\tvar start, length int\n\t\t\t\tstart, length, err = convertToPorts(mapper)\n\t\t\t\tgroups[i].ExposedStart = start\n\t\t\t\tgroups[i].ListenedStart = start\n\t\t\t\tgroups[i].Length = length\n\t\t\t\ttotalLength += length\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\tutils.Log.Fatalf(\"failed to convert FTP PASV port mapper %s: %v, the port mapper will be ignored.\", mapper, err)","sourceCodeStart":219,"sourceCodeEnd":255,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/server/ftp.go#L219-L255","documentation":"Returned while parsing an exposed:listened passive-port mapping entry (form 'exposedRange:listenRange', for NAT/container port remapping). Each side is parsed into (start, length); the mapper requires both ranges to cover the same number of ports so the random index into the pool maps 1:1. As with other mapper parse errors, the entry is fatal-logged and the mapper function is discarded.","triggerScenarios":"A setting like '50000-50100:60000-60200' where the exposed range spans 101 ports but the listened range spans 201; any exposed:listed pair whose range lengths differ.","commonSituations":"Docker/NAT deployments where the host-published range and container-listening range were configured with different widths; editing one side of the mapping and forgetting the other; mixing single-port and range syntax between the two halves.","solutions":["Make both ranges the same length, e.g. '50000-50100:60000-60100'","If ports map 1:1 identically, drop the colon form and just use the plain range '50000-50100'","Restart AList and confirm no 'port mapper will be ignored' fatal log appears"],"exampleFix":"// before\npasv: \"50000-50100:60000-60200\"\n// after\npasv: \"50000-50100:60000-60100\"","handlingStrategy":"validation","validationCode":"func validMapping(entry string) bool {\n    exposed, listened, mapped := strings.Cut(entry, \":\")\n    if !mapped { return true }\n    _, el := parseRangeLen(exposed)\n    _, ll := parseRangeLen(listened)\n    return el > 0 && el == ll\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Document that exposed and listened ranges in a mapped entry must be the same width","Generate docker -p publish flags from the same variable as the pasv setting to keep widths in sync","Prefer the plain (unmapped) form unless NAT actually remaps ports"],"tags":["ftp","config","nat","docker","validation"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}