Setup of a Prosody XMPP Server on Debian 12

/images/29928b3bcc53615b.jpg

Setting up an XMPP server is fairly easy with Prosody. This tutorial is a step-by-step guide.

This tutorial is a step-by-step guide meant for the experienced command line user to save time in setting up the details. If you want to setup an XMPP server and can run Debian 12, you can be sure your XMPP server will work after proceeding these steps.

Prerequisites

  • Being firm in using the command line.

  • A domain name, called foo.net in this tutorial, and bar.net as a second domain name.

  • A Debian server, set up as depicted in Setup of a Server with Debian 12.

Wording in this tutorial

string

meaning

foo.net

domain 1

bar.net

domain 2

xxxxxxxxx

any string

xx.xx.xxx.xx

IPv4 address

xxxx:xxx:xxxx:xxxx::1

IPv6 address

Firewall

First, let's open required ports with the firewall.

sudo ufw allow 5000/tcp
sudo ufw allow 5222/tcp
sudo ufw allow 5269/tcp
sudo ufw allow 5280/tcp
sudo ufw allow 5281/tcp
sudo ufw allow 5347/tcp
sudo ufw allow 5582/tcp
sudo ufw reload
sudo ufw status

Prosody

sudo aptitude install prosody lua-unbound luarocks liblua5.4-dev

Create a user that can be used as an admin:

sudo -u prosody prosodyctl adduser xxxxxxxxx@foo.net

Add it to /etc/prosody/prosody.cfg.lua (see example /etc/prosody/prosody.cfg.lua).

Cloud notification on iOS:

# NOTE: mod_cloud_notify_extensions is a meta module that will install
# mod_cloud_notify_encrypted, mod_cloud_notify_priority_tag,
# and mod_cloud_notify_filters.
sudo prosodyctl install --server=https://modules.prosody.im/rocks/ mod_cloud_notify_extensions
# Required by mod_cloud_notify_encrypted.
sudo aptitude install lua-luaossl

mod_vcard_muc module for Avatar pictures and vCard fields:

sudo prosodyctl install --server=https://modules.prosody.im/rocks/ mod_vcard_muc

Enable all modules as depicted in the example /etc/prosody/prosody.cfg.lua.

Configure VirtualHost section for each domain as depicted in example /etc/prosody/prosody.cfg.lua.

Start Prosody:

sudo systemctl enable prosody.service
sudo systemctl start prosody.service

Certbot

To provide encrypted server-to-client connection, the server needs a certificate for each domain and sub domain that is configured in /etc/prosody/prosody.cfg.lua.

For each domain or sub domain:

  • Go to your domain provider and point the domain to the server's IP address.

  • Wait half an hour.

  • Add domain/sub domain entry to a new VirtualHost section of /etc/prosody/prosody.cfg.lua.

  • Create a certificate with certbot. This will be done in the following steps.

Install

sudo aptitude install certbot

Firewall

# Firewall (ONLY IF HTTP PORT IS NOT GENERALLY OPEN)
sudo ufw allow 80/tcp
sudo ufw reload

Certbot

It is necessary to point the domains to the IP address of the server before continuing. This has already been done above.

Shut down your HTTP server if you have one running. If you don't want to shut it down, you can keep it running and run sudo certbot certonly --webroot instead of sudo certbot certonly --standalone as a command below.

# Request the certificate and import it to Prosody.
# Enter all domains and sub domains, e.g.
# ``example.com upload.example.com conference.example.com``:
sudo certbot certonly --standalone

Prosody

# Import
sudo prosodyctl --root cert import /etc/letsencrypt/live
# Test
sudo -u prosody prosodyctl check config -v
# Start up
sudo systemctl restart prosody.service

Firewall (ONLY IF HTTP PORT IS NOT GENERALLY OPEN FOR SOMETHING ELSE)

sudo ufw delete allow 80/tcp
sudo ufw reload
sudo ufw status

Renewing (in 3 months)

To renew the certificates in the future, either stop a running HTTP server (e.g. with sudo systemctl stop caddy.service or sudo systemctl stop nginx.service) or open port 80 on the firewall as shown above, and run:

sudo certbot renew --standalone --deploy-hook "prosodyctl --root cert import /etc/letsencrypt/live"

You may also force the renewal with:

sudo certbot renew --force-renewal --standalone --deploy-hook "prosodyctl --root cert import /etc/letsencrypt/live"

Start the HTTP server (e.g. via sudo systemctl start caddy.service) or close the port 80 on the firewall as shown above.

Stable audio/video call

XEP-0215: External Service Discovery (STUN)

Eturnal

Install:

sudo aptitude install extrepo
sudo extrepo enable eturnal
sudo aptitude update
sudo aptitude install eturnal
# Just in case it is not enabled by the installation yet:
sudo systemctl --now enable eturnal

[https://eturnal.net/doc/readme.html#installation]

Edit sudo vi /etc/eturnal.yml:

secret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
relay_ipv4_addr: "xx.xx.xxx.xx"
relay_ipv6_addr: "xxxx:xxx:xxxx:xxxx::1"

Restart:

sudo systemctl restart eturnal

Firewall

sudo ufw allow 3478/udp
sudo ufw allow 49152
sudo ufw allow 65535
sudo ufw reload

Prosody

Prosody module external_services needs to be activated in /etc/prosody/prosody.cfg.lua.

Configure external services in sudo vi /etc/prosody/prosody.cfg.lua (see example /etc/prosody/prosody.cfg.lua).

Restart:

sudo systemctl restart prosody.service
# Run a test (only works with the specific ``mod_turn_external`` module
# though):
sudo -u prosody prosodyctl check turn -v --ping=stun.conversations.im

Backup

Install a proper backup for your server. You don't want your XMPP users lose their accounts or data.

Appendix

Transfer

On the new server, install Prosody.

Copy configuration and data from the old server to the new server.

Copy the configuration file to the local machine and from there to the new server.

scp root@<OLD_SERVER>:/etc/prosody/prosody.cfg.lua prosody.cfg.lua
scp prosody.cfg.lua root@<NEW_SERVER>:/etc/prosody/prosody.cfg.lua

On the old server, pack the data folder:

tar czf prosody_data_backup.tar.gz /var/lib/prosody

Copy the archive file to the local machine, and then to the new server:

scp user@<OLD_SERVER>:prosody_data_backup.tar.gz .
scp prosody_data_backup.tar.gz  user@<NEW_SERVER>:

On the new server, unpack:

tar xzf prosody_data_backup.tar.gz

And move the folder into place:

sudo mv var/lib/prosody /var/lib/

Continue with the Certbot setup.

Supported functionalities

Our setup provides the following functionality of the XMPP protocol. You can test it thoroughly on https://compliance.conversations.im. The following process will reach a 95 % coverage of features. Of course, it's up to you to configure the last missing XEP (XEP-0368).

Results

  • [X] RFC 6121: Roster Versioning

  • [X] XEP-0215: External Service Discovery (STUN)

  • [X] XEP-0215: External Service Discovery (TURN)

  • [X] XEP-0153: ⚡ vCard-Based Avatar (MUC)

  • [X] XEP-0045: Multi-User Chat

  • [X] XEP-0065: SOCKS5 Bytestreams (Proxy)

  • [X] XEP-0115: Entity Capabilities

  • [X] XEP-0160: Best Practices for Handling Offline Messages

  • [X] XEP-0163: Personal Eventing Protocol

  • [X] XEP-0191: Blocking Command

  • [X] XEP-0198: Stream Management

  • [X] XEP-0280: Message Carbons

  • [X] XEP-0313: ⚡ Message Archive Management

  • [X] XEP-0313: ⚡ Message Archive Management (Multi-User Chat)

  • [X] XEP-0352: Client State Indication

  • [X] XEP-0357: Push Notifications

  • [X] XEP-0363: HTTP File Upload

  • [ ] XEP-0368: SRV records for XMPP over TLS

  • [X] XEP-0384: OMEMO Encryption

  • [X] XEP-0398: User Avatar to vCard-Based Avatars Conversion

Informational Tests

  • [X] XEP-0157: Contact Addresses for XMPP Services (Abuse)

  • [X] XEP-0077: In-Band Registration

  • [ ] XEP-0156: Discovering Alternative XMPP Connection Methods (HTTP)

  • [X] XEP-0280: Message Carbons - Recommended Rules

  • [X] XEP-0313: Message Archive Management (extended usage)

  • [X] XEP-0363: HTTP File Upload (CORS Headers)

  • [X] XEP-0402: PEP Native Bookmarks

Security and privacy considerations

We configure the server with TLS so the transport from client to server is always encrypted. In addition, for not having to trust what happens on the server, the chat content can always be encrypted by the clients.

However, by XMPPs current design, the user's contact list (roster) is unencrypted on the server. Encrypted messages still leave log of messages on the server (the "when and who with whom" information, if you decide to archive messages for greater sync usability). And the XEPs XEP-0153 and XEP-0313 always store unencrypted user profile information and logs on the server.

This is not a problem for you per-se, but your users should be aware of it. They need to trust you as a service provider. It is also a security issue that's not fully under your control, as you can never exclude the possibility of law enforcement requesting data, or your server being hacked.

The fact that profile information is end-to-end-encrypted is my point of criticism of the XMPP protocoll. We have to live with that currently, because there is no other decentralized system that has not at least the metadata problem, including Matrix. (Furthermore, Matrix spreads the all metadata across all systems which is accumulating more metadata on each system, and while Matrix is decentralized by design, in practice it works with some centralized features currently, and comes with opt-out-only tracking features).

At least, in XMPP, all chat content including pictures, voice records, attachments is being encrypted. So as long as users share personal information in encrypted chats, the level of privacy is acceptable. Just be aware that what happens outside the encrypted chat window, especially profile information, is not encrypted.

Example /etc/prosody/prosody.cfg.lua

You can just copy paste this configuration file.

Replace the values from Wording in this tutorial with your own ones.

admins = { "xxxxxxxxx@foo.net" }


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 clients in sync
        "dialback"; -- s2s dialback support
        "limits"; -- Enable bandwidth limiting for XMPP connections
        "pep"; -- Enables users to publish their avatar, mood, activity, playing music and more
        "smacks"; -- Stream management and resumption (XEP-0198)
        "private"; -- Private XML storage (for room bookmarks, etc.)
        "server_contact_info"; -- XEP-0157: Contact Addresses for XMPP Services (Abuse)
        "vcard4"; -- User profiles (stored in PEP)
        "vcard_legacy"; -- Conversion between legacy vCard and PEP Avatar, vcard

     -- Nice to have
        "csi_simple"; -- XEP-0352 Simple Mobile optimizations
        --"invites"; -- Create and manage invites
        --"invites_adhoc"; -- Allow admins/users to create invitations via their client
        --"invites_register"; -- Allows invited users to create accounts SECURITY CRITICAL
        "mam"; -- XEP-0313 Store messages in an archive and allow users to access it PRIVACY CRITICAL
        "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
        "user_account_management";
        "version"; -- Replies to server version requests

    -- Admin interfaces
        "admin_adhoc"; -- Allows administration via an XMPP client that supports ad-hoc commands
        "admin_shell"; -- Allow secure administration via 'prosodyctl shell'
        --"admin_telnet"; -- Opens telnet console interface on localhost port 5582

    -- HTTP modules
        --"bosh"; -- Enable BOSH clients, aka "Jabber over HTTP"
        --"websocket"; -- XMPP over WebSockets
        --"http_files"; -- Serve static files from a directory over HTTP

    -- Other specific functionality
        --"announce"; -- Send announcement to all online users
        "external_services";  -- XEP-0215: External Service Discovery (STUN)
        --"groups"; -- Shared roster support
        --"legacyauth"; -- Legacy authentication. Only used by some old clients and bots.
        "mimicking"; -- Prevent spoofing attacks
        "muc_limits"; -- Prevent room floods
        --"motd"; -- Send a message to users when they log in
        "proxy65"; -- XEP-0065 Enables a file transfer proxy service which clients behind NAT can use
        --"s2s_bidi"; -- Bi-directional server-to-server (XEP-0288)
        --"server_contact_info"; -- Publish contact information for this service
        "tombstones";
        "watchregistrations"; -- Alert admins of registrations
        --"welcome"; -- Welcome users who register accounts

    -- Community provided modules
        "cloud_notify"; -- XEP-0357 Support for sending “push notifications” to clients that need it, typically those running on certain mobile devices
        "cloud_notify_extensions"; -- XEP-0357 This is a meta-module that simply enables all the modules required to support Siskin or Snikket iOS
}

modules_disabled = {
    -- "offline"; -- Store offline messages
    -- "c2s"; -- Handle client connections
    -- "s2s"; -- Handle server-to-server connections
    -- "posix"; -- POSIX functionality, sends server to background, enables syslog, etc.
}

-- TODO: Fill out with real data.
contact_info = {
    abuse = { "mailto:abuse@localhost", "xmpp:abuse@localhost" };
    admin = { "mailto:admin@localhost", "xmpp:admin@localhost" };
    feedback = { "http://localhost/feedback.html", "mailto:feedback@localhost", "xmpp:feedback@localhost" };
    sales = { "xmpp:sales@localhost" };
    security = { "xmpp:security@localhost" };
    status = { "gopher://status.localhost" };
    support = { "https://localhost/support.html", "xmpp:support@localhost" };
}


external_services = {
    {
        type = "stun";
        transport = "udp";
        host = "xx.xx.xxx.xx";
        port = 3478;
        secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    };
    {
        type = "turn";
        transport = "udp";
        host = "xx.xx.xxx.xx";
        port = 3478;
        secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    };
}


allow_registration = true
watchregistrations = true
c2s_require_encryption = true

s2s_require_encryption = true
s2s_secure_auth = true
--s2s_insecure_domains = { "insecure.example" }
--s2s_secure_domains = { "jabber.org" }


-- Rate limits for incoming client and server connections
limits = {
  c2s = {
    -- rate = "10kb/s";
    rate = "3kb/s";
    burst = "2s";
  };
  s2sin = {
    -- rate = "30kb/s";
    rate = "10kb/s";
    burst = "5s";
  };
}


-- Select the authentication backend to use. The 'internal' providers
-- use Prosody's configured data storage to store the authentication data.

authentication = "internal_hashed"

-- Select the storage backend to use. By default Prosody uses flat files
-- in its configured data directory, but it also supports more backends
-- through modules. An "sql" backend is included by default, but requires
-- additional dependencies. See https://prosody.im/doc/storage for more info.

--storage = "sql" -- Default is "internal" (Note: "sql" requires installed
-- lua-dbi RPM package)

-- For the "sql" backend, you can uncomment *one* of the below to configure:
--sql = { driver = "SQLite3", database = "prosody.sqlite" } -- Default. 'database' is the filename.
--sql = { driver = "MySQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" }
--sql = { driver = "PostgreSQL", database = "prosody", username = "prosody", password = "secret", host = "localhost" }


-- 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.

archive_expires_after = "1w" -- Remove archived messages after 1 week

-- You can also configure messages to be stored in-memory only. For more
-- archiving options, see https://prosody.im/doc/modules/mod_mam


-- Logging configuration
-- For advanced logging see https://prosody.im/doc/logging
log = {
    -- Log everything of level "info" and higher (that is, all except "debug" messages)
    -- to /var/log/prosody/prosody.log and errors also to /var/log/prosody/prosody.err
    info = "/var/log/prosody/prosody.log"; -- Change 'info' to 'debug' for verbose logging
    error = "/var/log/prosody/prosody.err"; -- Log errors also to file
    -- error = "*syslog"; -- Log errors also to syslog
    -- "*console"; -- Log to the console, useful for debugging with daemonize=false
}


-- Uncomment to enable statistics
-- For more info see https://prosody.im/doc/statistics
-- statistics = "internal"


-- Certificates

-- Location of directory to find certificates in (relative to main config file):
certificates = "certs/"

-- HTTPS currently only supports a single certificate, specify it here:
--https_certificate = "/etc/prosody/certs/localhost.crt"

-- POSIX configuration
-- For more info see https://prosody.im/doc/modules/mod_posix
pidfile = "/run/prosody/prosody.pid";
--daemonize = false -- Default is "true"


----------- Virtual hosts -----------
-- You need to add a VirtualHost entry for each domain you wish Prosody to serve.
-- Settings under each VirtualHost entry apply *only* to that host.

--VirtualHost "example.com"
--    certificate = "/path/to/example.crt"

----------- Virtual host: foo.net  -----------

VirtualHost "foo.net"

-- XEP-0045 Multi User Chat
Component "conference.foo.net" "muc"
    name = "The foo.net chatrooms server"
    restrict_room_creation = false

modules_enabled = {
    "muc_mam", -- XEP-0313: Message Archive Management (Multi-User Chat)
    "vcard_muc" -- XEP-0153 vCard-Based avatar
}

-- XEP-0363: HTTP File Upload
Component "upload.foo.net" "http_file_share"
http_file_share_size_limit = 1024 * 1024 * 200 -- 200 MB
http_file_share_daily_quota = 1024 * 1024 * 200 -- 500 MB
http_file_share_expires_after = 60 * 60 * 24 * 7 -- a week in seconds


----------- Virtual host: bar.net -----------

VirtualHost "bar.net"

-- XEP-0045 Multi User Chat
Component "conference.bar.net" "muc"
    name = "The bar.net chatrooms server"
    restrict_room_creation = false

modules_enabled = {
    "muc_mam", -- XEP-0313: Message Archive Management (Multi-User Chat)
    "vcard_muc" -- XEP-0153 vCard-Based avatar
}

Component "upload.bar.net" "http_file_share"
http_file_share_size_limit = 1024 * 1024 * 200 -- 200 MB
http_file_share_daily_quota = 1024 * 1024 * 200 -- 500 MB
http_file_share_allowed_file_types = {
    }
http_file_share_expires_after = 60 * 60 * 24 * 7 -- a week in seconds


------ 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"