XMPP or Extensible Messaging and Presence Protocol is an open communication protocol.
It has been actively developed since it was introduced in 1999.
Over time, a large part of its user base migrated to centralized cloud platforms.
IRC is a simple, reliable, text-based protocol.
Matrix is a robust but more complex protocol with higher resource requirements.
XMPP sits between them, closer to IRC in simplicity while offering more modern features.
In this article you will learn to install a private, non-federated XMPP server.
Planned Features
By the end of this article you will have:
- 1to1 private messages, optionally end-to-end-encrypted (E2EE) with OMEMO (if your clients support it).
- Shared group chats for small- to medium-sized teams.
- Presence reporting in the client.
- Avatar images.
- Nicknames.
- File uploads and sharing with quotas.
- A private XMPP server running on your own infrastructure.
You will not set up federation (s2s – server to server communication) using this article.
Prerequisites
- Debian or compatible system.
rootorsudopermission.- DNS records pointing to the server.
certbotfor Let’s Encrypt.- Nginx (reverse proxy for httpfiletransfer)
DNS Settings
I use an example domain for the examples.
You must change it to your own domain.
The domains you will see in this article:
- jabber.tomsitcafe.com – Root URL of the service.
- files.jabber.tomsitcafe.com – Domain for file uploads handled by nginx.
- conference.jabber.tomsitcafe.com – Domain for MUC (Multi User Chat) group chats.
Certificates
Install certbot if it’s not present on your system.
Obtain certificates for every domain you use in your stack.
sudo certbot certonly --standalone \ -d jabber.tomsitcafe.com \ -d files.jabber.tomsitcafe.com \ -d conference.jabber.tomsitcafe.com \ --non-interactive \ --agree-tos \ -m certbotadmin@tomsitcafe.com
If you already have nginx, then remove the --standalone option.
You need the --webroot to point certbot to your ACME webroot.
Firewall
For a non-federated XMPP server the following ports are necessary:
- 5222/TCP – the clients connect to this port via TLS.
- 443/TCP – file uploads will use this port through nginx.
Additionally you need 80/TCP for the certbot ACME challenge.
Installation
On Debian 13 or any compatible systems use apt for the installation.
sudo apt install -y prosody prosody-modules
Prosody Configuration
The main configuration file is installed to /etc/prosody/prosody.cfg.lua.
You only need to modify it for admin users in this setup.
Open the file and edit the admins part. Use your planned JID (Jabber ID) here.
admins = { "tom@jabber.tomsitcafe.com" }
Create a file in conf.avail for your virtual host.
It will be included in the main config file after symlinking it to conf.d.
For example sudo vim /etc/prosody/conf.avail/jabber.tomsitcafe.com.cfg.lua.
VirtualHost "jabber.tomsitcafe.com"
ssl = {
key = "/etc/prosody/certs/jabber.tomsitcafe.com.key";
certificate = "/etc/prosody/certs/jabber.tomsitcafe.com.crt";
}
modules_enabled = {
"roster";
"saslauth";
"tls";
"disco";
"pep";
"carbons";
"smacks";
"mam";
"http";
"register";
"vcard4";
}
modules_disabled = { "s2s" }
allow_registration = false
c2s_require_encryption = true
authentication = "internal_hashed"
Component "conference.jabber.tomsitcafe.com" "muc"
modules_enabled = { "muc_mam" }
muc_room_default_public = false
muc_room_default_members_only = true
muc_room_default_persistent = true
restrict_room_creation = true
Component "files.jabber.tomsitcafe.com" "http_file_share"
http_file_share_size_limit = 20 * 1024 * 1024 -- 20 mb
http_file_share_expires_after = 60 * 60 * 24 * 7 -- 1 week
http_file_share_access = "local"
http_external_url = "https://files.jabber.tomsitcafe.com"
This is a basic, minimal configuration.
Enable it by linking the file from conf.avail to conf.d.
sudo ln -s /etc/prosody/conf.avail/jabber.tomsitcafe.com.cfg.lua /etc/prosody/conf.d/
Prosody doesn’t use the Let’s Encrypt certificates in /etc/letsencrypt directly by default.
You import the certs to /etc/prosody/certs with a prosodyctl command.
It ensures that the certificates have the right permissions.
sudo prosodyctl --root cert import /etc/letsencrypt/live
Create a certbot hook to run it when the system gets new certificates.
Load the configuration by restarting Prosody:
sudo systemctl restart prosody.service
Nginx Configuration
In this configuration, file uploads work through an nginx reverse proxy.
Set up a virtual host for the http_file_share module.
Create for example /etc/nginx/sites-available/xmpp-upload.conf.
server {
listen 443 ssl;
server_name files.jabber.tomsitcafe.com;
ssl_certificate /etc/letsencrypt/live/jabber.tomsitcafe.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/jabber.tomsitcafe.com/privkey.pem;
client_max_body_size 20M;
access_log /var/log/nginx/prosody_access.log;
error_log /var/log/nginx/prosody_error.log warn;
location /file_share/ {
proxy_pass http://localhost:5280;
proxy_set_header Host files.jabber.tomsitcafe.com;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
tcp_nodelay on;
}
location / {
return 404;
}
}
Enable the site:
sudo ln -s /etc/nginx/sites-available/xmpp-upload.conf /etc/nginx/sites-enabled/
Check the config syntax:
sudo nginx -t
Restart nginx:
sudo systemctl restart nginx.service
Modules
You enabled the following modules:
- roster – user list
- saslauth – SASL authentication
- tls – TLS connections
- disco – service discovery (group chats, file uploads)
- pep – required for OMEMO
- carbons – multi-device sync
- smacks – stream resumption
- mam – message archive
- http – base http
- register – enables in-band registration and password changes, restricted here by configuration
- vcard4 – avatars, nicks, etc.
- muc – multi user chat (group chats)
- http_file_share – file uploads
To keep the server non-federated, the s2s (server-to-server) module is disabled:
modules_disabled = { "s2s" }
This prevents server-to-server communication in this setup.
For stricter control, you can additionally block outgoing and incoming s2s traffic on port 5269/TCP.
Port 5269/TCP is intentionally not opened, as federation is not used.
Clients
There are numerous clients for XMPP.
Choose the one that fits your requirements.
On xmpp.org there is a database of the XMPP clients.
Here at Tom’s IT Cafe we tested:
- Gajim on desktop
- Conversations on Android
Both support the core features used in this setup.
Final Thoughts
You can see multiple communication protocols Tom’s IT Cafe uses in its ecosystem.
- Matrix is a secure and established protocol.
- IRC with InspIRCd and Anope is a modular, lightweight backup communication solution.
- Ergo Chat is for those who embrace simplicity in a modern IRC server with integrated services.
XMPP is more feature-rich than IRC, yet lighter than Matrix.
You don’t have to pick just one.
You can set up a chain of communication platforms for fallback, or for different communities.
The Silent Architect IRC network is a quiet place.
Discussion is slow.
Silence is normal.
People speak when they have something worth saying.
Idle minds are welcome.
Noise is not.
Server: irc.silentarchitect.org
Port: 6697 (TLS)
Channel: #ghostops
Discord invite link: https://discord.gg/nxvna45STM
Discover more from Tom's IT Cafe
Subscribe to get the latest posts sent to your email.