{"record":{"id":"6a994da6e06fdc4f","repo":"anomalyco/sst","slug":"protocols-must-be-either-all-http-https-or-all-tc","errorCode":null,"errorMessage":"Protocols must be either all http/https, or all tcp/udp/tcp_udp/tls.","messagePattern":"Protocols must be either all http/https, or all tcp/udp/tcp_udp/tls\\.","errorType":"validation","errorClass":"VisibleError","httpStatus":null,"severity":"error","filePath":"platform/src/components/aws/service-v1.ts","lineNumber":267,"sourceCode":"        const ports = pub.ports.map((v) => {\n          const listenParts = v.listen.split(\"/\");\n          const forwardParts = v.forward ? v.forward.split(\"/\") : listenParts;\n          return {\n            listenPort: parseInt(listenParts[0]),\n            listenProtocol: listenParts[1],\n            forwardPort: parseInt(forwardParts[0]),\n            forwardProtocol: forwardParts[1],\n          };\n        });\n\n        // validate protocols are consistent\n        const appProtocols = ports.filter(\n          (port) =>\n            [\"http\", \"https\"].includes(port.listenProtocol) &&\n            [\"http\", \"https\"].includes(port.forwardProtocol),\n        );\n        if (appProtocols.length > 0 && appProtocols.length < ports.length)\n          throw new VisibleError(\n            `Protocols must be either all http/https, or all tcp/udp/tcp_udp/tls.`,\n          );\n\n        // validate certificate exists for https/tls protocol\n        ports.forEach((port) => {\n          if ([\"https\", \"tls\"].includes(port.listenProtocol) && !pub.domain) {\n            throw new VisibleError(\n              `You must provide a custom domain for ${port.listenProtocol.toUpperCase()} protocol.`,\n            );\n          }\n        });\n\n        return ports;\n      });\n\n      const domain = output(args.public).apply((pub) => {\n        if (!pub.domain) return undefined;\n","sourceCodeStart":249,"sourceCodeEnd":285,"githubUrl":"https://github.com/anomalyco/sst/blob/a0bd20f762883e72a35caccb4896c42ce5b3f707/platform/src/components/aws/service-v1.ts#L249-L285","documentation":"A Service's public ports must be uniformly either application-layer (http/https, backed by an ALB) or network-layer (tcp/udp/tcp_udp/tls, backed by an NLB) — the underlying load balancer type can't mix both. normalizePublic counts http/https ports and throws if only some of the ports are app-protocol.","triggerScenarios":"Mixing e.g. `[{ listen: \"80/http\" }, { listen: \"22/tcp\" }]` in `public.ports`; adding one TCP port to an otherwise HTTP service.","commonSituations":"Exposing both an HTTP API and a raw TCP port (e.g. SSH, game server) on one service; incremental edits adding a port with a different protocol.","solutions":["Make all `public.ports` entries use http/https protocols","Or make all entries use tcp/udp/tcp_udp/tls protocols","Split into two separate Service components if both protocol families are genuinely needed"],"exampleFix":"// before\npublic: { ports: [{ listen: \"80/http\" }, { listen: \"22/tcp\" }] }\n// after\npublic: { ports: [{ listen: \"80/http\" }, { listen: \"443/https\" }] }","handlingStrategy":"validation","validationCode":"const app = [\"http\",\"https\"]; const net = [\"tcp\",\"udp\",\"tcp_udp\",\"tls\"];\nconst protos = args.public?.ports?.map(p => p.listen.split(\"/\")[1]) ?? [];\nconst allApp = protos.every(p => app.includes(p));\nconst allNet = protos.every(p => net.includes(p));\nif (protos.length && !allApp && !allNet) throw new Error(\"mixing app and network protocols in public.ports\");","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Pick one protocol family per service","Use ALB-style http/https for web APIs, NLB-style tcp/udp for raw sockets","Split mixed workloads into separate services"],"tags":["ports","protocols","load-balancer","validation"],"backgroundTag":"mixed-protocol-ports","analyzedSha":"a0bd20f762883e72a35caccb4896c42ce5b3f707","analyzedAt":"2026-08-30T11:26:00.383Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}