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

TypeNameContentNotes
Axmpp.example.comIP addryour main xmpp server address
Aconference.xmpp.example.comIP addrneeded for MUC (Multi User Chat)
Aproxy.xmpp.example.comIP addrneeded for SOCKS5 proxy support
Apubsub.xmpp.example.comIP addrneeded for publish/subscribe support
Aupload.xmpp.example.comIP addrneeded for file uploads
Astun.xmpp.example.comIP addrneeded for audio&video calling
Aturn.xmpp.example.comIP addrneeded 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
  • 5223: Jabber client connections, using the old SSL method
  • 5269: Jabber/XMPP incoming server connections
  • 5280/5443: HTTP/HTTPS for Web Admin and many more
  • 7777: 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:

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

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;
    }
}

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.


  1. Citizen-led initiative collecting information about Chat Controle https://fightchatcontrol.eu ↩︎

  2. Explanation by Patrick Breyer, former member of the European Parliament https://www.patrick-breyer.de/en/posts/chat-control/ ↩︎

This post was created on .