Setting up XMPP (Prosody) on Debian Bookworm
Howto on setting up and running Prosody XMPP server on Debian 12, Bookworm.
I've run an XMPP server off and on since around the year 2000, and in that time i've tried most of the major server software. I started with the original jabberd, then jabberd2, Openfire, ejabberd, and finally Prosody.
XMPP is a great protocol for Instant Messaging and back in the early 2000s it looked like messaging had a bright and unified future. Then the big players started to close their messengers and silo off their users.
Recently i have noticed that their seems to be a renewed interest in XMPP and breaking free of siloed messaging. Since i just got this shiny new VPS i decided to go ahead and set up Prosody on it.
Snikket is a Docker containerised version of Prosody and various other components to give a quick and simple private messaging platform. It's a great choice for those starting out or who are happy with the opinionated configuration that Snikket provides, but sometimes you just want something more than what Snikket is capable of.
This guide is about setting up Prosody from scratch on a fresh Debian 12, Bookworm, VPS.
DNS
Before we touch the server we are going to need to set up some DNS records for Prosody to communicate with other servers and its own component services.
In this guide we have the domain name example.org and the server, in keeping with XMPP's Romeo & Juliet theme, is called mercutio. We are also going to create an xmpp hostname to make things easier and for future portablity.
So we need to create 3 "A" (and 3 "AAAA" if we have IPv6 on our server) records pointing to our server's IP for the following:
example.org
mercutio.example.org
xmpp.example.org
In our example we are also going to have services for HTTP File Sharing, Proxy65, Chatrooms, and PubSub. So we will create some CNAME records pointing towards xmpp.example.org for the following:
chat.example.org
proxy.example.org
share.example.org
pubsub.example.org
Finally we need to create some SRV (Service) records so that other XMPP servers can find our server and components. Service records are more complex than other types and allow you define the priority, weight and even the ports to connect to. I'll give the records themselves and then explain a bit about them. First the records for clients to find and connect to the server:
_xmpp-client._tcp.example.org 10 5 5222 xmpp.example.org
_xmpps-client._tcp.example.org 0 5 5223 xmpp.example.org
This tells clients to connect to xmpp.example.org over TCP on port 5223 using DirectTLS (xmpps) as it has a higher priorty (lower number) of "0". If the client doesn't support DirectTLS it will use the older StartTLS method on port 5222.
Now the records for telling other servers how to connect:
_xmpp-server._tcp.example.org 10 5 5269 xmpp.example.org
_xmpps-server._tcp.example.org 0 5 5270 xmpp.example.org
_xmpp-server._tcp.chat.example.org 10 5 5269 xmpp.example.org
_xmpps-server._tcp.chat.example.org 0 5 5270 xmpp.example.org
_xmpp-server._tcp.pubsub.example.org 10 5 5269 xmpp.example.org
_xmpps-server._tcp.pubsub.example.org 0 5 5270 xmpp.example.org
We are now done with the DNS. On to the server!
Setting up a STUN/TURN server
We want our clients to be able to do voice and video calls so we want a STUN/TURN server to help alleviate any problems with NAT.
We're going to use a package called coturn which provides both STUN and TURN. It's in the Debian package repository so is simple to install:
apt update && apt install coturn
Coturn is installed so now let's configure it. Replace the file /etc/turnserver.conf with this:
# Coturn TURN SERVER configuration file
#
# Boolean values note: where a boolean value is supposed to be used,
# you can use '0', 'off', 'no', 'false', or 'f' as 'false,
# and you can use '1', 'on', 'yes', 'true', or 't' as 'true'
# If the value is missing, then it means 'true' by default.
#
# TURN listener port for UDP and TCP (Default: 3478).
# Note: actually, TLS & DTLS sessions can connect to the
# "plain" TCP & UDP port(s), too - if allowed by configuration.
#
listening-port=3478
# Uncomment if no TCP client listener is desired.
# By default TCP client listener is always started.
#
no-tcp
# Uncomment if no TLS client listener is desired.
# By default TLS client listener is always started.
#
no-tls
# Uncomment if no DTLS client listener is desired.
# By default DTLS client listener is always started.
#
no-dtls
# Option to redirect all log output into system log (syslog).
#
syslog
# Option to hide software version. Enhance security when used in production.
# Revealing the specific software version of the agent through the
# SOFTWARE attribute might allow them to become more vulnerable to
# attacks against software that is known to contain security holes.
# Implementers SHOULD make usage of the SOFTWARE attribute a
# configurable option (https://tools.ietf.org/html/rfc5389#section-16.1.2)
#
no-software-attribute
# Disable RFC5780 (NAT behavior discovery).
#
# Originally, if there are more than one listener address from the same
# address family, then by default the NAT behavior discovery feature enabled.
# This option disables the original behavior, because the NAT behavior
# discovery adds extra attributes to response, and this increase the
# possibility of an amplification attack.
#
# Strongly encouraged to use this option to decrease gain factor in STUN
# binding responses.
#
no-rfc5780
# Disable handling old STUN Binding requests and disable MAPPED-ADDRESS
# attribute in binding response (use only the XOR-MAPPED-ADDRESS).
#
# Strongly encouraged to use this option to decrease gain factor in STUN
# binding responses.
#
no-stun-backward-compatibility
# Only send RESPONSE-ORIGIN attribute in binding response if RFC5780 is enabled.
#
# Strongly encouraged to use this option to decrease gain factor in STUN
# binding responses.
#
response-origin-only-with-rfc5780
realm=xmpp.example.org
use-auth-secret
static-auth-secret=SUPER-SECRET-PASSWORD
Remember to replace the SUPER-SECRET-PASSWORD with one of your own.
Run systemctl restart coturn
to pick up the new configuration.
SSL Certificate & Nginx Reverse Proxy
We need SSL certificates for our XMPP server and components. We also want to use nginx as a reverse proxy for our HTTP File Share component (and possibly for running standard websites on our server), so we're going to install nginx and Let's Encrypt certbot:
Nginx
apt update && apt install nginx-light certbot
Create a file /etc/nginx/sites-available/xmpp-service containing:
server {
listen 80;
listen [::]:80;
server_name example.org;
server_name chat.example.org;
server_name proxy.example.org;
server_name pubsub.example.org;
server_name share.example.org;
server_name xmpp.example.org;
access_log /var/log/nginx/xmpp-service.access.log;
error_log /var/log/nginx/xmpp-service.error.log crit;
location /.well-known/acme-challenge/ {
root /var/www/mercutio.example.org;
}
location / {
return 404;
}
}
Now we symlink this file to Nginx's sites-enabled directory:
ln -s /etc/nginx/sites-available/xmpp-service /etc/nginx/sites-enabled/
Make the root directory for mercutio.example.org:
mkdir -p /var/www/mercutio.example.org
Restart nginx:
systemctl restart nginx
Getting SSL certificates
With DNS and Nginx set up we're now ready to get our certificates:
certbot certonly --webroot -w /var/www/mercutio.example.org \
-d example.org -d chat.example.org -d proxy.example.org \
-d pubsub.example.org -d share.example.org -d xmpp.example.org
If successful you should have a nice certificate for all your XMPP needs.
Now let's modify /etc/nginx/sites-available/xmpp-service to make use of them. Add some extra SSL-enabled servers below your existing non-SSL one:
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.org;
server_name chat.example.org;
server_name proxy.example.org;
server_name pubsub.example.org;
server_name xmpp.example.org;
access_log /var/log/nginx/xmpp-service.access.log;
error_log /var/log/nginx/xmpp-service.error.log crit;
ssl_certificate /etc/letsencrypt/live/example.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.org/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/example.org/chain.pem;
location / {
return 404;
}
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name share.example.org;
access_log /var/log/nginx/xmpp-service.access.log;
error_log /var/log/nginx/xmpp-service.error.log crit;
ssl_certificate /etc/letsencrypt/live/example.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.org/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/example.org/chain.pem;
add_header Content-Security-Policy "default-src 'none'; frame-ancestors 'none';";
location / {
return 404;
}
location /file_share {
proxy_pass http://127.0.0.1:5280;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
tcp_nodelay on;
client_max_body_size 104857616; # 100MB + 16 bytes
}
}
Restart Nginx to enable the SSL sites systemctl restart nginx
Now, on to the final stretch; setting up Prosody itself.
Prosody
Installing Prosody
Debian Bookworm has a slightly older version of Prosody, so we're going to enable the debian-backports repository and get the latest version from there.
Create a new /etc/apt/sources.list.d/bookworm-backports.list file containing:
deb http://deb.debian.org/debian bookworm-backports main
Now we're ready to install Prosody:
apt update && apt install prosody/bookworm-backports prosody-modules/bookworm-backports
Prosody is now installed. Let's configure it.
Configuring Prosody
Replace /etc/prosody/prosody.cfg.lua with this:
-- Information on configuring Prosody can be found at
-- https://prosody.im/doc/configure
--
-- Tip: You can check that the syntax of this file is correct
-- when you have finished by running this command:
-- prosodyctl check config
---------- Server-wide settings ----------
-- Settings in this section apply to the whole server and are the default settings
-- for any virtual hosts
-- Example: admins = { "user1@example.com", "user2@example.net" }
admins = { }
plugin_paths = { "/usr/local/lib/prosody/modules" }
-- Documentation for modules: https://prosody.im/doc/modules
modules_enabled = {
-- Generally required
"disco"; -- Service discovery
"roster"; -- Allow users to have a roster. Recommended ;)
"saslauth"; -- Authentication for clients and servers. Recommended if you want to log in.
"tls"; -- Add support for secure TLS on c2s/s2s connections
-- Not essential, but recommended
"blocklist"; -- Allow users to block communications with other users
"bookmarks"; -- Synchronise the list of open rooms between clients
"carbons"; -- Keep multiple online clients in sync
"dialback"; -- Support for verifying remote servers using DNS
"limits"; -- Enable bandwidth limiting for XMPP connections
"pep"; -- Allow users to store public and private data in their account
"private"; -- Legacy account storage mechanism (XEP-0049)
"smacks"; -- Stream management and resumption (XEP-0198)
"vcard4"; -- User profiles (stored in PEP)
"vcard_legacy"; -- Conversion between legacy vCard and PEP Avatar, vcard
-- Nice to have
"csi_simple"; -- Simple but effective traffic optimizations for mobile devices
"ping"; -- Replies to XMPP pings with pongs
"register"; -- Allow users to register on this server using a client and change passwords
"time"; -- Let others know the time here on this server
"uptime"; -- Report how long server has been running
"version"; -- Replies to server version requests
"mam"; -- Store recent messages to allow multi-device synchronization
-- Admin interfaces
"admin_adhoc"; -- Allows administration via an XMPP client that supports ad-hoc commands
"admin_shell"; -- Allow secure administration via 'prosodyctl shell'
-- Other specific functionality
"posix"; -- POSIX functionality, sends server to background, enables syslog, etc.
"announce"; -- Send announcement to all online users
-- "s2s_bidi"; -- Bi-directional server-to-server (XEP-0288)
"watchregistrations"; -- Alert admins of registrations
"cloud_notify"; -- XEP-0357: Cloud push notifications
"cloud_notify_filters"; -- Support for push notification filtering rules
"external_services"; -- External Service Discovery
"csi_battery_saver"; -- CSI module to save battery on mobile devices, based on mod_csi_pump
}
-- These modules are auto-loaded, but should you want
-- to disable them then uncomment them here:
modules_disabled = {
}
pidfile = "/run/prosody/prosody.pid";
-- Server-to-server authentication
-- Require valid certificates for server-to-server connections?
-- If false, other methods such as dialback (DNS) may be used instead.
s2s_secure_auth = true
-- Rate limits
-- Enable rate limits for incoming client and server connections. These help
-- protect from excessive resource consumption and denial-of-service attacks.
limits = {
c2s = {
rate = "10kb/s";
};
s2sin = {
rate = "30kb/s";
};
}
-- Authentication
-- Select the authentication backend to use. The 'internal' providers
-- use Prosody's configured data storage to store the authentication data.
-- For more information see https://prosody.im/doc/authentication
authentication = "internal_hashed"
-- Archiving configuration
-- If mod_mam is enabled, Prosody will store a copy of every message. This
-- is used to synchronize conversations between multiple clients, even if
-- they are offline. This setting controls how long Prosody will keep
-- messages in the archive before removing them. For more see:
-- https://prosody.im/doc/modules/mod_mam
archive_expires_after = "1m" -- Remove archived messages after 1 month
-- Logging configuration
-- For advanced logging see https://prosody.im/doc/logging
log = {
-- Log files (change 'info' to 'debug' for debug logs):
info = "/var/log/prosody/prosody.log";
error = "/var/log/prosody/prosody.err";
-- Syslog:
{ levels = { "error" }; to = "syslog"; };
}
-- Location of directory to find certificates in (relative to main config file).
-- For more information see https://prosody.im/doc/certificates
certificates = "certs"
-- Using nginx as reverse proxy so set up trusted proxy and prevent Prosody from
-- listening for HTTPS traffic on external addresses.
trusted_proxies = { "127.0.0.1"; "::1"; }
https_interfaces = { "127.0.0.1"; "::1"; }
-- Enable DirectTLS for clients and servers.
c2s_direct_tls_ports = { 5223 }
s2s_direct_tls_ports = { 5270 }
-- External STUN and TURN services
external_services = {
{
type = "stun";
transport = "udp";
host = "xmpp.example.org";
port = 3478;
secret = "SUPER-SECRET-PASSWORD";
};
{
type = "turn";
transport = "udp";
host = "xmpp.example.org";
port = 3478;
secret = "SUPER-SECRET-PASSWORD";
};
}
------ Additional config files ------
-- For organizational purposes you may prefer to add VirtualHost and
-- Component definitions in their own config files. This line includes
-- all config files in /etc/prosody/conf.d/
Include "conf.d/*.cfg.lua"
- Don't forget to replace the SUPER-SECRET-PASSWORD with the same one you used for your STUN/TURN server.
- Don't forget to add your JID to the admins section.
Note In the modules_enabled section i have commented out the s2s_bidi module. This reuses connections between servers and is a great feature for reducing server load. Unfortunately when ejabberd added support in 24.10 they messed up. If you leave it enabled you won't be able to communicate with any ejabberd servers running 24.10. Hopefully they'll fix it soon.
We've now got the base server done. Time for our example.org VirtualHost.
Create a new /etc/prosody/conf.avail/example.org.cfg.lua file containing:
VirtualHost "example.org"
enabled = true
Component "chat.example.org" "muc"
name = "Chatrooms"
restrict_room_creation = "local"
modules_enabled = {
"muc_mam";
"vcard_muc";
}
muc_room_default_persistent = true
Component "proxy.example.org" "proxy65"
modules_disabled = { "s2s"; }
name = "File Transfer Proxy"
proxy65_address = "xmpp.example.org"
proxy65_acl = { "example.org" }
Component "pubsub.example.org" "pubsub"
name = "Publish-Subscribe Service"
add_permissions = {
["prosody:registered"] = { "pubsub:create-node" }
}
Component "share.example.org" "http_file_share"
name = "HTTP File Sharing"
modules_disabled = { "s2s"; }
http_file_share_size_limit = 1024 * 1024 * 20 -- 20MiB per file
http_file_share_global_quota = 1024*1024*2048 -- 2 GiB total
http_external_url = "https://share.example.org/"
http_file_share_access = {
"example.org";
}
Symlink the file:
cd /etc/prosody/conf.d/
ln -s ../conf.avail/example.org.cfg.lua .
Get prosody to import your certificates:
/usr/bin/prosodyctl --root cert import /etc/letsencrypt/live
To make it so Prosody reimports certificates after Let's Encrypt renews them add a /etc/letsencrypt/renewal-hooks/deploy/02-prosody.sh file containing:
#!/bin/sh
/usr/bin/prosodyctl --root cert import /etc/letsencrypt/live
Configuration is now complete; Let's check things are good:
prosodyctl check config
for the config
prosodyctl check certs
for certificates
prosodyctl check dns
for DNS
All good? Then let's restart things:
systemctl restart prosody
Now create your users using prosodyctl adduser romeo@example.org
Don't forget to add the following locations to your backups:
/etc/letsencrypt
/etc/nginx
/etc/prosody
/var/lib/prosody
/var/www
/etc/turnserver.conf
All done. You now have a working XMPP server with service components, A/V-capability and a reverse proxy for HTTP file sharing.
Welcome to the world of federated comms!