Configuring a modern XMPP server with Prosody

· in instant messaging

Tested on Debian 13 (Docker) with Prosody 13.0.7 from packages.prosody.im on 5 November 2026: prosodyctl check config, check certs and check features, server start-up, certificate import and invite creation.

This guide sets up a Prosody 13 XMPP server on a Debian machine at home. At the end you have a chat server for yourself and a few friends with encrypted connections, group chats, photo and file sharing, push notifications for phones and invite-only sign-up. A configuration file you can download covers every setting below.

The guide assumes you can log in to the server over SSH and run commands with sudo. If XMPP terms such as JID, MUC or federation are new to you, read the XMPP glossary first. Audio and video calls need a TURN server as well, which has its own guide.

What you need before you start

  • A domain name where you can add DNS records. Your users' addresses will look like name@example.org. This guide uses example.org throughout; replace it with your own domain.
  • A machine running Debian 13 (trixie). The Debian package calls Prosody a lightweight server, and a Raspberry Pi, a used mini PC or an old laptop can run it for a household and some friends. Choosing home server hardware compares the options.
  • A public IP address that reaches your home. Other servers and your phone on mobile data must be able to connect to ports on that machine. You forward those ports on your router, as described in the port forwarding guide.

If your internet provider puts you behind carrier-grade NAT (CGNAT), port forwarding on your router does nothing, because the public address is shared with other customers. Ask the provider for a public IPv4 address, or run Prosody on a small rented server instead. If your public IP changes from time to time, you also need a dynamic DNS service that updates your A record.

Self-hosting chat is the wrong choice if the people using it depend on it being up all the time and you can't fix it when you are away. A home server goes down with a power cut, a router reboot or a failed SD card. For a few friends who accept that, it works well.

DNS records

Prosody in this guide serves three names. example.org is the domain in your users' addresses. conference.example.org hosts group chats. upload.example.org receives the photos and files people share. Each name needs to resolve to your public IP address.

NameTypeValuePurpose
example.orgA (and AAAA for IPv6)your public IPClient and server connections
conference.example.orgA/AAAA, or CNAME to example.orgyour public IPGroup chats
upload.example.orgA/AAAA, or CNAME to example.orgyour public IPFile sharing over HTTPS
_xmpps-client._tcp.example.orgSRV0 5 5223 example.org.Direct TLS for clients (optional)

If Prosody runs on the machine that example.org points to and uses the standard ports, A records are enough. Other servers and clients fall back to them when no SRV records exist. You need SRV records when the server runs under another name. For example, if your website at example.org is hosted elsewhere, point _xmpp-client._tcp.example.org (port 5222) and _xmpp-server._tcp.example.org (port 5269) at a name such as xmpp.example.org. The target of an SRV record must be a name with an A or AAAA record, not an IP address or a CNAME.

The _xmpps-client record advertises port 5223, where clients can connect with TLS from the first byte instead of upgrading a plain connection with STARTTLS (XEP-0368). The configuration below enables that port, prosodyctl check asks for the record, and the compliance tester further down checks for it.

Install Prosody 13

Debian 13 ships Prosody 13.0.1. The Prosody project runs its own package repository with the latest bug-fix release of the same branch, which was 13.0.7 when this guide was tested. Add the repository and install the package:

sudo apt install wget lsb-release
sudo wget https://prosody.im/downloads/repos/$(lsb_release -sc)/prosody.sources -O/etc/apt/sources.list.d/prosody.sources
sudo apt update
sudo apt install prosody

Then check which version runs and which Lua interpreter it uses:

sudo prosodyctl about

Prosody 13 needs Lua 5.2 or newer and recommends 5.4. In our test, a fresh Debian 13 install from the Prosody repository left the system lua command pointing at Lua 5.1, because one of the recommended packages pulls it in. prosodyctl then stopped with "Prosody is no longer compatible with Lua 5.1". Switch the interpreter to 5.4 and run prosodyctl about again:

sudo update-alternatives --set lua-interpreter /usr/bin/lua5.4

TLS certificates from Let's Encrypt

Clients and other servers only talk to Prosody over TLS, and they check that the certificate matches each name. One Let's Encrypt certificate can cover all three names. Certbot's standalone mode answers the validation request itself on port 80. Certbot then renews the certificate automatically before it expires, and each standalone renewal needs port 80 again. So forward TCP port 80 to the server and leave it forwarded, and stop any web server that already uses that port. If port 80 is closed when a renewal runs, the renewal fails and the certificate expires.

sudo apt install certbot
sudo certbot certonly --standalone -d example.org -d conference.example.org -d upload.example.org

If a web server already runs on port 80 on this machine, use certbot's --webroot mode instead. The certificate ends up in /etc/letsencrypt/live/example.org/ either way.

Prosody runs as its own user and can't read the files in /etc/letsencrypt, which only root can open. prosodyctl copies them into /etc/prosody/certs with the right owner:

sudo prosodyctl --root cert import /etc/letsencrypt/live

In our test, this command imported one certificate with all three names for all three hosts. To repeat the import after every renewal, save this as /etc/letsencrypt/renewal-hooks/deploy/prosody.sh:

#!/bin/sh
/usr/bin/prosodyctl --root cert import /etc/letsencrypt/live

Make it executable with sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/prosody.sh. Certbot runs every script in that folder after it renews a certificate.

The Prosody configuration

Download prosody.cfg.lua, replace every example.org with your domain and set your own admin address. Then copy it over the packaged file, keep the permissions Debian uses and check the syntax:

sudo cp prosody.cfg.lua /etc/prosody/prosody.cfg.lua
sudo chown root:prosody /etc/prosody/prosody.cfg.lua
sudo chmod 640 /etc/prosody/prosody.cfg.lua
sudo prosodyctl check config

The file is Lua. Settings at the top apply to the whole server. Settings after a VirtualHost or Component line apply only to that host, so never add global settings at the bottom of the file.

Modules for phones and multiple devices

Modern clients such as Conversations, Monal and Gajim expect a set of protocol extensions (XEPs) from the server. In Prosody 13 all of them ship with Prosody itself, so you no longer need the community module collection for a full-featured server. The packaged configuration already enables most of them. The downloaded file adds the message archive and contact addresses, and leaves calls switched off until you set up TURN.

ModuleWhat it does for usersXEP
carbonsCopies each message to all of your logged-in devices0280
mamKeeps a server-side archive so a device that was off can fetch what it missed0313
smacksResumes a session after a short network drop without losing messages0198
csi_simpleHolds back unimportant traffic while the phone screen is off, saving battery0352
cloud_notifyWakes a sleeping phone through its push service when a message arrives0357
pep, vcard4, vcard_legacyAvatars, profiles and OMEMO encryption keys0163, 0292, 0398
blocklistLets users block other addresses0191
bookmarksSyncs the list of joined group chats between devices0402
server_contact_infoPublishes an admin and abuse contact for your server0157
turn_externalGives clients credentials for your TURN server, for calls0215

The archive keeps messages for two weeks in this file (archive_expires_after = "2w"). Prosody's default is one week. A longer time helps people whose phones stay offline for days, but every message then sits on your disk for longer.

Push notifications go from Prosody to the push service of the client's developer, and from there to Google or Apple. By default Prosody sends neither the message text nor the sender, and its documentation advises keeping it that way, because both would be visible to those services. Some iOS clients, such as Siskin and the Snikket iOS app, need an extra community module, mod_cloud_notify_extensions, for reliable push.

Ports Prosody listens on

The configuration keeps the standard ports and adds direct TLS on 5223. In our test, Prosody started and logged clients on 5222 and 5223, servers on 5269, HTTPS on 5281 and plain HTTP on 5280 bound to localhost only. Since Prosody 0.12, the plain HTTP port is no longer reachable from other machines by default.

Group chats and file sharing

Group chats run as a component on their own name:

Component "conference.example.org" "muc"
	modules_enabled = { "muc_mam" }
	restrict_room_creation = "local"
	muc_log_expires_after = "2w"

muc_mam keeps an archive per room, so a phone that was off can catch up on a group conversation the same way it does for one-to-one chats. restrict_room_creation = "local" lets only accounts on example.org create rooms, which stops strangers from using your server to host their own rooms.

File sharing (XEP-0363) also runs as a component. A client asks Prosody for an upload slot, sends the file over HTTPS and then shares a link in the chat:

Component "upload.example.org" "http_file_share"
	parent_host = "example.org"
	http_file_share_size_limit = 100 * 1024 * 1024
	http_file_share_daily_quota = 1024 * 1024 * 1024
	http_file_share_global_quota = 10 * 1024 * 1024 * 1024
	http_file_share_expires_after = "2w"

parent_host tells Prosody 13 which users may upload. The limits allow files of up to 100 MiB, 1 GiB per user per day and 10 GiB in total, and files are deleted after two weeks. Prosody's defaults are 10 MiB per file and one week. Set the total quota to what your disk can spare. In our test, Prosody served uploads at https://upload.example.org:5281/file_share, which is why port 5281 must be reachable from outside.

Router and firewall ports

Forward these TCP ports from your router to the server, and allow them in the server's firewall if it runs one.

PortProtocolUsed by
5222TCPClients (STARTTLS)
5223TCPClients (direct TLS)
5269TCPOther XMPP servers (federation)
5281TCPHTTPS file uploads and downloads
80TCPCertbot standalone; keep it open for automatic renewals

With ufw, the first four are one command: sudo ufw allow 5222,5223,5269,5281/tcp. Leave port 5280 closed. Calls need more ports, all listed in the audio and video calls guide.

Start Prosody and create accounts

Restart Prosody so it loads the new configuration, then create your own account. The command asks for a password twice:

sudo systemctl restart prosody
sudo prosodyctl adduser admin@example.org

Because the address is listed under admins in the configuration, the account gets admin rights.

Invite-only registration

Open registration on a public server attracts spam accounts. The configuration allows registration only with an invitation. Create an invite link on the server:

sudo prosodyctl shell invite create_account example.org

The command prints a link of the form xmpp:example.org?register;preauth=…. Send it to the person you are inviting. A client that supports invitations opens it and lets them choose a username and password. Add --admin to give the new account admin rights. Links expire after seven days (invite_expiry), and sudo prosodyctl shell invite list example.org shows the ones still open. Users can also create invites from their own client through invites_adhoc. If someone's client doesn't handle the link, create the account for them with prosodyctl adduser instead.

For help choosing apps for your users, see picking modern XMPP clients.

Check the server

Prosody has a built-in checker. Run it after every change:

sudo prosodyctl check

It checks the configuration, DNS records and certificates, and the TURN setup when turn_external is on. In our container test the configuration passed, and the only problems reported were DNS records and certificates, which can't pass on a machine without a public name. On a real server, fix every DNS and certificate warning it prints. If prosodyctl check reports addresses it doesn't recognise because the server sits behind your router, add your public IP to the configuration as external_addresses = { "203.0.113.10" }, as the checker suggests.

A second command lists what clients will find on your server:

sudo prosodyctl check features

With this configuration, it reported basic messaging, multi-device sync, mobile optimizations, profiles, blocking, push notifications, file sharing and group chats as OK. It flagged web connections (BOSH and WebSocket, which only browser clients use) and audio/video calls, which need the TURN server from the next guide.

Online compliance tester

The XMPP Compliance Tester logs in to your server from outside and checks the extensions that modern clients use, including stream management, carbons, message archives, push, file upload, OMEMO and XEP-0368 SRV records. Create a separate test account for it, because the site stores the account's password. Two tests will fail with this configuration until you add the missing parts. External service discovery (STUN and TURN) passes once you set up calls. SOCKS5 bytestreams needs the proxy65 file transfer proxy, which this configuration leaves out because clients now share files through HTTP upload.

Keeping it running

Updates for Prosody come through sudo apt update && sudo apt upgrade, from the repository you added. Run sudo prosodyctl check config before and after a major upgrade, as the Prosody release notes advise. For backups, copy /etc/prosody (configuration and certificates) and /var/lib/prosody (accounts, contact lists, archives and uploaded files).

Snikket, the packaged alternative

If you would rather not maintain a configuration file, Snikket packages Prosody, a coturn TURN server, a web admin portal and automatic Let's Encrypt certificates as Docker containers. You create invitations from the web portal or with docker exec snikket create-invite, and users install the Snikket app or another XMPP client.

The trade-off is control. Snikket expects to own ports 80 and 443 on the machine, so it doesn't sit well next to an existing website. It uses fixed subdomains and also opens a range of about 16,000 UDP ports for calls, which some home routers can't forward. A hand-built Prosody server lets you choose each module, port and limit, and you understand every line of the configuration you run. Both run the same server software and talk to the same XMPP network.

Frequently asked questions

Which ports does an XMPP server need open?

Clients connect on TCP 5222, or 5223 for direct TLS, and other servers connect on TCP 5269. Prosody's file sharing uses HTTPS on TCP 5281, and plain HTTP on 5280 should stay closed.

Do I need SRV records for an XMPP server?

No, not when Prosody runs on the machine your domain's A record points to and uses the standard ports. You need SRV records when the server runs under another name, such as xmpp.example.org, and the SRV target must be a name with an A or AAAA record.

Can I run an XMPP server at home behind CGNAT?

Not directly, because port forwarding on your router does nothing when your provider shares one public address between customers. Ask the provider for a public IPv4 address, or run Prosody on a small rented server.

How do I let friends register on my Prosody server?

Create an invite with sudo prosodyctl shell invite create_account example.org and send them the link it prints. With mod_invites_register loaded, registration without an invite is refused, and the config in this guide makes invite links expire after seven days.

Does Prosody support push notifications?

Yes. The config in this guide loads mod_cloud_notify, which wakes a sleeping phone through its push service when a message arrives. By default it sends neither the message text nor the sender to the push service.

What is the difference between Prosody and Snikket?

Snikket packages Prosody with a TURN server, a web admin portal and automatic certificates in Docker containers. Plain Prosody needs more setup but lets you choose every module, port and limit.

Sources

  1. Prosody: download and latest version
  2. Prosody: package repository for Debian and Ubuntu
  3. Prosody 13.0.0 release notes
  4. Prosody 0.12.0 release notes
  5. Prosody: DNS configuration
  6. Prosody: port and network configuration
  7. Prosody: certificates
  8. Prosody: Let's Encrypt
  9. Prosody: module list
  10. Prosody: mod_mam
  11. Prosody: mod_muc
  12. Prosody: mod_http_file_share
  13. Prosody: mod_cloud_notify
  14. Prosody: mod_invites and mod_invites_register
  15. XEP-0368: SRV records for XMPP over TLS
  16. Certbot user guide
  17. XMPP Compliance Tester: list of tests
  18. Snikket: quick start and firewall ports