anomalyco/sst · error · VisibleError

You must provide a custom domain for ${rule.listenProtocol.t

Error message

You must provide a custom domain for ${rule.listenProtocol.toUpperCase()} protocol.

What it means

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.

Source

Thrown at platform/src/components/aws/service.ts:2036

              forwardPort,
              forwardProtocol,
              container: v.container ?? containers[0].name,
            };
          });

          // validate protocols are consistent
          const appProtocols = rules.filter(
            (rule) => protocolType(rule.listenProtocol) === "application",
          );
          if (appProtocols.length > 0 && appProtocols.length < rules.length)
            throw new VisibleError(
              `Protocols must be either all http/https, or all tcp/udp/tcp_udp/tls.`,
            );

          // validate certificate exists for https/tls protocol
          rules.forEach((rule) => {
            if (["https", "tls"].includes(rule.listenProtocol) && !lb.domain) {
              throw new VisibleError(
                `You must provide a custom domain for ${rule.listenProtocol.toUpperCase()} protocol.`,
              );
            }
          });

          return rules;
        },
      );

      // normalize domain
      const domain = output(inlineLoadBalancer).apply((lb) => {
        if (!lb.domain) return undefined;

        // normalize domain
        const domain =
          typeof lb.domain === "string" ? { name: lb.domain } : lb.domain;
        return {
          name: domain.name,

View on GitHub (pinned to a0bd20f762)

Solutions

  1. Add a domain to the load balancer config, e.g. domain: "api.example.com"
  2. Switch the rule to a plain http/tcp protocol if TLS termination isn't actually needed
  3. Attach to an existing ALB/cert setup where the domain is provided

Example fix

// before
loadBalancer: { ports: { "80/http": "app:3000" }, rules: [{ listen: "443/https", forward: "80/http" }] }
// after
loadBalancer: { domain: "api.example.com", ports: { "80/http": "app:3000" }, rules: [{ listen: "443/https", forward: "80/http" }] }
Defensive patterns

Strategy: validation

Validate before calling

for (const r of config.loadBalancer.rules) {
  const proto = r.listen.split("/")[1];
  if (["https","tls"].includes(proto) && !config.loadBalancer.domain)
    throw new Error(`rule ${r.listen} requires loadBalancer.domain`);
}

Type guard

const needsDomain = (listen: string) =>
  ["https","tls"].includes(listen.split("/")[1]);

Try / catch

null

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of anomalyco/sst@a0bd20f762 (2026-08-30). Data as JSON: /api/errors/01311c2cda348b82. Report an issue: GitHub.