https://www.codedge.de/posts/modern-messaging-running-your-own-xmpp-server codedge archive cv Modern messaging: Running your own XMPP server (c) Faye Yu 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.^1^ 2 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 curl -o /etc/apt/sources.list.d/ejabberd.list https://repo.process-one.net/ejabberd.list 2 curl -o /etc/apt/trusted.gpg.d/ejabberd.gpg https://repo.process-one.net/ejabberd.gpg 3 apt update 4 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 curl -L \ 2 -o /tmp/ejabberd_2507.deb \ 3 https://github.com/processone/ejabberd/releases/download/25.07/ejabberd_25.07-1_amd64.deb 4 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: * 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 hosts: 2 - xmpp.example.com Set the database type Instead of using the default mnesia type, we opt for sql, better said sqlite. 1 default_db: sql 2 3 host_config: 4 xmpp.example.com: 5 auth_method: sql 6 sql_type: sqlite Generate DH params Generate a fresh set of params for the DH key exchange. In your terminal run 1 sudo mkdir -p /opt/ejabberd && \ 2 openssl dhparam -out /opt/ejabberd/dhparams.pem 4096 and link the new file in the ejabberd configuration. 1 define_marco: 2 # Other config 3 'DH_fiLE': "/opt/ejabberd/dhparams.pem" 4 5 # ... 6 c2s_dhfile: 'DH_FILE' 7 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 listen: 2 - 3 port: 5222 4 ip: "::" 5 module: ejabberd_c2s 6 max_stanza_size: 262144 7 shaper: c2s_shaper 8 access: c2s 9 starttls_required: true 10 protocol_options: 'TLS_OPTIONS' 11 - 12 port: 5223 13 ip: "::" 14 module: ejabberd_c2s 15 max_stanza_size: 262144 16 shaper: c2s_shaper 17 access: c2s 18 tls: true 19 protocol_options: 'TLS_OPTIONS' 20 - 21 port: 5269 22 ip: "::" 23 module: ejabberd_s2s_in 24 max_stanza_size: 524288 25 - 26 port: 5270 27 ip: "::" 28 module: ejabberd_s2s_in 29 tls: true 30 max_stanza_size: 524288 31 - 32 port: 5443 33 ip: "::" 34 module: ejabberd_http 35 tls: true 36 protocol_options: 'TLS_OPTIONS' 37 ciphers: 'TLS_CIPHERS' 38 request_handlers: 39 /admin: ejabberd_web_admin 40 /captcha: ejabberd_captcha 41 /upload: mod_http_upload 42 /ws: ejabberd_http_ws 43 - 44 port: 5280 45 ip: "::" 46 module: ejabberd_http 47 tls: false 48 request_handlers: 49 /.well-known/acme-challenge: ejabberd_acme 50 - 51 port: 3478 52 ip: "::" 53 transport: udp 54 module: ejabberd_stun 55 use_turn: true 56 ## The server's public IPv4 address: 57 turn_ipv4_address: "{{ ipv4 }}" 58 ## The server's public IPv6 address: 59 turn_ipv6_address: "{{ ipv6 }}" 60 - 61 port: 1883 62 ip: "::1" 63 module: mod_mqtt 64 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 acl: 2 admin: 3 user: root 4 # ... 5 proxy_users: 6 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 access_rules: 2 #... 3 configure: 4 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 modules: 2 #... 3 mod_http_upload: 4 put_url: https://@HOST@:5443/upload 5 docroot: /var/www/ejabberd/upload 6 max_size: 10000000 ## 10 MB 7 thumbnail: false 8 custom_headers: 9 "Access-Control-Allow-Origin": "https://@HOST@" 10 "Access-Control-Allow-Methods": "GET,HEAD,PUT,OPTIONS" 11 "Access-Control-Allow-Headers": "Content-Type" 12 #... 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 access_rules: 2 #... 3 register: 4 allow: all 5 #... 6 7 modules: 8 #... 9 mod_register: 10 access: register 11 ip_access: all 12 captcha_protected: true 13 password_strength: 64 14 #... 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 server { 2 listen 80; 3 listen [::]:80; 4 5 server_name xmpp.example.com proxy.xmpp.example.com pubsub.xmpp.example.com conference.xmpp.example.com; 6 7 access_log /var/log/nginx/20-xmpp.access.log; 8 error_log /var/log/nginx/20-xmpp.error.log; 9 10 location = /.well-known/host-meta { 11 default_type 'application/xrd+xml'; 12 add_header Access-Control-Allow-Origin '*' always; 13 14 root /var/www/ejabberd; 15 } 16 location = /.well-known/host-meta.json { 17 default_type 'application/jrd+json'; 18 add_header Access-Control-Allow-Origin '*' always; 19 20 root /var/www/ejabberd; 21 } 22 23 location / { 24 proxy_set_header X-Real-IP $remote_addr; 25 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 26 proxy_set_header X-Forwarded-Proto $scheme; 27 28 proxy_pass http://127.0.0.1:5280; 29 } 30 } 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 5 host-meta.json 1 { 2 "links": [ 3 { 4 "rel": "urn:xmpp:alt-connections:websocket", 5 "href": "wss://xmpp.example.com:5443/ws" 6 } 7 ] 8 } 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. --------------------------------------------------------------------- 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 16 Sep 2025 and updated on 06 Oct 2025 . 100DaysToOffload xmpp messaging codedge 2025 no ads, no cookies, no 3rd-party trackers Goatcounter dashboard Powered by hugo