Server support for audio and video calls with Prosody and coturn

· in instant messaging

Tested on 5 November 2026 in Docker: Prosody 13.0.7 on Debian 13 with coturn 4.18.0 (coturn/coturn image) and coturn 4.6.1 (Debian 13 package); prosodyctl check turn got a relay allocation from both.

XMPP clients such as Conversations can make encrypted audio and video calls, but on many networks the call only connects if your server offers a TURN relay. This guide adds one to a Prosody server with coturn and Prosody's built-in mod_turn_external, and shows how to test it.

It assumes you already run Prosody 13, for example set up with the Prosody server guide. The two files used here are turnserver.conf for coturn and prosody.cfg.lua for Prosody.

How a call finds a path

An XMPP call has two parts. The clients agree on the call through your XMPP server, using the Jingle protocol. The audio and video then travel between the two devices with WebRTC, and the media is encrypted with DTLS between the two clients. The XMPP server never carries the media itself.

The hard part is finding a network path between two phones that both sit behind routers. The clients try three kinds of path:

  • Direct. Two devices on the same Wi-Fi, or with public IPv6 addresses, can often reach each other without help.
  • STUN. A STUN server tells each device which public address and port its traffic comes from. On many home networks, that is enough for the two devices to reach each other.
  • TURN. On more restrictive networks, no direct path works. A TURN server then relays the encrypted media between the two devices.

The author of Conversations advises server admins to provide both STUN and TURN, so calls work on every kind of network. coturn does both. Clients find it through your XMPP server: they ask Prosody for "external services" (XEP-0215), and mod_turn_external answers with the TURN address and a temporary username and password. Prosody creates those credentials from a secret it shares with coturn, and coturn checks them with the same secret, so you never store per-user TURN passwords. By default the credentials are valid for one day.

Before you start: TURN on a home connection

A TURN server needs a public IP address that both callers can reach, and that is harder at home than on a rented server. Check these points first:

  • A public IPv4 address. Behind carrier-grade NAT (CGNAT), port forwarding does nothing, and a TURN server at home can't work. Run coturn on a small rented server instead and point Prosody at it.
  • A fixed or tracked IP. coturn behind a router must be told your public IP address in its configuration. If your provider changes that address, you have to update the line and restart coturn.
  • A router that forwards a UDP port range. The relay uses one UDP port per relayed connection, taken from a range you choose. The router must forward that range to the same port numbers on the server. Some home routers can't forward a large range, so this guide uses 501 ports instead of coturn's default of more than 16,000.
  • Protection for your own network. Prosody's documentation warns against running TURN inside a NAT or LAN. Anyone with valid credentials can ask the relay to send traffic to an address, including devices on your home network. The configuration below blocks all private address ranges for that reason.

If one of these is a problem, run coturn on a small rented server and leave Prosody at home. Only the turn_external_host line in Prosody changes.

Add a DNS A record (and AAAA if you use IPv6) for the TURN server, such as turn.example.org, pointing at your public IP. Prosody hands this name to clients and never connects to it itself, so it must be the public name, never 127.0.0.1 or a LAN address.

Install coturn

Debian 13 ships coturn 4.6.1. Install it on the machine that will run the relay:

sudo apt install coturn

On Debian, the systemd service runs turnserver -c /etc/turnserver.conf as the turnserver user, so that file is the one to edit. The coturn project also publishes a Docker image, coturn/coturn, which was at version 4.18.0 when this guide was tested.

Configure coturn

Generate a long random secret. Prosody and coturn must use exactly the same string:

openssl rand -hex 32

Download turnserver.conf and replace the four placeholders listed at its top: the TURN name, your public IP, the server's LAN IP and the secret. Then install it and keep the secret readable only by root and coturn:

sudo cp turnserver.conf /etc/turnserver.conf
sudo chown root:turnserver /etc/turnserver.conf
sudo chmod 640 /etc/turnserver.conf

The file has five parts.

Listening port

listening-port=3478
no-tls

3478 is the standard STUN and TURN port, and it is also the port mod_turn_external announces unless you set another. coturn listens on it over both UDP and TCP. no-tls turns off the TURN-over-TLS listener, which would need its own certificate. The call media is already encrypted between the clients. The Conversations author does recommend TURN over TLS on port 443 for the most restrictive networks, but that needs a second public IP address, which home connections rarely have.

Addresses behind your router

external-ip=203.0.113.10/192.168.1.10
min-port=49160
max-port=49660

external-ip maps the public address (left) to the server's LAN address (right). coturn tells clients to send relayed traffic to the public address, and your router passes it on. min-port and max-port set the relay range. coturn's default is 49152 to 65535, and a smaller range is easier to forward. A household needs far fewer than 501 relayed connections at the same time.

Authentication shared with Prosody

use-auth-secret
static-auth-secret=replace-with-a-long-random-secret
realm=turn.example.org
fingerprint

use-auth-secret switches coturn to time-limited credentials made from static-auth-secret, which is the scheme mod_turn_external uses. coturn needs a realm, and the TURN server's name works.

Blocking your own network

no-multicast-peers
denied-peer-ip=10.0.0.0-10.255.255.255
denied-peer-ip=172.16.0.0-172.31.255.255
denied-peer-ip=192.168.0.0-192.168.255.255
…

The denied-peer-ip lines stop the relay from sending traffic to private, loopback, link-local and CGNAT addresses. The downloadable file lists every range, for IPv4 and IPv6. coturn's own example configuration describes denied-peer-ip for this case, a TURN server behind NAT. In our test, coturn logged each blocked range at start-up. It also logged that it allows the LAN address from external-ip, which is the relay itself.

Hardening and logging

no-software-attribute
no-cli
log-file=stdout

no-software-attribute stops coturn from announcing its version. no-cli keeps the telnet admin console off. coturn 4.6.1 accepts this line silently. coturn 4.18.0 has the console off by default and logs "no-cli option is deprecated" for it, so you can delete the line on newer versions. With log-file=stdout, the log goes to the systemd journal, where journalctl -u coturn shows it.

Start coturn and make it start at boot:

sudo systemctl enable --now coturn
sudo systemctl status coturn

In our tests, both coturn 4.6.1 and 4.18.0 started with this file and listened on port 3478 over UDP and TCP. Older guides often include a no-dtls line. coturn 4.18.0 logs "Bad configuration format: no-dtls" for it, because the DTLS listener is now off unless you turn it on, so the line is left out here.

Router and firewall ports

Forward these ports from your router to the machine running coturn, with the same port numbers on both sides:

PortProtocolUsed by
3478UDP and TCPSTUN and TURN requests
49160–49660UDPRelayed media

With ufw on the server: sudo ufw allow 3478 and sudo ufw allow 49160:49660/udp. The router steps are in the port forwarding guide.

Point Prosody at coturn

On the Prosody server, edit /etc/prosody/prosody.cfg.lua. Enable the module in modules_enabled and set the three options in the global section, above the first VirtualHost line:

modules_enabled = {
	-- … other modules …
	"turn_external";
}

turn_external_host = "turn.example.org"
turn_external_port = 3478
turn_external_secret = "the-same-secret-as-in-turnserver.conf"

mod_turn_external has shipped with Prosody since 0.12, so older guides that install the turncredentials community module are out of date. Check the file and restart Prosody:

sudo prosodyctl check config
sudo systemctl restart prosody

If the secret is missing, Prosody logs "Failed to initialize: the 'turn_external_secret' option is not set in your configuration" and offers no TURN service.

Test the TURN server

Prosody can test the TURN server itself. It fetches credentials the same way a client does and asks coturn for a relay:

sudo prosodyctl check turn -v

In our test against both coturn versions, the output ended like this:

Relayed address 1: 192.168.96.2:49633
Success!

All checks passed, congratulations!

The relayed port falls inside the min-port to max-port range, which shows that coturn accepted Prosody's credentials. With a wrong secret, the same check failed with "TURN server failed to create allocation: 401". Our test also warned "STUN returned a private IP!", because the containers had no router in between. On a real home server that warning means external-ip is missing or wrong.

Prosody's documentation describes a fuller test that sends traffic through the relay to an outside STUN server. Use a public server for --ping, never your own:

sudo prosodyctl check turn -v --ping=stun.conversations.im

Then make a real call. Put one phone on mobile data with Wi-Fi off, and call a contact on another network. A call that connects between two different networks has found a path, and when the direct paths fail, that path is your relay. The XMPP Compliance Tester also has two tests for this setup, for external service discovery of STUN and of TURN.

Troubleshooting

  • "401" from prosodyctl check turn. The secrets in Prosody and coturn differ. Copy the string again, with no extra spaces or quotes, and restart both services.
  • Calls work on Wi-Fi but fail on mobile data. The direct path works, but the relay doesn't. Check that the UDP range is forwarded with the same port numbers and that external-ip holds your current public address. Prosody's documentation notes that intermittent call failures may point to a TURN problem, because many calls succeed without STUN or TURN.
  • Calls stopped working after a router restart. Your public IP may have changed. Update external-ip and the DNS record, then restart coturn.
  • Prosody doesn't offer the relay. Run sudo prosodyctl check features. It flags "Audio/video calls and P2P" until turn_external is loaded. Both callers' clients also need call support. See picking modern XMPP clients.
  • coturn doesn't start. Read journalctl -u coturn. Lines with "Bad configuration format" name the option coturn doesn't understand.

If you manage the server remotely, the SSH guide covers logging in securely.

Frequently asked questions

Do I need a TURN server for XMPP audio and video calls?

Not for every call, because devices on the same Wi-Fi or with public addresses can often connect directly, and STUN is enough on many home networks. On more restrictive networks, a call only connects through a TURN relay, so the Conversations author advises providing both STUN and TURN.

Which ports does coturn need for XMPP calls?

Port 3478 over UDP and TCP for STUN and TURN requests, plus the UDP relay range set by min-port and max-port. The configuration in this guide uses 49160 to 49660, forwarded to the same port numbers on the server.

Can I run coturn at home behind NAT?

Yes, if you have a public IPv4 address, set external-ip to your public and LAN addresses, forward the ports and block private ranges with denied-peer-ip. It can't work behind carrier-grade NAT, and in that case coturn belongs on a rented server.

What is mod_turn_external in Prosody?

It is a Prosody module, included since version 0.12, that tells clients where your TURN server is and gives them temporary credentials. It creates the credentials from a secret shared with coturn's static-auth-secret, and they are valid for one day by default.

How do I test my TURN server with Prosody?

Run sudo prosodyctl check turn -v, which fetches credentials and asks the TURN server for a relay. "Success!" with a relayed address means coturn accepted the credentials, and a 401 error means the secrets don't match.

Are calls through a TURN relay still encrypted?

Yes. The media is encrypted with DTLS between the two clients, and the relay only forwards the encrypted packets.

Sources

  1. Prosody: audio/video calls with a STUN/TURN server
  2. Prosody: setting up coturn
  3. Prosody: mod_turn_external
  4. Prosody 0.12.0 release notes
  5. coturn: example turnserver.conf
  6. coturn on GitHub
  7. Conversations: requirements for audio/video calls
  8. XEP-0215: External Service Discovery
  9. XEP-0167: Jingle RTP Sessions
  10. Snikket: firewall ports, on forwarding large UDP ranges