Skip to content

Commit e7e301d

Browse files
committed
Add self-hosted LiveKit AWS guide
1 parent 6bcc151 commit e7e301d

2 files changed

Lines changed: 748 additions & 0 deletions

File tree

docs/SELF_HOSTED_LIVEKIT_AWS.md

Lines changed: 372 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,372 @@
1+
# Self-Hosted LiveKit on AWS
2+
3+
Last updated: 2026-05-25
4+
5+
This document describes a practical AWS deployment shape for the official
6+
LiveKit Server using EC2, ECS on EC2, or EKS, with Network Load Balancers,
7+
ElastiCache Redis, security groups, and TURN. It is an architecture and
8+
operations guide, not a Terraform module.
9+
10+
## Goals
11+
12+
- Run the official LiveKit media server, not a custom SFU implementation.
13+
- Keep signaling behind TLS while exposing the WebRTC media ports that LiveKit
14+
advertises to clients.
15+
- Support single-node development, multi-node production, and Kubernetes/ECS
16+
variants from the same network model.
17+
- Use Redis for redundant multi-node deployments.
18+
- Keep security groups explicit enough that failed calls can be debugged from
19+
the port table.
20+
21+
## Core Assumptions
22+
23+
- Public clients connect to `wss://livekit.example.com`.
24+
- TURN uses a separate name such as `turn.example.com`.
25+
- API keys are generated and stored outside the repository.
26+
- LiveKit Server runs from the official binary or Docker image.
27+
- Production nodes have public routability for advertised ICE candidates, or
28+
clients are forced through TURN.
29+
30+
## First Decision
31+
32+
For production, prefer this order:
33+
34+
1. EKS with the official LiveKit Helm chart when the team already operates
35+
Kubernetes.
36+
2. ECS on EC2 when the team wants container orchestration without Kubernetes.
37+
3. Plain EC2 with Docker Compose/systemd for the smallest reliable footprint.
38+
39+
Avoid Fargate or private/serverless-only clusters for the media server. LiveKit
40+
needs direct network access for RTC ports, and the official Kubernetes guidance
41+
requires host networking with one LiveKit pod per node.
42+
43+
## Network Model
44+
45+
LiveKit is not only an HTTPS application. The API and WebSocket endpoint can sit
46+
behind a TLS-terminating load balancer, but RTC traffic must reach the LiveKit
47+
node and port that the server advertises in ICE candidates.
48+
49+
Recommended split:
50+
51+
- `livekit.example.com`: TLS endpoint for API and WebSocket signaling.
52+
- `turn.example.com`: TURN/TLS and TURN/UDP endpoint.
53+
- LiveKit nodes: directly reachable for ICE/UDP and ICE/TCP, unless the
54+
deployment intentionally uses TURN-only connectivity.
55+
- Redis: private only.
56+
57+
Redis enables distributed LiveKit routing. When Redis is configured, LiveKit
58+
uses it for room data and internal messaging so a client can connect to any
59+
node and still be bridged to the node hosting the room. A room still needs to
60+
fit on one LiveKit node.
61+
62+
## Port Matrix
63+
64+
| Purpose | Default | Direction | Public? | Notes |
65+
| --- | ---: | --- | --- | --- |
66+
| API and WebSocket | `7880/TCP` | Client to LiveKit through LB | No direct node exposure | Put behind TLS on `443`. |
67+
| ICE/UDP | `50000-60000/UDP` | Client to LiveKit node | Yes | Default RTC media range. Each participant can use multiple ports. |
68+
| ICE/TCP fallback | `7881/TCP` | Client to LiveKit node | Yes | LiveKit notes this cannot sit behind TLS/LB. |
69+
| ICE/UDP mux | `7882/UDP` or range | Client to LiveKit node | Yes | Optional alternative to the wide UDP range. Remove `port_range_start/end` when using it. |
70+
| TURN/TLS | `5349/TCP` | Client to TURN | Usually through NLB | Use `443` for best restrictive-firewall coverage if using a dedicated TURN LB/IP. |
71+
| TURN/UDP | `3478/UDP` | Client to TURN | Yes | Can also be `443/UDP` if not used by another UDP service. |
72+
| Redis | `6379/TCP` | LiveKit to Redis | No | Private subnets only. Prefer TLS and AUTH/RBAC. |
73+
| Prometheus | `6789/TCP` | Monitoring to LiveKit | No | Only enable from monitoring subnets/security groups. |
74+
| SSH | `22/TCP` | Admin to node | No public if possible | Prefer SSM Session Manager. |
75+
76+
Optional SIP/Ingress/Egress ports should be added only when those components
77+
are deployed.
78+
79+
## Load Balancers
80+
81+
Use an internet-facing Network Load Balancer for layer-4 traffic that must stay
82+
close to the transport layer. AWS NLB target groups support TCP, TLS, UDP,
83+
TCP_UDP, QUIC, and TCP_QUIC protocols.
84+
85+
Recommended listeners:
86+
87+
| DNS name | Listener | Target | Health check |
88+
| --- | --- | --- | --- |
89+
| `livekit.example.com` | `443/TLS` | LiveKit `7880/TCP` | `TCP:7880` |
90+
| `turn.example.com` | `443/TLS` or `5349/TLS` | LiveKit TURN TLS port | `TCP:7880` or a dedicated TCP check |
91+
| `turn.example.com` | `3478/UDP` or `443/UDP` | LiveKit TURN UDP port | Non-UDP check, usually `TCP:7880` |
92+
93+
If both API and TURN/TLS must use public port `443`, use separate NLBs or
94+
separate public IPs. NLB TLS can select certificates with SNI, but it is not an
95+
HTTP router and should not be treated like ALB host-based routing.
96+
97+
Do not plan to publish the default `50000-60000/UDP` media range through a
98+
single generic listener. Either expose the node media ports directly with
99+
security groups, use an explicitly tested UDP mux design, or rely on TURN for
100+
restricted networks.
101+
102+
For UDP target groups, use a TCP or HTTP health check. AWS documents that UDP
103+
and QUIC services are checked with non-UDP health checks.
104+
105+
## Security Groups
106+
107+
Create separate security groups for the NLB, LiveKit nodes/tasks, Redis, and
108+
monitoring.
109+
110+
### NLB Security Group
111+
112+
Inbound:
113+
114+
- `443/TCP` from `0.0.0.0/0` and `::/0` for API TLS.
115+
- `443/TCP` or `5349/TCP` from `0.0.0.0/0` and `::/0` for TURN/TLS.
116+
- `3478/UDP` or `443/UDP` from `0.0.0.0/0` and `::/0` for TURN/UDP.
117+
118+
Outbound:
119+
120+
- To the LiveKit node security group on `7880/TCP`.
121+
- To the LiveKit node security group on the chosen TURN/TCP and TURN/UDP ports.
122+
123+
### LiveKit Node Security Group
124+
125+
Inbound:
126+
127+
- `7880/TCP` from the NLB security group.
128+
- `7881/TCP` from `0.0.0.0/0` and `::/0` if ICE/TCP fallback is enabled.
129+
- `50000-60000/UDP` from `0.0.0.0/0` and `::/0` if using the default media range.
130+
- The configured `rtc.udp_port` UDP port or range from `0.0.0.0/0` and `::/0`
131+
if using UDP mux instead of the default media range.
132+
- TURN/TCP and TURN/UDP ports from the NLB security group, or from the internet
133+
if TURN is exposed directly.
134+
- `6789/TCP` from the monitoring security group if Prometheus is enabled.
135+
- `22/TCP` only from a trusted admin CIDR, or omit it and use SSM.
136+
137+
Outbound:
138+
139+
- `6379/TCP` to the Redis security group.
140+
- `443/TCP` to the internet for image pulls, certificates, package updates, and
141+
optional STUN discovery.
142+
- UDP/TCP ephemeral outbound as required by the operating system and LiveKit
143+
media flows.
144+
145+
### Redis Security Group
146+
147+
Inbound:
148+
149+
- `6379/TCP` only from LiveKit node/task/pod security groups.
150+
151+
Outbound:
152+
153+
- Default VPC egress is normally enough.
154+
155+
## Redis
156+
157+
Use Amazon ElastiCache for Valkey or Redis OSS.
158+
159+
Starter production shape:
160+
161+
- Replication group, cluster mode disabled.
162+
- Multi-AZ with automatic failover.
163+
- One primary and at least one replica in a different Availability Zone.
164+
- In-transit encryption enabled.
165+
- AUTH token or RBAC enabled.
166+
- Private subnets only.
167+
168+
LiveKit does not need Redis to be public. Store the endpoint and credentials in
169+
AWS Secrets Manager, SSM Parameter Store, or the platform-native secret system.
170+
171+
## LiveKit Configuration
172+
173+
Baseline config:
174+
175+
```yaml
176+
port: 7880
177+
log_level: info
178+
179+
rtc:
180+
tcp_port: 7881
181+
port_range_start: 50000
182+
port_range_end: 60000
183+
use_external_ip: true
184+
185+
redis:
186+
address: <elasticache-primary-endpoint>:6379
187+
username: default
188+
password: <redis-password>
189+
tls:
190+
enabled: true
191+
insecure: false
192+
server_name: <elasticache-primary-endpoint>
193+
194+
keys:
195+
<api-key>: <api-secret>
196+
197+
turn:
198+
enabled: true
199+
domain: turn.example.com
200+
udp_port: 3478
201+
tls_port: 5349
202+
external_tls: true
203+
relay_range_start: 1024
204+
relay_range_end: 30000
205+
206+
prometheus_port: 6789
207+
```
208+
209+
Only set `turn.external_tls: true` when a layer-4 load balancer terminates TLS
210+
before forwarding traffic to LiveKit. If LiveKit terminates TURN/TLS itself,
211+
provide `cert_file` and `key_file` instead.
212+
213+
UDP mux alternative:
214+
215+
```yaml
216+
rtc:
217+
tcp_port: 7881
218+
udp_port: 7882-7890
219+
use_external_ip: true
220+
```
221+
222+
When using `rtc.udp_port`, remove `port_range_start` and `port_range_end`.
223+
LiveKit recommends a UDP mux range at least as large as the node vCPU count for
224+
better performance.
225+
226+
## EC2 Deployment
227+
228+
Use this for the simplest production-capable setup.
229+
230+
1. Create public subnets in at least two Availability Zones.
231+
2. Create the NLB listeners and target groups from this document.
232+
3. Create an ElastiCache replication group in private subnets.
233+
4. Launch compute-optimized EC2 instances with enhanced networking.
234+
5. Run LiveKit with Docker host networking or the native binary.
235+
6. Register instances in the NLB target groups.
236+
7. Configure Auto Scaling lifecycle hooks or deployment automation to send
237+
`SIGTERM` and wait for LiveKit draining before terminating instances.
238+
239+
Docker Compose shape:
240+
241+
```yaml
242+
services:
243+
livekit:
244+
image: livekit/livekit-server:<pinned-version>
245+
network_mode: host
246+
restart: unless-stopped
247+
command: --config /etc/livekit/livekit.yaml
248+
volumes:
249+
- /etc/livekit/livekit.yaml:/etc/livekit/livekit.yaml:ro
250+
```
251+
252+
Pin the LiveKit image version that was tested in staging. Avoid `latest` in
253+
production.
254+
255+
## ECS on EC2 Deployment
256+
257+
Use ECS only on EC2 for the media server.
258+
259+
Recommended ECS settings:
260+
261+
- Launch type or capacity provider: EC2.
262+
- Task network mode: `host`.
263+
- Placement: one LiveKit task per EC2 instance.
264+
- Placement constraint: `distinctInstance` or a capacity model that enforces
265+
one task per host.
266+
- Target groups: register the ECS service with the API/TURN target groups.
267+
- Secrets: inject LiveKit config or sensitive values from Secrets Manager/SSM.
268+
269+
Host networking is supported for ECS tasks on EC2, but not on Fargate. AWS also
270+
notes that host mode prevents running multiple copies of the same task on one
271+
host when the same ports are required. That limitation matches LiveKit's
272+
networking model.
273+
274+
Use `awsvpc` only after explicit validation. ECS supports port ranges, but
275+
LiveKit's Docker guidance prefers host networking for optimal media behavior.
276+
277+
## EKS Deployment
278+
279+
Use the official LiveKit Helm chart when deploying to EKS.
280+
281+
EKS requirements:
282+
283+
- AWS Load Balancer Controller installed if using AWS-managed ingress/load
284+
balancers from Kubernetes resources.
285+
- LiveKit pods with host networking.
286+
- One LiveKit pod per node.
287+
- Dedicated or labeled node group for LiveKit nodes.
288+
- Publicly routable nodes for direct ICE, or TURN-only connectivity.
289+
- External ElastiCache Redis, not an in-cluster single pod Redis for production.
290+
- Long termination grace period so LiveKit can drain active rooms.
291+
292+
Operational pattern:
293+
294+
1. Create or reuse an EKS cluster with public ingress for LiveKit.
295+
2. Create a LiveKit node group sized for network bandwidth and CPU.
296+
3. Install Redis externally with Multi-AZ.
297+
4. Install the LiveKit Helm chart with Redis, TURN, and RTC settings.
298+
5. Verify that pod scheduling keeps one LiveKit pod per node.
299+
6. Verify NLB/Ingress resources and DNS records.
300+
7. Run connect, TURN-only, weak-network, and multi-participant tests before
301+
production traffic.
302+
303+
## DNS and Certificates
304+
305+
Recommended records:
306+
307+
- `livekit.example.com` -> API NLB alias record.
308+
- `turn.example.com` -> TURN NLB alias record.
309+
310+
Use ACM certificates for NLB TLS termination. If LiveKit terminates TURN/TLS
311+
itself, provision the TURN certificate onto the node or into the Kubernetes
312+
secret expected by the Helm chart.
313+
314+
## Verification Checklist
315+
316+
Before production:
317+
318+
- `livekit.example.com` resolves to the API NLB.
319+
- `turn.example.com` resolves to the TURN NLB or direct TURN endpoint.
320+
- NLB target groups are healthy.
321+
- LiveKit can connect to Redis over TLS.
322+
- A generated token can connect to `wss://livekit.example.com`.
323+
- A browser/mobile client can join, publish, subscribe, leave, and reconnect.
324+
- UDP media succeeds without TURN from a normal network.
325+
- TURN/UDP succeeds from a restricted test network.
326+
- TURN/TLS succeeds from a network that blocks UDP.
327+
- ICE/TCP fallback is verified if `rtc.tcp_port` is enabled.
328+
- Redis failover is tested in staging.
329+
- Node termination sends `SIGTERM` and LiveKit drains instead of dropping rooms.
330+
- Prometheus metrics are scraped from a private monitoring path only.
331+
332+
Useful client tests:
333+
334+
- Normal Wi-Fi/cellular connection.
335+
- VPN or corporate network.
336+
- UDP-blocked network.
337+
- TURN-only forced test.
338+
- Multi-participant room.
339+
- Long-running room soak.
340+
341+
## Common Failure Modes
342+
343+
- Clients connect to WebSocket but media never flows: RTC UDP/TCP ports are not
344+
reachable, or LiveKit advertised a private IP.
345+
- TURN/TLS fails behind corporate networks: the public TURN port is not `443`,
346+
the certificate domain does not match, or TLS is terminated in the wrong
347+
place.
348+
- Multi-node rooms behave inconsistently: Redis is missing, unreachable, or
349+
configured without the same credentials on all LiveKit nodes.
350+
- NLB UDP target is unhealthy: health checks are pointed at UDP instead of a
351+
TCP/HTTP health path.
352+
- Rolling deploys drop calls: compute is terminated without allowing LiveKit's
353+
native draining behavior to complete.
354+
- EKS schedules multiple LiveKit pods on one node: host networking and port
355+
ownership are not being respected in scheduling constraints.
356+
357+
## References
358+
359+
- [LiveKit self-hosting overview](https://docs.livekit.io/transport/self-hosting/)
360+
- [LiveKit deployment guide](https://docs.livekit.io/transport/self-hosting/deployment/)
361+
- [LiveKit ports and firewall](https://docs.livekit.io/transport/self-hosting/ports-firewall/)
362+
- [LiveKit virtual machines guide](https://docs.livekit.io/transport/self-hosting/vm/)
363+
- [LiveKit Kubernetes guide](https://docs.livekit.io/transport/self-hosting/kubernetes/)
364+
- [LiveKit distributed multi-region guide](https://docs.livekit.io/transport/self-hosting/distributed/)
365+
- [LiveKit config sample](https://github.com/livekit/livekit/blob/master/config-sample.yaml)
366+
- [AWS Network Load Balancer overview](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/introduction.html)
367+
- [AWS NLB listeners](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-listeners.html)
368+
- [AWS NLB target group health checks](https://docs.aws.amazon.com/elasticloadbalancing/latest/network/target-group-health-checks.html)
369+
- [Amazon ECS task networking on EC2](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-networking.html)
370+
- [Amazon ECS host network mode](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/networking-networkmode-host.html)
371+
- [ElastiCache in-transit encryption](https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/in-transit-encryption.html)
372+
- [ElastiCache Multi-AZ automatic failover](https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/AutoFailover.html)

0 commit comments

Comments
 (0)