{"record":{"id":"01311c2cda348b82","repo":"anomalyco/sst","slug":"you-must-provide-a-custom-domain-for-rule-listen","errorCode":null,"errorMessage":"You must provide a custom domain for ${rule.listenProtocol.toUpperCase()} protocol.","messagePattern":"You must provide a custom domain for (.+?) protocol\\.","errorType":"validation","errorClass":"VisibleError","httpStatus":null,"severity":"error","filePath":"platform/src/components/aws/service.ts","lineNumber":2036,"sourceCode":"              forwardPort,\n              forwardProtocol,\n              container: v.container ?? containers[0].name,\n            };\n          });\n\n          // validate protocols are consistent\n          const appProtocols = rules.filter(\n            (rule) => protocolType(rule.listenProtocol) === \"application\",\n          );\n          if (appProtocols.length > 0 && appProtocols.length < rules.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          rules.forEach((rule) => {\n            if ([\"https\", \"tls\"].includes(rule.listenProtocol) && !lb.domain) {\n              throw new VisibleError(\n                `You must provide a custom domain for ${rule.listenProtocol.toUpperCase()} protocol.`,\n              );\n            }\n          });\n\n          return rules;\n        },\n      );\n\n      // normalize domain\n      const domain = output(inlineLoadBalancer).apply((lb) => {\n        if (!lb.domain) return undefined;\n\n        // normalize domain\n        const domain =\n          typeof lb.domain === \"string\" ? { name: lb.domain } : lb.domain;\n        return {\n          name: domain.name,","sourceCodeStart":2018,"sourceCodeEnd":2054,"githubUrl":"https://github.com/anomalyco/sst/blob/a0bd20f762883e72a35caccb4896c42ce5b3f707/platform/src/components/aws/service.ts#L2018-L2054","documentation":"HTTPS and TLS listeners terminate TLS and therefore need a certificate, which SST provisions through the lb.domain property. If any rule listens on https or tls but no custom domain is configured, SST cannot issue/attach a cert and throws before deployment.","triggerScenarios":"Any loadBalancer.rules entry with listen \"443/https\" or \"9000/tls\" (external ALB attachment included) while the Service's loadBalancer.domain (or the shared ALB's domain) is undefined.","commonSituations":"Testing TLS locally without a domain; forgetting that shared/external ALB domains must be declared where SST expects them; renaming the domain field during refactors.","solutions":["Add a domain to the load balancer config, e.g. domain: \"api.example.com\"","Switch the rule to a plain http/tcp protocol if TLS termination isn't actually needed","Attach to an existing ALB/cert setup where the domain is provided"],"exampleFix":"// before\nloadBalancer: { ports: { \"80/http\": \"app:3000\" }, rules: [{ listen: \"443/https\", forward: \"80/http\" }] }\n// after\nloadBalancer: { domain: \"api.example.com\", ports: { \"80/http\": \"app:3000\" }, rules: [{ listen: \"443/https\", forward: \"80/http\" }] }","handlingStrategy":"validation","validationCode":"for (const r of config.loadBalancer.rules) {\n  const proto = r.listen.split(\"/\")[1];\n  if ([\"https\",\"tls\"].includes(proto) && !config.loadBalancer.domain)\n    throw new Error(`rule ${r.listen} requires loadBalancer.domain`);\n}","typeGuard":"const needsDomain = (listen: string) =>\n  [\"https\",\"tls\"].includes(listen.split(\"/\")[1]);","tryCatchPattern":"null","preventionTips":["Always set domain when any listener is https/tls","Prefer http internally and terminate TLS at one place","Add a config schema requiring domain when https/tls protocols are present"],"tags":["load-balancer","tls","certificate","domain"],"backgroundTag":"missing-certificate-domain","analyzedSha":"a0bd20f762883e72a35caccb4896c42ce5b3f707","analyzedAt":"2026-08-30T11:26:00.383Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}