coturn stages the cert at start, so a renewal needs a container restart. Record that the certbot deploy hook (which lives outside this repo) now restarts coturn alongside prosody-xmpp. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5.3 KiB
Prosody XMPP Server
Dockerized Prosody XMPP server with MySQL backend.
Prerequisites
- Docker and Docker Compose
- External MySQL network:
mysqlgit_mysql_network - Environment variables configuration (see Configuration section)
Configuration
This setup uses environment variables for configuration. Create a .env file in the project root with:
# XMPP Configuration
XMPP_DOMAIN=xmpp.guschin.info
XMPP_USER=mish
XMPP_PASSWORD=your_password
# MySQL Configuration
MYSQL_HOST=mysql
MYSQL_USER=prosody
MYSQL_PASSWORD=your_mysql_password
MYSQL_DATABASE=prosody
Starting the Server
Run from the project directory:
docker-compose up -d
This will:
- Build and start the Prosody container
- Connect to external MySQL network (
mysqlgit_mysql_network) - Wait for MySQL to be ready
- Generate self-signed SSL/TLS certificates (if not present)
- Configure Prosody with MySQL backend
Access Points
| Service | Port | Description |
|---|---|---|
| C2S (Client to Server) | 5222 | XMPP client connections |
| S2S (Server to Server) | 5269 | XMPP federation |
| Component Protocol | 5347 | External components |
| HTTP/BOSH/WebSocket | 5280 | Web-based connections |
| HTTPS | 5281 | Secure web connections |
| STUN/TURN | 3478 (UDP+TCP) | NAT traversal for audio/video calls |
| STUN/TURN over TLS | 5349 (UDP+TCP) | Encrypted TURN (turns:) |
| TURN relay range | 49160-49200 (UDP) | Media relay ports |
Features
- Virtual Host:
xmpp.guschin.info - Multi-User Chat (MUC):
muc.xmpp.guschin.info - Storage: MySQL backend with SQL storage
- Admin User:
admin@guschin.info - Health Checks: Automatic container health monitoring
- Memory Limit: 200M
- Auto-restart: Container restarts unless stopped
Enabled Modules
Core modules: roster, saslauth, tls, dialback, disco, carbons, pep, private, blocking, vcard, version, uptime, time, ping, register, last_activity, admin_adhoc, muc_mam
Data Persistence
Volumes are mounted to persist data:
./data/prosody→/var/lib/prosody./logs/prosody→/var/log/prosody./data/prosody/configuration→/etc/prosody/conf.d
Connecting Clients
Use any XMPP client with:
- Server: xmpp.guschin.info
- Port: 5222
- Username: your configured user
- Domain: xmpp.guschin.info
Audio/Video Calls (Jingle)
Voice and video calls use Jingle (XEP-0166/0167/0176). Prosody only carries the signalling; media flows directly between clients, falling back to a TURN relay when NAT prevents a direct path.
Two pieces make this work:
- coturn (
coturncontainer) — the STUN/TURN server. Runs with host networking because a relay needs its whole UDP port range reachable. mod_turn_externalin Prosody — advertises coturn to clients over XEP-0215 and issues time-limited TURN credentials.
Configuration
TURN_SECRET in .env must match static-auth-secret in
coturn/turnserver.conf. .env is gitignored, so
these values are set per-deployment and not committed:
TURN_HOST=guschin.info
TURN_SECRET=<same value as static-auth-secret>
TURN_HOST is guschin.info, not turn.guschin.info: turns: (TLS) clients
validate the hostname against the certificate, and only guschin.info is in
the current cert's SANs. To use turn.guschin.info instead, reissue the
certificate with that name added and update both TURN_HOST and the
external-ip/cert settings accordingly.
turnserver.conf pins the server's public address in three places:
listening-ip, relay-ip, and external-ip. Update all three if the
server IP changes, otherwise coturn advertises relay candidates that peers
cannot reach. Pinning them also stops coturn from auto-discovering the Docker
bridge interfaces and relaying on unreachable private addresses.
Verifying
Check that Prosody advertises the service and coturn answers:
docker logs coturn # should show listeners on 3478/5349
docker exec prosody-xmpp prosodyctl check config
From a client, place a call between two accounts. Clients that support Jingle audio/video include Conversations (Android), Dino, Gajim, and Monal (iOS). Note that Jingle A/V is client-to-client — both parties need a client that supports it.
Certificate renewal
coturn/entrypoint.sh copies the certificate at container start, because
coturn runs as nobody and cannot read Let's Encrypt's root-only
privkey.pem directly. A renewal therefore needs a container restart, which
the certbot deploy hook on the server handles (outside this repo):
/etc/letsencrypt/renewal-hooks/deploy/restart-prosody.sh
It restarts both prosody-xmpp and coturn when the guschin.info or
xmpp.guschin.info lineage is renewed. Without the coturn restart, turns:
on 5349 would keep serving the old certificate until the next restart.
Firewall
Ports 3478 and 5349 (both UDP and TCP) plus the UDP relay range 49160-49200
must be reachable from the internet. The host has no local firewall
(iptables INPUT policy is ACCEPT), but any provider-level firewall needs
these opened.
Notes
- TLS encryption is not required by default (configurable in prosody.cfg.lua)
- Self-signed certificates are auto-generated on first run
- The container depends on an external MySQL network named
mysqlgit_mysql_network