anomalyco/sst · error · VisibleError

Protocols must be either all http/https, or all tcp/udp/tcp_

Error message

Protocols must be either all http/https, or all tcp/udp/tcp_udp/tls.

What it means

A single ALB listener set must be homogeneous: SST groups rules into application (http/https) or network (tcp/udp/tcp_udp/tls) listeners, and an Elastic Load Balancer cannot mix listener types in one load balancer configuration. normalizeLoadBalancer throws when some but not all rules are application-protocol rules.

Source

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

                `The listen protocol "${v.listen}" must match the forward protocol "${v.forward}".`,
              );
            return {
              type: "forward" as const,
              listenPort,
              listenProtocol,
              listenConditions,
              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) => {

View on GitHub (pinned to a0bd20f762)

Solutions

  1. Make all rules use http/https, or all use tcp/udp/tcp_udp/tls
  2. Move the odd-protocol listener into a separate Service or a separately defined load balancer
  3. Drop the rule that no longer applies

Example fix

// before
rules: [{ listen: "80/http" }, { listen: "8080/tcp" }]
// after
rules: [{ listen: "80/http" }, { listen: "8080/http" }]
Defensive patterns

Strategy: validation

Validate before calling

const families = new Set(config.loadBalancer.rules.map(r =>
  ["http","https"].includes(r.listen.split("/")[1]) ? "app" : "network"));
if (families.size > 1) throw new Error("all rules must be http/https or all tcp/udp/tcp_udp/tls");

Type guard

const allSameFamily = (rules: { listen: string }[]) => {
  const fam = (p: string) => ["http","https"].includes(p) ? "app" : "network";
  return rules.every(r => fam(r.listen.split("/")[1]) === fam(rules[0].listen.split("/")[1]));
};

Try / catch

null

Prevention

When it happens

Trigger: Providing loadBalancer.rules where one rule listens on "80/http" and another on "8080/tcp", or "443/https" mixed with "9000/tls".

Common situations: Adding a gRPC/TLS port next to existing HTTP rules; migrating a service from HTTP to TCP listeners while leaving an old rule behind; merging configs from two services.

Understand the failure class

Related errors


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