Modern messaging: Running your own XMPP server
Since a years we know, or might suspect, our chats are listend on, our uploaded files are sold for advertising or what purpose ever and the chance our social messengers leak our private data is incredibly high. It is about time to work against this.
Since 3 years the European Commission works on a plan to automatically monitor all chat, email and messenger conversations. If this is going to pass, and I strongly hope it will not, the European Union is moving into a direction we know from states suppressing freedom of speech.
I went for setting up my own XMPP server, as this does not have any big resource requirements and still support clustering (for high-availabilty purposes), encryption via OMEMO, file sharing and has support for platforms and operating systems. Also the ecosystem with clients and multiple use cases evolved over the years to provide rock-solid software and solutions for multi-user chats or event audio and video calls.
Info All steps and settings are bundled in a repository containing Ansible roles: https://codeberg.org/codedge/chat
All code snippets written below work in either Debian os Raspberry Pi OS.
Setting up your own XMPP server
The connection from your client to the XMPP server is encrypted and we need certificates for our server. First thing to do is setting up our domains and point it to the IP - both IPv4 and IPv6 is supported and we can specify both later in our configuration.
I assume the server is going to be run under xmpp.example.com and you all the following domains have been set up.
Type Name Notes A xmpp.example.com your main xmpp server address A conference.xmpp.example.com needed for MUC (Multi User Chat) A proxy.xmpp.example.com needed for SOCKS5 proxy support A pubsub.xmpp.example.com needed for publish/subscribe support A upload.xmpp.example.com needed for file uploads A stun.xmpp.example.com needed for audio&video calling A turn.xmpp.example.com needed for audio&video calling
Fill in the IPv6 addresses accordingly.
ejabberd is a robust server software, that is included in most Linux distributions.
Install from Process One repository
I discovered ProcessOne, the company behind ejabberd, also provides a Debian repository.
1 2 3 4 curl -o /etc/apt/sources.list.d/ejabberd.list https://repo.process-one.net/ejabberd.list curl -o /etc/apt/trusted.gpg.d/ejabberd.gpg https://repo.process-one.net/ejabberd.gpg apt update apt install ejabberd
Install from Github
To get the most recent one, I use the packages offered in their code repository. Installing version 25.07 just download the asset from the release:
1 2 3 4 curl -L \ -o /tmp/ejabberd_2507.deb \ https://github.com/processone/ejabberd/releases/download/25.07/ejabberd_25.07-1_amd64.deb apt install /tmp/ejabberd_2507.deb
Make sure the fowolling ports are opened in your firewall, taken from ejabberd firewall settings.
5222 : Jabber/XMPP client connections, plain or STARTTLS
: Jabber/XMPP client connections, plain or STARTTLS 5223 : Jabber client connections, using the old SSL method
: Jabber client connections, using the old SSL method 5269 : Jabber/XMPP incoming server connections
: Jabber/XMPP incoming server connections 5280/5443 : HTTP/HTTPS for Web Admin and many more
: HTTP/HTTPS for Web Admin and many more 7777 : SOCKS5 file transfer proxy
: SOCKS5 file transfer proxy 3478/5349: STUN+TURN/STUNS+TURNS service
Port 1883 , used for MQTT, is also mentioned in the ejabberd docs, but we do not use this in our setup. So this port stays closed.
Depending how you installed ejabberd the config file is either at /etc/ejabberd/conf/ejabberd.yml or /opt/ejabberd/conf/ejabberd.yml .
General configuration
The configuration is a balance of 70:30 between having a privacy-focused setup for your users and meeting most of the suggestions of the XMPP complicance test. That means, settings that protect the provacy of the users are higher rated despite not passing the test.
Therefore notable privacy and security settings are:
XMPP over HTTP is disabled (mod_bosh)
Discover then a user last accessed a server is disabled (mod_last)
Delete uploaded files on a regular base (see upload config)
Register account via a web page is disabled (mod_register_web)
In-band registration can be enabled, default off, captcha secured (mod_register, see registration config)
Info The configuration file is in YAML format. Keep an eye for indentation.
Let’s start digging into the configuration.
Set the domain of your server
1 2 hosts : - xmpp.example.com
Set the database type
Instead of using the default mnesia type, we opt for sql , better said sqlite .
1 2 3 4 5 6 default_db : sql host_config : xmpp.example.com : auth_method : sql sql_type : sqlite
Generate DH params
Generate a fresh set of params for the DH key exchange. In your terminal run
1 2 sudo mkdir -p /opt/ejabberd && \ openssl dhparam -out /opt/ejabberd/dhparams.pem 4096
and link the new file in the ejabberd configuration.
1 2 3 4 5 6 7 define_marco : # Other config 'DH_fiLE' : "/opt/ejabberd/dhparams.pem" # ... c2s_dhfile : 'DH_FILE' s2s_dhfile : 'DH_FILE'
Ensure TLS for server-to-server connections
Use TLS for server-to-server (s2s) connections.
1 s2s_use_starttls: required
The listners
The listeners aka request_handlers inside the config especially for /admin , /captcha , /upload and /ws are important. All of them listen on port 5443 . Only one request handler is attached to port 5280 , the /.well-known/acme-challenge .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 listen : - port : 5222 ip : "::" module : ejabberd_c2s max_stanza_size : 262144 shaper : c2s_shaper access : c2s starttls_required : true protocol_options : 'TLS_OPTIONS' - port : 5223 ip : "::" module : ejabberd_c2s max_stanza_size : 262144 shaper : c2s_shaper access : c2s tls : true protocol_options : 'TLS_OPTIONS' - port : 5269 ip : "::" module : ejabberd_s2s_in max_stanza_size : 524288 - port : 5270 ip : "::" module : ejabberd_s2s_in tls : true max_stanza_size : 524288 - port : 5443 ip : "::" module : ejabberd_http tls : true protocol_options : 'TLS_OPTIONS' ciphers : 'TLS_CIPHERS' request_handlers : /admin : ejabberd_web_admin /captcha : ejabberd_captcha /upload : mod_http_upload /ws : ejabberd_http_ws - port : 5280 ip : "::" module : ejabberd_http tls : false request_handlers : /.well-known/acme-challenge : ejabberd_acme - port : 3478 ip : "::" transport : udp module : ejabberd_stun use_turn : true ## The server's public IPv4 address: turn_ipv4_address : "{{ ipv4 }}" ## The server's public IPv6 address: turn_ipv6_address : "{{ ipv6 }}" - port : 1883 ip : "::1" module : mod_mqtt backlog : 1000
ACLs & Access rules
For adminstration of ejabberd we need a user with admin rights and properly set up ACLs and access rules. There is a separat section for ACLs inside the config in which we set up an admin user name root . The name of the user is important for later, when we actually create this user.
1 2 3 4 5 6 acl: admin: user: root # ... proxy_users: server: xmpp.example.com
The access_rules should already be set up, just to confirm that you have a correct entry for the configure action.
1 2 3 4 access_rules: #... configure: allow: admin
Now the new root user needs to be create by running this command on the console. Watch out to put in the correct domain.
1 ejabberdctl register root xmpp.example.com 'my_password_here'
Another user can be registered with the same command.
We set root as the admin user in the config previously. That is how ejabberd knows which user has admin permissions.
Enable file uploads
Enabling file uploads is done with mod_http_upload . First, create a folder where the uploads should be stored.
1 mkdir -p /var/www/ejabberd/upload
Now update the ejabberd configuration like this:
1 2 3 4 5 6 7 8 9 10 11 12 modules : #... mod_http_upload : put_url : https://@HOST@:5443/upload docroot : /var/www/ejabberd/upload max_size : 10000000 ## 10 MB thumbnail : false custom_headers : "Access-Control-Allow-Origin": "https://@HOST@" "Access-Control-Allow-Methods": "GET,HEAD,PUT,OPTIONS" "Access-Control-Allow-Headers": "Content-Type" #...
The allowed file upload size is defined in the max_size param and is set to 10MB.
Make sure, to delete uploaded files in a reasonable amount of time via cronjob. This is an example of a cronjob, that deletes files that are older than 1 week.
1 0 * * * * find /var/www/ejabberd/upload/upload -type f -cmin +10080 -exec rm -rf {}
Registration
Registration in ejabberd is done via mod_register and can be enabled with these entries in the config file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 access_rules : #... register : allow : all #... modules : #... mod_register : access : register ip_access : all captcha_protected : true password_strength : 64 #...
If you want to enable registration for your server make sure you enable a captcha for it. Otherwise you will get a lot of spam and fake registrations.
ejabberd provides a working captcha script, that you can copy to your server and link in your configuration. You will need imaggemagick and gstools installed on you system. In the ejabberd.yml config file
Add TLS
ejabberd can provision TLS certificates on its own. No need to install certbot. To not expose ejabberd directly to the internet, nginx is put in front of the XMPP server. Instead of using nginx, every other web server (caddy, …) or proxy can be used as well.
Here is a sample config for nginx:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 server { listen 80; listen [::]:80; server_name xmpp.example.com proxy.xmpp.example.com pubsub.xmpp.example.com conference.xmpp.example.com; access_log /var/log/nginx/20-xmpp.access.log; error_log /var/log/nginx/20-xmpp.error.log; location = /.well-known/host-meta { default_type 'application/xrd+xml'; add_header Access-Control-Allow-Origin '*' always; root /var/www/ejabberd; } location = /.well-known/host-meta.json { default_type 'application/jrd+json'; add_header Access-Control-Allow-Origin '*' always; root /var/www/ejabberd; } location / { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass http://127.0.0.1:5280; } }
Alternative connection methods
The nginx vhosts offers files, host-meta and host-meta.json , for indicating which other connection methods (BOSH, WS) your server offers. The details can be read in XEP-0156 extension. Opposite to the examples in the XEP, there is no BOSH, but only a websocket connection our server offers. The BOSH part is removed from the config file.
host-meta
1 2 3 4 5
host-meta.json
1 2 3 4 5 6 7 8 { "links" : [ { "rel" : "urn:xmpp:alt-connections:websocket" , "href" : "wss://xmpp.example.com:5443/ws" } ] }
Put that file in a folder your nginx serves. Have a look at the path and URL it is expected to be, see .well-known .
Choose your client
Clients I can recommend are Profanity, an easy to use command-line client, and Monal for MacOS and iOS. A good overview of client can be found on the offical XMPP website.