Skip to content

Router: router.route() with http:// URL origin returns 502 (Failed to contact the origin) #6742

Description

@turisanapo

Bug

router.route("/*", "http://<alb-dns>") results in a 502 "Failed to contact the origin" because CloudFront attempts HTTPS:443 instead of HTTP:80.

Reproduction

const router = new sst.aws.Router("MyRouter", { domain: "example.com" });
const service = new sst.aws.Service("MyService", {
  cluster,
  loadBalancer: {
    rules: [{ listen: "80/http", forward: "8080/http" }],
  },
});

// Pass the ALB's raw DNS as an http:// URL
const albUrl = service.nodes.loadBalancer.dnsName.apply(
  (dns) => `http://${dns}`
);
router.route("/*", albUrl);

The ALB only listens on port 80 (HTTP). CloudFront returns 502 because it tries to connect on HTTPS:443.

Root cause

In the generated CloudFront Function, setUrlOrigin builds the origin object like this:

function setUrlOrigin(urlHost, override) {
  const origin = {
    domainName: urlHost,
    customOriginConfig: { port: 443, protocol: "https", sslProtocols: ["TLSv1.2"] },
    originAccessControlConfig: { enabled: false },
  };
  override = override ?? {};
  if (override.protocol === "http") {
    delete origin.customOriginConfig;
  }
  // ...
  cf.updateRequestOrigin(origin);
}

When protocol === "http", customOriginConfig is deleted. The expectation is that cf.updateRequestOrigin inherits the placeholder origin's customOriginConfig (which is http-only port 80). However, the presence of originAccessControlConfig: { enabled: false } appears to prevent this inheritance — CloudFront defaults to HTTPS:443 instead.

The KV store metadata confirms the route is correctly stored:

{
  "host": "my-alb-1234567890.us-east-2.elb.amazonaws.com",
  "origin": { "protocol": "http" }
}

And the placeholder origin is correctly configured:

{
  "CustomOriginConfig": {
    "HTTPPort": 80,
    "OriginProtocolPolicy": "http-only"
  }
}

But the ALB gets an HTTPS connection attempt on 443 → connection refused → 502.

Suggested fix

Don't include originAccessControlConfig for non-OAC origins. For HTTP custom origins it serves no purpose and interferes with customOriginConfig inheritance:

function setUrlOrigin(urlHost, override) {
  const origin = { domainName: urlHost };

  if (!override || override.protocol !== "http") {
    origin.customOriginConfig = { port: 443, protocol: "https", sslProtocols: ["TLSv1.2"] };
  }

  if (override?.connectionAttempts) origin.connectionAttempts = override.connectionAttempts;
  if (override?.timeouts) origin.timeouts = override.timeouts;
  if (override?.originAccessControlConfig) {
    origin.originAccessControlConfig = override.originAccessControlConfig;
  }

  cf.updateRequestOrigin(origin);
}

Workaround

Monkeypatch cf.updateRequestOrigin via edge.viewerRequest.injection to strip the empty originAccessControlConfig before it reaches CloudFront:

new sst.aws.Router("MyRouter", {
  domain: "example.com",
  edge: {
    viewerRequest: {
      injection: `
const _update = cf.updateRequestOrigin.bind(cf);
cf.updateRequestOrigin = (o) => {
  if (o.originAccessControlConfig && !o.originAccessControlConfig.enabled) delete o.originAccessControlConfig;
  _update(o);
};`,
    },
  },
});

Environment

  • SST: v4.7.3
  • AWS provider: 7.20.0
  • Region: us-east-2
  • CloudFront Function runtime: cloudfront-js-2.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions