Self-hosting LiveKit on AWS boils down to one EC2 instance, a domain with two DNS records, and a security group that actually lets UDP through. The official config generator handles nearly everything else. The tricky part, and where most deployments stumble, is on the AWS side.

That's worth saying up front, because LiveKit's own "Running LiveKit on AWS" post is from 2021 and carries a note saying it's out of date. The ports it lists no longer match what the server uses. This guide follows the current LiveKit VM deployment docs and fills in the AWS bits they skip: which instance to pick, which subnet to use, which IP to assign, and what it all costs once traffic starts flowing.

The short version

Run livekit/generate on your laptop, paste the cloud-init file into the EC2 User data field, attach an Elastic IP, open UDP 50000-60000 plus five other ports, and point two DNS records at the instance. Use a compute-optimized instance in a public subnet. Never put the media server behind a NAT gateway or an ALB.

Self-hosted vs LiveKit Cloud

Before you build anything, check that self-hosting is actually the right call. LiveKit Cloud is good, and for plenty of teams it's the cheaper option once you count engineering time.

The LiveKit server is open source under Apache 2.0. The current release as of this writing is v1.13.7, published on 14 September 2026. You get the same core SFU that LiveKit Cloud is built on, but some things stay behind on the Cloud side.

What self-hosting gives you

  • No per-minute billing.
  • Media that never leaves your AWS account.
  • Full control over versions and configuration.

What stays on LiveKit Cloud

  • The dashboard and session analytics. Self-hosted gives you a Prometheus metrics endpoint and not much else, so you build your own Grafana boards or grab one of the community dashboards.
  • Enhanced noise cancellation. The Krisp-based models LiveKit ships are tied to LiveKit Cloud. Self-hosted voice agents need another option: an ai-coustics licence, or an open source plugin such as the rnnoise plugin proposed for the LiveKit agents repo.
  • One room across several servers. On a self-hosted cluster, every room lives on a single node. Adding servers gives you more rooms, not bigger ones.

For voice AI agent stacks, that trade-off is often the whole reason to self-host, and it's also why teams with data residency rules end up here. If you're still choosing a media server, we compared LiveKit with a finished meeting product in LiveKit vs Jitsi, and our LiveKit alternatives roundup covers the wider field, both self-hosted and managed.

The cost maths, honestly

This is the part most "self-host and save" posts skip. Self-hosting removes LiveKit's usage fees. It does not remove bandwidth fees, because AWS charges for data leaving EC2.

$0.09 / GB AWS egress for the first 10 TB each month, after the first 100 GB free
$0.12 / GB LiveKit Cloud overage on the Ship plan
$0.10 / GB LiveKit Cloud overage on the Scale plan

The difference per GB is small. So the answer depends on what you're sending.

Voice is cheap to move

An Opus audio stream is roughly 32 kbps, about 0.24 MB per minute in each direction. A month of 100,000 one-to-one voice agent minutes is on the order of 50 GB of egress, which fits inside the AWS free 100 GB. On LiveKit Cloud's Ship plan ($50 a month with 5,000 agent session minutes included, then $0.01 a minute), the same 100,000 minutes of cloud-hosted agent sessions come to roughly $1,000 before WebRTC minutes. A single c7i.xlarge running LiveKit costs about $130 a month on demand in us-east-1. Your agent workers and model APIs cost money either way, so leave them out of the comparison.

Video is not cheap

In LiveKit's own benchmark, a 150-person 720p meeting pushed 93 MBps out of a single 16-core server. At AWS rates that's roughly 335 GB, or about $30, for every hour that meeting runs. Self-hosting still saves the per-minute fees on video, but bandwidth becomes the biggest line on the bill, and it's almost the same number wherever you run it.

Before you start

Prerequisites

  • An AWS account with permission to launch EC2 instances, edit security groups, and allocate Elastic IPs.
  • If you hit a vCPU quota error when launching a compute-optimized instance, follow our guide to increasing the AWS vCPU quota.
  • A domain you control, with two subdomains free: one for LiveKit (e.g. livekit.example.com) and one for TURN (turn.example.com). Route 53 works, and so does any other DNS provider.
  • Docker on your own machine, to run the config generator.
  • An SSH key pair in the region you deploy to.

Two subdomains, not one

The generated setup runs TURN/TLS on port 443 alongside HTTPS, and Caddy tells them apart by hostname. If you reuse one name for both, TURN/TLS won't work. You'll only notice when someone on a locked-down corporate network can't join.

Deploy LiveKit on EC2

Six steps, from an empty folder on your laptop to a server you've load-tested.

Step 1: Generate the configuration

On your own machine, in an empty folder:

docker pull livekit/generate
docker run --rm -it -v$PWD:/output livekit/generate

It asks for your two domains and whether you want Egress or Ingress, then writes a folder containing livekit.yaml, docker-compose.yaml, caddy.yaml, redis.conf, and either init_script.sh or a cloud_init.xxxx.yaml file. It also prints an API key and secret. Save those somewhere safe now, because every token your app issues is signed with them.

Choose the cloud-init option. LiveKit tests it on Ubuntu and Amazon Linux, and on EC2 it means the server installs itself during the first boot.

Step 2: Create the security group

Create a new security group before you launch the instance. These are the ports the generated stack actually listens on:

Port Protocol Used for Needed
443TCPHTTPS signalling and TURN/TLSAlways
80TCPLet's Encrypt certificate issuance by CaddyAlways
7881TCPWebRTC over TCP, the fallback when UDP is blockedAlways
3478UDPTURN/UDPAlways
50000-60000UDPWebRTC mediaAlways
1935TCPRTMP IngressIngress only
7885UDPWHIP IngressIngress only
22TCPSSHYour IP only

Ignore the 2021 port list

Any guide that tells you to open 7880, 7881 and a single UDP port 7882 and nothing else is using the layout from LiveKit's 2021 post. The current server uses the 50000-60000 range for media, and a security group that leaves it closed produces the most common symptom of a broken self-hosted install: the room joins, and there's no audio or video.

Step 3: Launch the instance

In the EC2 console:

  1. Choose Launch instance and pick a current Ubuntu Server LTS AMI.
  2. Pick a compute-optimized instance type. See the sizing section below; c7i.xlarge is a reasonable first choice.
  3. Under Network settings, choose a public subnet and attach the security group from Step 2.
  4. Give the root volume at least 20 GB, or more if you enabled Egress, since recordings are written locally before upload.
  5. Open Advanced details and paste the full contents of the cloud_init.xxxx.yaml file into User data.
  6. Launch.

Then allocate an Elastic IP and associate it with the instance. This is not optional. A normal public IP changes every time the instance stops and starts, and your DNS records and TLS certificates go with it.

Step 4: Point DNS at the Elastic IP

Create two A records, one for each subdomain, both pointing at the Elastic IP. Caddy requests certificates for both names on first boot. If DNS isn't resolving yet, issuance fails and Caddy retries, so it's fine to launch first and add the records straight after.

Step 5: Check it's running

SSH in and look at the service the init script created:

sudo systemctl status livekit-docker
cd /opt/livekit
sudo docker compose ps
sudo docker compose logs -f livekit

You want three containers up (livekit, caddy and redis), and Caddy's log should show a certificate obtained for both domains. Then run LiveKit's connection test against wss://livekit.example.com. It checks signalling, UDP, TCP and TURN separately, so it tells you which path is broken, not just that something is.

Step 6: Test with real load

Install the LiveKit CLI (lk) on your machine, register the server with the key and secret from Step 1, and put some traffic through it:

lk project add --url wss://livekit.example.com --api-key <key> --api-secret <secret> my-aws-livekit
lk load-test --project my-aws-livekit --room test-room --video-publishers 8 --subscribers 20 --duration 2m

Watch htop and the instance's network graph in CloudWatch while it runs. This is how you find out whether the instance size is right, before real users do.

Upgrading later

The version is pinned in /opt/livekit/docker-compose.yaml. Change the livekit/livekit-server image tag, then docker compose pull and docker compose up -d. Pin exact versions rather than latest, and read the release notes first. v1.10.0 shipped a bug that broke browser connections on hosts without IPv6. It was fixed, but anyone on latest took the broken build without choosing to.

Sizing the instance

LiveKit's docs say compute-optimized instances suit it best, and on AWS that means the C family. The SFU forwards packets without re-encoding them, so it needs steady CPU and lots of network, and very little memory. A starting point:

c7i.large

2 vCPU, about $0.09 an hour

Development and a handful of rooms.

c7i.4xlarge and up

16 vCPU and more

Once a single room goes past a few dozen video participants. The 150-person 720p benchmark ran at 85% CPU on a 16-core machine, so a room of that size needs a big instance.

The trap is network bandwidth

AWS advertises smaller instances as "up to 12.5 Gbps", but that's a burst figure. The c7i.xlarge has a baseline of about 1.56 Gbps, and sustained video runs at baseline once the burst credits run out. A large video room can use up the burst in minutes and then drop packets while the CPU graph still looks fine. If you run video, size for the baseline figure in the instance specs, not the headline number.

What breaks on AWS

Nearly every AWS-specific failure comes down to one thing: WebRTC needs clients to reach the server's real IP directly over UDP, and several standard AWS patterns get in the way of that. If your install is misbehaving, find the symptom here first.

Symptom Likely cause Fix
Room joins, no audio or videoUDP 50000-60000 closed, or the instance sits behind a NAT gatewayOpen the range; use a public subnet and an Elastic IP
Clients get an address they can't reachuse_external_ip is offSet use_external_ip: true in livekit.yaml
Calls break once there are several nodesMedia is going through a load balancerLoad-balance signalling only; send media to each node's public IP
Users on corporate networks can't joinOnly outbound TCP 443 is allowedMake sure TURN/TLS on 443 still reaches LiveKit

The instance is in a private subnet

A common VPC layout puts application servers in private subnets and sends outbound traffic through a NAT gateway. That does not work for an SFU. Clients can't start UDP flows inward through a NAT gateway, and LiveKit's external IP discovery can end up advertising the NAT gateway's address, which nothing can connect to. There's a documented LiveKit issue showing this happens even with use_external_ip set to false when TURN servers are configured: LiveKit discovers the NAT gateway's public IP via TURN reflection and advertises it as an srflx candidate anyway.

Fix: Put LiveKit nodes in a public subnet with their own public IPs.

use_external_ip is off

On EC2 the instance only sees its private address. Your public IP is mapped to it outside the VM. With use_external_ip: true in livekit.yaml, LiveKit uses STUN to find its public address and advertises that to clients. The generated config sets it for you. If you write your own config, this is the line people most often leave out:

rtc:
  tcp_port: 7881
  port_range_start: 50000
  port_range_end: 60000
  use_external_ip: true

Media behind a load balancer

An Application Load Balancer only handles HTTP and WebSockets. That's fine for signalling and useless for media. A Network Load Balancer can pass UDP, but WebRTC media has to reach the specific node hosting the room, so a load balancer that spreads it across nodes breaks calls. This is also why the LiveKit Kubernetes docs require host networking, one LiveKit pod per node, and no private clusters.

Fix: Load-balance signalling if you want to, and let media go directly to each node's public IP.

Users on strict corporate networks can't join

Some networks allow only outbound TCP 443. For those users, TURN/TLS on 443 is the only path. The generated stack handles this by letting Caddy route TLS traffic for the TURN hostname to LiveKit's built-in TURN server.

Fix: If you've replaced Caddy with your own proxy, make sure that routing still works. For large deployments, some teams run a dedicated TURN tier instead. Coturn works with LiveKit like any other standard TURN server, and we keep a ready-made Coturn image for that setup.

Scaling past one server

A single server takes you further than you might expect. When you need more, LiveKit scales out by adding nodes that share one Redis. These are the four pieces to plan.

Redis

Point every node at the same Redis. On AWS that's usually ElastiCache in the same VPC, which also takes Redis off the media node. LiveKit uses it to route each new room to a node and to find which node hosts an existing room.

Nodes

Each node is a copy of the Step 3 instance, with its own Elastic IP and the same keys. An Auto Scaling group works, as long as scale-in waits for rooms to finish. LiveKit's Helm chart sets a five-hour termination grace period for exactly that reason.

Egress

Recording and streaming run as a separate service. LiveKit recommends at least 4 CPUs and 4 GB of memory for each Egress instance, and a RoomComposite recording can use anywhere from 2 to 6 CPUs. Give Egress its own instances so a recording can't take CPU from live calls.

EKS

The official Helm chart works on EKS with the AWS Load Balancer Controller for signalling. ACM can terminate TLS for the main domain, but LiveKit's docs still call for a separate certificate that the embedded TURN/TLS server loads itself. Only move to Kubernetes if you already run it. For two or three nodes, plain EC2 is easier to operate.

More nodes means more rooms, not bigger rooms

If one room needs more than one large instance can handle, that's the point where LiveKit Cloud's multi-node rooms are worth paying for.

If you want LiveKit on AWS without owning the ports, Redis, TURN and upgrades yourself, our WebRTC consulting team builds and runs LiveKit deployments as well as Jitsi. For background on why the SFU model behaves the way it does, see our WebRTC architecture explainer and the wider comparison of open source WebRTC media servers.

Frequently Asked Questions

Can you self-host LiveKit?

Yes. The LiveKit server is Apache 2.0 licensed, and the current release is v1.13.7 (September 2026). You run the same core SFU that LiveKit Cloud uses.

What ports does self-hosted LiveKit need?

The generated stack needs TCP 443, TCP 80, TCP 7881, UDP 3478, and UDP 50000-60000. Ingress adds TCP 1935 and UDP 7885. SSH on TCP 22 should be restricted to your IP.

Is self-hosting LiveKit cheaper than LiveKit Cloud?

It depends on volume and what you're sending. For voice, a c7i.xlarge at about $130 a month handles a lot of concurrent calls, and voice egress is small enough to fit inside AWS's free tier. For video, bandwidth dominates the bill and self-hosting's savings are slimmer.

What EC2 instance should I use for LiveKit?

A c7i.xlarge (4 vCPU, 8 GiB) is a reasonable production starting point for voice agents and small video meetings. Size for the instance's baseline bandwidth (1.56 Gbps for c7i.xlarge), not the "up to 12.5 Gbps" burst figure.

Can I run LiveKit behind an AWS load balancer?

Only for signalling. WebRTC media must reach the specific node hosting the room, so an ALB or NLB that spreads media across nodes will break calls. Send media directly to each node's public IP.

What features do I lose by self-hosting LiveKit?

The dashboard and session analytics, Krisp-based enhanced noise cancellation (which requires LiveKit Cloud auth), and multi-node rooms (a single room must fit on one node). Everything else, including the SFU, Egress, Ingress and SIP, is available self-hosted.

Why does my self-hosted LiveKit connect but show no video?

Almost always blocked UDP. Check that UDP 50000-60000 is open, that use_external_ip: true is set in livekit.yaml, and that the instance is in a public subnet with an Elastic IP, not behind a NAT gateway.

Need LiveKit Running on AWS, Properly?

We design, deploy and support self-hosted LiveKit and Jitsi on AWS: sizing, TURN, Redis, recording and upgrades, in your own account.

Talk to Our WebRTC Engineers