I Deployed a `coturn` Server to AWS

#coding #theWeb #learning #devOps #aws

Thanks to, last night I successfully deployed my first coturn server on an aws ec2 instance. Why you might ask? I am building a thing which needs a WebRTC Peer Connection! Everything in the guide worked as advertised, but I want to have this repeatable. While Terraform is no longer the open source sweetheart it was when I started learning it 6 months ago, I wanted to build on the little knowledge I have of it. Plus I’m using it for personal reasons so it’s still fine as far as licenses are concerned.

Scripting the setup

please ignore the broken syntax highlighting and random backticks. There’s a bug somewhere and I’m in the process of fixing it but the bug is breaking my whole blog so I’d rather this one post be a bit broken than the whole blog. Thanks!

Maybe there is a better way, but it seems straightforward to add an init template that runs when the instance starts. The template is a bash script that installs and configures the coturn server, as well as certbot as the article above suggests. One thing that will need to be done in the terraform is opening the proper ports. The default security group won’t have the proper config so make sure not to skip that part, otherwise you’ll see timeout errors on the testing site.

You can choose to skip user credentials on the turnserver depending on your use case. I’m going to set them up via the suggested CLI args in the “docs”, which as far as I can tell is limited to this very well commented .conf file.

This is meant for running as an init script on aws ec2. I am using ubuntu since I don’t want to deal withyum or building the coturn source.

Feel free to use the code below. No guarantees or warranties 😊.

The Script

TLDR: install coturn and certbot, configure them, and flip the ON switch!


# shellcheck disable=SC2154


echo 'Initializing turn server...'


set -e


#ubuntu needs root

sudo -i


echo 'Installing stuff...'

#install coturn and cerbot

apt-get update -y

apt-get install coturn certbot -y


echo 'Setting config...'

# enable the server

cat >> /etc/default/coturn << EOF




# start the service

systemctl start coturn


#get the ip of the current ec2 instace








# Don't forget any DNS related credentials


# or for an encrypted pw: 
# USER_INFO=$(turnadmin -k -u "$COTURN_USER" -r "$DOMAIN" -p "$COTURN_PASS" | head -n 1)


# set the config

cat >> /etc/turnserver.conf << EOF











echo 'Creating DNS record...'

# update the dns records at your DNS provider with the public ip

# wait so that we can ensure the dns record is created and propogated
# might not be necessary but seemed safer than not waiting

sleep 30


echo 'Creating cert...'

# set the cert

certbot certonly -n --agree-tos --standalone --preferred-challenges http \

--deploy-hook "systemctl restart coturn" \

-d "$FQDN" \

-m "$EMAIL"


# was facing an issue like this without copying the certs to a dir
# that coturn could read:

mkdir -p /etc/coturn/certs

cp "/etc/letsencrypt/live/$FQDN/cert.pem" "/etc/coturn/certs/$FQDN.cert"

cp "/etc/letsencrypt/live/$FQDN/privkey.pem" "/etc/coturn/certs/$FQDN.key"

chown turnserver -R "/etc/coturn/certs"

chmod 700 -R "/etc/coturn/certs"


service coturn restart


echo 'Done!'

The Terraform

And the terraform: TLDR: Spins up a keypair to be able to ssh into our ec2 instance, and the proper iam roles and permissions to do things in the least permissive way. A security group is also created with the proper ports open for our coturn server to talk to the rest of the web.

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"

  required_version = ">= 1.2.0"

provider "aws" {
  region = var.aws_region

locals {
  user_data = templatefile("${path.module}/init.tftpl", {
    subdomain        = var.subdomain
    domain           = var.domain
    username         = var.coturn_user
    password         = var.coturn_pass
    email            =
    # any extra vars needed for DNS

# key pair used to ssh in to the ec2 instance
resource "aws_key_pair" "deployer" {
  key_name   = "coturn_keypair"
  public_key = file(var.public_key)

resource "aws_instance" "app_server" {
  instance_type = "t3.nano"
  # ubuntu 22.10
  ami                  = "ami-0fc5d935ebf8bc3bc"
  key_name             = aws_key_pair.deployer.key_name
  iam_instance_profile =
  security_groups      = []
  root_block_device {
    delete_on_termination = true
  # init script
  user_data = local.user_data
  tags = {
    Name = "Coturn"

# IAM roles and policy docs

# IAM role for the instance profile
resource "aws_iam_role" "role" {
  name               = "coturn_iam_role_ec2"
  path               = "/"
  assume_role_policy = data.aws_iam_policy_document.assume_role.json

# attach role to an instance profile for use with the ec2 instance
resource "aws_iam_instance_profile" "coturn" {
  name = "coturn_iam_profile_ec2"
  role =
  tags = {
    Name = "CoturnAdmin"

# ec2 role
data "aws_iam_policy_document" "assume_role" {
  statement {
    sid    = 1
    effect = "Allow"
    principals {
      type        = "Service"
      identifiers = [""]

    actions = ["sts:AssumeRole"]

# security group to open ports
resource "aws_security_group" "coturn_sg" {
  name        = "coturn_sg"
  description = "security group for the coturn ec2 instance"

  ingress {
    from_port   = 3478
    to_port     = 3479
    protocol    = "tcp"
    cidr_blocks = [""]

  ingress {
    from_port   = 3478
    to_port     = 3479
    protocol    = "udp"
    cidr_blocks = [""]

  # tls
  ingress {
    from_port   = 5349
    to_port     = 5350
    protocol    = "tcp"
    cidr_blocks = [""]

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = [""]

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = [""]

And the tf vars file:

variable "aws_region" {
  description = "AWS region"
  type        = string
  default     = "us-east-1"

variable "public_key" {
  description = "path to AWS keypair public key"
  type        = string
  default     = "~/.ssh/"

variable "domain" {
  description = "the domain name of the server"
  type        = string

variable "subdomain" {
  description = "the subdomain of the server"
  type        = string

variable "username" {
  description = "the username for the lts creds"
  type        = string

variable "password" {
  description = "the pass for the lts creds"
  type        = string
variable "email" {
  description = "the email address for the ssl cert"
  type        = string

With these combined, you should be able to deploy a coturn server with the flick of the terraform apply wrist!

If you have a new A record on your DNS provider, that means at least that step worked.

If you need to SSH into the instance, make sure to open up SSH port 22 in the security group for your IP. You can do this in the terraform if you want, but I wouldn’t expect to need to get in to the server much except to grab the encrypted password. Also the AWS console offers a nice “My IP” option. I know it’s possible to code this into the terraform but it seemed like too much effort for not a lot gained.

Test the server with


These resources were extremely helpful in learning and implementing all of this WebRTC stuff:

And for actually implementing WebRTC in an application, specifically SvelteKit, these were helpful. I ended up using instead of ws as implemented in the example:

This was helpful too as I had never worked with websockets before:

Created on:
Last updated:

Webmention Replies

Be the first to webmention this post!

Link to this post in your webmentioned enabled content.

<< Go Back