# AWS appendix — ALB + ACM (docs only, never a shipped artifact) Scope: operators **already on EC2**. There, an ALB is trust-neutral (the engine is on AWS infrastructure regardless) and ACM renewal is genuinely zero-touch. This page is an appendix, not a recommendation to move to AWS — the shipped edges are Caddy ([topologies.md](topologies.md)) and BYO proxies ([reverse-proxies.md](reverse-proxies.md)). All ALB/NLB facts below are grounded in the [TLS/edge decision brief §3(c)](../superpowers/specs/2026-07-05-tls-edge-decision-brief.md). ## Never NLB-TLS Do not put the engine behind an NLB TLS listener. Two disqualifiers, ratified in [ADR-0011](../adr/0011-deployment-topology.md): 1. **Fixed 350-second idle timeout** that silently drops flows — any quiet WebSocket (hold, parked call, unidirectional stream) dies mid-call with no close frame. 2. **No HTTP context**: an NLB adds no `X-Forwarded-Proto/Host`, so the engine cannot reconstruct the public URL that `X-Twilio-Signature` is HMAC'd over ([Twilio's SSL-termination guidance](https://www.twilio.com/en-us/blog/developers/tutorials/building-blocks/handle-ssl-termination-twilio-node-js-helper-library)) — genuine traffic fails validation. ## The three mandatory ALB attribute overrides The defaults kill telephony. All three overrides are **mandatory**, not tuning: | Attribute | Value | Why | |---|---|---| | `idle_timeout.timeout_seconds` | `4000` | The default 60 s kills any quiet WS. 4000 is the ALB maximum — still finite, so the engine's app-level WS pings (`RUTSTER_WS_PING_SECS`, default 20) remain load-bearing here. | | `client_keep_alive.seconds` | `604800` | The 3600 s default can terminate the client connection under a **>1-hour call**. Set to the 7-day maximum. | | `routing.http.preserve_host_header.enabled` | `true` | Signature validation reconstructs the URL *as Twilio saw it* — the engine must receive the honest public `Host`, and its `RUTSTER_TRUSTED_PROXIES` must include the ALB's subnet CIDRs. | ## Copy-paste path (CLI) Assumes: engine node(s) running per [quickstart-docker.md](quickstart-docker.md) with the `caddy` service disabled (plaintext `:8080`), a VPC, two subnets, and a security group allowing `:443` from anywhere and `:8080` from the ALB. ```bash VPC=vpc-0123456789abcdef0 SUBNETS="subnet-0aaa subnet-0bbb" SG=sg-0ccc ALB_ARN=$(aws elbv2 create-load-balancer --name rutster-edge --type application \ --subnets $SUBNETS --security-groups $SG \ --query 'LoadBalancers[0].LoadBalancerArn' --output text) # THE THREE MANDATORY OVERRIDES — do not skip: aws elbv2 modify-load-balancer-attributes --load-balancer-arn "$ALB_ARN" --attributes \ Key=idle_timeout.timeout_seconds,Value=4000 \ Key=client_keep_alive.seconds,Value=604800 \ Key=routing.http.preserve_host_header.enabled,Value=true TG_ARN=$(aws elbv2 create-target-group --name rutster-node-1 \ --protocol HTTP --port 8080 --vpc-id "$VPC" \ --health-check-path /readyz \ --query 'TargetGroups[0].TargetGroupArn' --output text) aws elbv2 register-targets --target-group-arn "$TG_ARN" --targets Id=i-0node1 CERT_ARN=$(aws acm request-certificate --domain-name '*.pbx.example.com' \ --validation-method DNS --query CertificateArn --output text) # Create the DNS validation CNAME ACM reports, wait for ISSUED, then: LISTENER_ARN=$(aws elbv2 create-listener --load-balancer-arn "$ALB_ARN" \ --protocol HTTPS --port 443 \ --ssl-policy ELBSecurityPolicy-TLS13-1-2-2021-06 \ --certificates CertificateArn="$CERT_ARN" \ --default-actions Type=forward,TargetGroupArn="$TG_ARN" \ --query 'Listeners[0].ListenerArn' --output text) # Fleet growth: one target group + one host-header rule per node # (node-addressed placement — ADR-0011): aws elbv2 create-rule --listener-arn "$LISTENER_ARN" --priority 10 \ --conditions Field=host-header,Values=node-1.pbx.example.com \ --actions Type=forward,TargetGroupArn="$TG_ARN" ``` Point `*.pbx.example.com` (alias record) at the ALB. First call: identical to [quickstart-docker.md](quickstart-docker.md) — browser at your domain, Twilio webhook at `https://pbx.example.com/v1/trunk/webhook`. The `--ssl-policy` above serves TLS 1.2+1.3 — never pick a 1.3-only policy (Twilio's TLS 1.3 client support is undocumented; see the invariants in [certificates.md](certificates.md)). ## WebRTC media does not ride the ALB The ALB carries HTTPS/WSS only. WebRTC media UDP goes **direct to each node**: give every engine node an Elastic IP, open the media UDP range in the node's security group, and set `RUTSTER_MEDIA_ADVERTISED_IP` to that EIP. ## Caps and traps - **Hard cap: 100 target groups per ALB.** With one TG per node (host-header routing), that is your fleet ceiling per ALB. - **AWS fleet rotation can still cut a multi-hour call** — ALB infrastructure is replaced under maintenance without regard for your WS lifetimes. Accepted residual risk; there is no attribute for it. - Health checks target `/readyz` (503 while draining or at the admission cap) — scale-in must be drain-then-terminate: deregister the target, wait out `RUTSTER_DRAIN_DEADLINE_SECS`, then terminate the instance.