TechSomething

Ejabberd server, selfhosting our chat audio-videocall server

Why: #

I've used a Jabber service provided by a friend until now but I wanted to selfhost mine.
In addition we wanted to debug some incompatibilities with iOS clients like Siskin IM with a brand new installation.

Guide I've followed: #

I've followed this post:
https://www.aroundtheglobe.biz/posts/20210819-your_own_xmpp_server_with_ejabberd_on_Debian_11_Bullseye.html

Most of it is perfect and correct,
just the part about Letsencrypt have changed in the last version I think (see dedicated part)

I've also tried to link all the resources I've used in the post and the configfiles.

for the advanced configs I've followed:
https://www.process-one.net/blog/how-to-configure-ejabberd-to-get-100-in-xmpp-compliance-test/

Result: #

and 100% score on XMPP Compliance Tester:
description

Let's start: #

what we are using:

Install Ejabberd: #

apt install ejabberd

Firewalling: #

We'll need some (a lot) of ports:

the redirection of port 80 to 5280 is used to allow certbot to generate certificates for our installation, ejabberd will run as a non-privileged user so it won't be able to open ports under 1000.

iptables config:

# Generated by iptables-save v1.8.7 on Sun Jan 23 17:33:37 2022
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [41917:19766826]
:fail2ban-ssh - [0:0]
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp -m multiport --dports 22 -j fail2ban-ssh
-A INPUT -i eth0 -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 5222 -m comment --comment "ejabberd_c2s plain" -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 5223 -m comment --comment "ejabberd_c2s tls" -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 5269 -m comment --comment "ejabberd_s2s_in plain" -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 5270 -m comment --comment "ejabberd_s2s_in tls" -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 5443 -m comment --comment "ejabberd_http TLS" -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 5280 -m comment --comment "ejabberd_http plain" -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp --dport 5349 -m comment --comment "ejabberd_stun TCP" -j ACCEPT
-A INPUT -i eth0 -p udp -m udp --dport 3478 -m comment --comment "ejabberd_stun UDP" -j ACCEPT
-A INPUT -i eth0 -p udp -m udp -m multiport --dports 49152:65535 -m comment --comment "stun UDP" -j ACCEPT
-A INPUT -i eth0 -p tcp -m tcp -m multiport --dports 49152:65535 -m comment --comment "stun TCP" -j ACCEPT
-A INPUT -p icmp -m icmp --icmp-type 8 -j ACCEPT
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -j DROP
-A OUTPUT -o lo -j ACCEPT
-A fail2ban-ssh -j RETURN
COMMIT
# Completed on Sun Jan 23 17:33:37 2022
# Generated by iptables-save v1.8.7 on Sun Jan 23 17:33:37 2022
*nat
:PREROUTING ACCEPT [281:19041]
:INPUT ACCEPT [307:20513]
:OUTPUT ACCEPT [1287:92719]
:POSTROUTING ACCEPT [1287:92719]
-A PREROUTING -i eth0 -p tcp -m tcp --dport 80 -j REDIRECT --to-ports 5280
-A OUTPUT -p tcp -m tcp --dport 80 -j REDIRECT --to-ports 5280
COMMIT
# Completed on Sun Jan 23 17:33:37 2022

NET Config: #

I've used a VPS that has a local IP and a Public ip, but the second one is not directly on the network interface of our VM.

our domain will be: banana.io

(the IPs and domain are fictional)

DNS config: #

we'll need to configure some subdomains:

pointing at our server (see: https://www.process-one.net/blog/ejabberd-xmpp-server-useful-configuration-steps/)

and some SRV records for clients and servers, see: https://wiki.xmpp.org/web/SRV_Records

our final DNS config:

@ 300 IN A 111.222.333.444
conference 300 IN A 111.222.333.444
proxy 300 IN A 111.222.333.444
pubsub 300 IN A 111.222.333.444
upload 300 IN A 111.222.333.444

_stun._tcp 300 IN SRV 5 0 3478 conference.banana.io.
_stun._udp 300 IN SRV 5 0 3478 conference.banana.io.
_stuns._tcp 300 IN SRV 5 0 5349 conference.banana.io.
_turn._tcp 300 IN SRV 5 0 3478 conference.banana.io.
_turn._udp 300 IN SRV 5 0 3478 conference.banana.io.
_turns._tcp 300 IN SRV 5 0 5349 conference.banana.io.
_xmpp-client._tcp 300 IN SRV 5 0 5222 conference.banana.io.
_xmpp-server._tcp 300 IN SRV 5 0 5269 conference.banana.io.
_xmpps-client._tcp 300 IN SRV 5 0 5223 conference.banana.io.
_xmpps-server._tcp 300 IN SRV 5 0 5270 conference.banana.io.

I want to try to do the same but on a subdomain, like chat.banana.io instead of banana.io (these are fictional domains)

Create the uploads folder: #

mkdir -p /var/www/upload
chown ejabberd:ejabberd /var/www/upload

Ejabberd config: #

we can find the config in: /etc/ejabberd/ejabberd.yml

###
### ejabberd configuration file
###
### The parameters used in this configuration file are explained at
###
### https://docs.ejabberd.im/admin/configuration
###
### The configuration file is written in YAML.
### *******************************************************
### ******* !!! WARNING !!! *******
### ******* YAML IS INDENTATION SENSITIVE *******
### ******* MAKE SURE YOU INDENT SECTIONS CORRECTLY *******
### *******************************************************
### Refer to http://en.wikipedia.org/wiki/YAML for the brief description.
###


# loglevel: Verbosity of log files generated by ejabberd
loglevel: info

# rotation: Disable ejabberd's internal log rotation, as the Debian package
# uses logrotate(8).
log_rotate_count: 0

# hosts: Domains served by ejabberd.
# You can define one or several, for example:
# hosts:
# - "example.net"
# - "example.com"
# - "example.org"

hosts:
- banana.io

certfiles:
- "/etc/ejabberd/ejabberd.pem"
# - /etc/letsencrypt/live/localhost/fullchain.pem
# - /etc/letsencrypt/live/localhost/privkey.pem

# TLS configuration
define_macro:
'TLS_CIPHERS': "HIGH:!aNULL:!eNULL:!3DES:@STRENGTH"
'TLS_OPTIONS':
- "no_sslv3"
- "no_tlsv1"
- "no_tlsv1_1"
- "cipher_server_preference"
- "no_compression"
# 'DH_FILE': "/path/to/dhparams.pem"
# generated with: openssl dhparam -out dhparams.pem 2048

c2s_ciphers: 'TLS_CIPHERS'
c2s_protocol_options: 'TLS_OPTIONS'
s2s_protocol_options: 'TLS_OPTIONS'


#s2s_ciphers: 'TLS_CIPHERS'
###
### SOURCE: https://www.process-one.net/blog/securing-ejabberd-with-tls-encryption/
s2s_use_starttls: required
s2s_dhfile: /etc/ssl/ejabberd/dh2048.pem
s2s_ciphers: "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256"

# c2s_dhfile: 'DH_FILE'
# s2s_dhfile: 'DH_FILE'

listen:
-
port: 5222
ip: 0.0.0.0
module: ejabberd_c2s
max_stanza_size: 262144
shaper: c2s_shaper
access: c2s
starttls_required: true
#protocol_options: 'TLS_OPTIONS'
###
### SOURCE: https://www.process-one.net/blog/securing-ejabberd-with-tls-encryption/
protocol_options:
- no_sslv2
- no_sslv3
- no_tlsv1
- no_tlsv1_1
ciphers: "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256"
starttls: true
tls_compression: false
dhfile: /etc/ssl/ejabberd/dh2048.pem
-
port: 5223
ip: 0.0.0.0
module: ejabberd_c2s
max_stanza_size: 262144
shaper: c2s_shaper
access: c2s
tls: true
protocol_options: 'TLS_OPTIONS'
-
port: 5269
ip: 0.0.0.0
module: ejabberd_s2s_in
max_stanza_size: 524288
###
### SOURCE: https://www.process-one.net/blog/securing-ejabberd-with-tls-encryption/
protocol_options:
- no_sslv2
- no_sslv3
- no_tlsv1
- no_tlsv1_1
-
port: 5443
ip: 0.0.0.0
module: ejabberd_http
tls: true
protocol_options: 'TLS_OPTIONS'
request_handlers:
/api: mod_http_api
/bosh: mod_bosh
## /captcha: ejabberd_captcha
/upload: mod_http_upload
/ws: ejabberd_http_ws
-
port: 5280
ip: 0.0.0.0
module: ejabberd_http
tls: false
protocol_options: 'TLS_OPTIONS'
request_handlers:
#/admin: ejabberd_web_admin
/.well-known/acme-challenge: ejabberd_acme
-
port: 3478
#ip: 0.0.0.0
#transport: udp
#module: ejabberd_stun
#use_turn: true
### The server's public IPv4 address:
#turn_ipv4_address: "111.222.333.444"
## The server's public IPv6 address:
# turn_ipv6_address: "2001:db8::3"
###
### SOURCE:
### https://www.process-one.net/blog/how-to-set-up-ejabberd-video-voice-calling/
transport: udp
module: ejabberd_stun
use_turn: true
turn_min_port: 49152
turn_max_port: 65535
## The server's public IPv4 address:
turn_ipv4_address: 111.222.333.444
-
port: 5349
###
### SOURCE:
### https://www.process-one.net/blog/how-to-set-up-ejabberd-video-voice-calling/
transport: tcp
module: ejabberd_stun
use_turn: true
tls: true
turn_min_port: 49152
turn_max_port: 65535
#ip returns an error if the ip is not on the interface, so we need to sepcify the private ip here:
ip: 192.168.1.10
turn_ipv4_address: 111.222.333.444
-
port: 1883
ip: 0.0.0.0
module: mod_mqtt
backlog: 1000


acme:
## Staging environment
#ca_url: https://acme-staging-v02.api.letsencrypt.org/directory
## Production environment (the default):
ca_url: https://acme-v02.api.letsencrypt.org/directory
auto: true
#contact: "user@banana.io"
#ca_url: "https://acme-v02.api.letsencrypt.org"

## Disabling digest-md5 SASL authentication. digest-md5 requires plain-text
## password storage (see auth_password_format option).
disable_sasl_mechanisms:
- "digest-md5"
- "X-OAUTH2"

## Store the plain passwords or hashed for SCRAM:
auth_password_format: scram

## Full path to a script that generates the image.
## captcha_cmd: "/usr/share/ejabberd/captcha.sh"

acl:
admin:
user:
- "admin@banana.io"

local:
user_regexp: ""
loopback:
ip:
- 127.0.0.0/8

access_rules:
local:
allow: local
c2s:
deny: blocked
allow: all
announce:
allow: admin
configure:
allow: admin
muc_create:
allow: local
pubsub_createnode:
allow: local
trusted_network:
allow: loopback

api_permissions:
"console commands":
from:
- ejabberd_ctl
who: all
what: "*"
"admin access":
who:
access:
allow:
- acl: loopback
- acl: admin
oauth:
scope: "ejabberd:admin"
access:
allow:
- acl: loopback
- acl: admin
what:
- "*"
- "!stop"
- "!start"
"public commands":
who:
ip: 127.0.0.1/8
what:
- status
- connected_users_number

shaper:
normal:
rate: 3000
burst_size: 20000
fast: 200000

shaper_rules:
max_user_sessions: 10
max_user_offline_messages:
5000: admin
100: all
c2s_shaper:
none: admin
normal: all
s2s_shaper: fast

modules:
mod_adhoc: {}
mod_admin_extra: {}
mod_announce:
access: announce
mod_avatar: {}
mod_blocking: {}
mod_bosh: {}
mod_caps: {}
mod_carboncopy: {}
mod_client_state: {}
mod_configure: {}
## mod_delegation: {} # for xep0356
mod_disco: {}
mod_fail2ban: {}
mod_http_api: {}

mod_http_upload:
put_url: https://@HOST@:5443/upload
docroot: /var/www/upload
custom_headers:
"Access-Control-Allow-Origin": "https://@HOST@"
"Access-Control-Allow-Methods": "GET,HEAD,PUT,OPTIONS"
"Access-Control-Allow-Headers": "Content-Type"
#SOURCE: https://www.process-one.net/blog/how-to-configure-ejabberd-to-get-100-in-xmpp-compliance-test/

mod_last: {}

mod_mam:
## Mnesia is limited to 2GB, better to use an SQL backend
## For small servers SQLite is a good fit and is very easy
## to configure. Uncomment this when you have SQL configured:
## db_type: sql
assume_mam_usage: true
default: always

mod_mqtt: {}
mod_muc:
access:
- allow
access_admin:
- allow: admin
access_create: muc_create
access_persistent: muc_create
access_mam:
- allow
default_room_options:
mam: true
### SOURCE: https://www.process-one.net/blog/ejabberd-xmpp-server-useful-configuration-steps/
allow_subscription: true
persistent: true
mod_muc_admin: {}
mod_offline:
access_max_user_messages: max_user_offline_messages
mod_ping: {}
mod_pres_counter:
count: 5
interval: 60
mod_privacy: {}
mod_private: {}
mod_proxy65:
access: local
max_connections: 5
mod_pubsub:
access_createnode: pubsub_createnode
plugins:
- flat
- pep
force_node_config:
"eu.siacs.conversations.axolotl.*":
access_model: open
## Avoid buggy clients to make their bookmarks public
storage:bookmarks:
access_model: whitelist
mod_push: {}
mod_push_keepalive: {}
## mod_register:
## ## Only accept registration requests from the "trusted"
## ## network (see access_rules section above).
## ## Think twice before enabling registration from any
## ## address. See the Jabber SPAM Manifesto for details:
## ## https://github.com/ge0rg/jabber-spam-fighting-manifesto
## ip_access: trusted_network
mod_register:
ip_access: trusted_network
mod_roster:
versioning: true
mod_s2s_dialback: {}
mod_shared_roster: {}
mod_sic: {}
mod_stream_mgmt:
resend_on_timeout: if_offline

### SOURCE: https://www.process-one.net/blog/how-to-set-up-ejabberd-video-voice-calling/
mod_stun_disco:
credentials_lifetime: 12h
services:
-
host: 111.222.333.444
port: 3478
type: stun
transport: udp
restricted: false
-
host: 111.222.333.444
port: 3478
type: turn
transport: udp
restricted: true
-
host: banana.io
port: 5349
type: stuns
transport: tcp
restricted: false
-
host: banana.io
port: 5349
type: turns
transport: tcp
restricted: true
mod_vcard:
search: false
mod_vcard_xupdate: {}
mod_version:
show_os: false

### Local Variables:
### mode: yaml
### End:
### vim: set filetype=yaml tabstop=8

Generate DH key: #

mkdir /etc/ssl/ejabberd/
openssl dhparam -out /etc/ssl/ejabberd/dh2048.pem 2048

Start the server: #

systemctl start ejabberd.service

About letsencrypt #

NB: I HAVE TO RE-TEST THIS PART:

In one of the latest versions ejabberd supports natively letsencrypt,
just some notes:

From my experience it's better to start with "auto: false" in your ejabberd "acme" configuration,
and issue manually the generation of the certificates:

ejabberdctl request-certificate all

for the webroot give: /etc/ejabberd

when the certificates have been generated then you can switch to "auto: true"

also:
when testing remember to change "ca_url" to the staging url

https://acme-staging-v02.api.letsencrypt.org/directory

otherwise you'll incur in letsencrypt's rate limiting, see: https://letsencrypt.org/docs/rate-limits/

you can check the certificates with this command:

ejabberdctl list-certificates

sample output:

conference.banana.io	/var/lib/ejabberd/acme/live/11111	true
proxy.banana.io		/var/lib/ejabberd/acme/live/22222	true
pubsub.banana.io	/var/lib/ejabberd/acme/live/33333	true
banana.io		/var/lib/ejabberd/acme/live/44444	true
upload.banana.io	/var/lib/ejabberd/acme/live/55555	true

NB: I've yet to test the autorenew but I suppose it should be automatic.

see: https://docs.ejabberd.im/admin/configuration/basic/#acme

NB:
in ejabberd config we are not using ejabberd.pem (I've deleted it),
if you want to remove the entry you have to remove also "certfiles:"

#certfiles:
#  - "/etc/ejabberd/ejabberd.pem"

otherwise you'll get this error:

Failed to start ejabberd application: Invalid value of option certfiles: Expected list, got empty string instead

Useful stuff: #

Create user: #

ejabberdctl register USERNAME banana.io PASSWORD

Logs (are your friends): #

tail -f /var/log/ejabberd/*

Migrating to a new server: #

copy:

/etc/ejabberd
/etc/ssl/ejabberd
/var/lib/ejabberd

to the new machine,
if the Erlang node name is the same (it seems it's based on the hostname of the machine) then you're set, otherwise you'll need to convert the Mnesia database following this guide at "Change Computer Hostname":

https://docs.ejabberd.im/admin/guide/managing/

I've not followed the guide since I've managed to keep the hostnames the same so i've just moved the files and installed ejabberd, and voila', i was set to go.

Clustering: #

Not in my todo list, but here it is:

https://docs.ejabberd.im/admin/guide/clustering/

Sources: #

https://www.aroundtheglobe.biz/posts/20210819-your_own_xmpp_server_with_ejabberd_on_Debian_11_Bullseye.html