I will be keeping this post up to date to keep track on how to configure and mantain an ejabberd server working efficiently and secure. I strongly advise any reader to read carefully what is written here and not just copy-and-paste the configuration file.
My blog also contains a bunch of other posts regarding ejabberd that are worth giving a look at, use the search form.

Server


CentOS 7.5.1804 x86_64
Erlang/OTP 21.1.1-1 x86_64
ejabberd 18.09

Client


LineageOS 15.1 (Android Nougat)
Conversations 2.3.5+fcr

.:. Installation and initial configuration

Download and install erlang (release numbers here may not be up to date):


[root@CentOS ~]$ cd /tmp/
[root@CentOS tmp]$ wget http://packages.erlang-solutions.com/site/esl/esl-erlang/FLAVOUR_1_general/esl-erlang_17.1-1~centos~6_amd64.rpm
[root@CentOS tmp]$ yum localinstall esl-erlang_17.1-1~centos~6_amd64.rpm

Alternatively add erlang-solutions repo erlang-solutions.com:


[root@CentOS ~]$ cd /tmp/
[root@CentOS tmp]$ wget http://packages.erlang-solutions.com/erlang-solutions-1.0-1.noarch.rpm
[root@CentOS tmp]$ rpm -Uvh erlang-solutions-1.0-1.noarch.rpm
[root@CentOS tmp]$ yum install esl-erlang

As for ejabberd, ProcessOne also provides a few precompiled installers but when installed this way I was not able to make it run as unpriviledged user (no one should feel confortable with having an internet exposed service running as root) so the only viable option here is to compile from source. Get the source from process-one.net, install the required dependencies, compile and install:


[root@CentOS ~]$ tar -xvf ejabberd-xxx.tgz
[root@CentOS ~]$ yum install gcc gcc-c++ expat-devel openssl-devel automake git libyaml libyaml-devel
[root@CentOS ~]$ cd ejabberd-xxx

### where `ejabber` is the unpriviledged user that will run the ejabberd service
[root@CentOS ejabberd-xxx]$ ./configure --enable-user=ejabberd --disable-graphics

[root@CentOS ejabberd-xxx]$ make && make install

Now ejabberd should be installed, if everything went well it is time to create a jabber user and grant him admin priviledges:


[root@CentOS ~]$ ejabberdctl register admin domain.tld password
[root@CentOS ~]$ vi /usr/local/etc/ejabberd/ejabberd.yml
---
acl:
  admin:
    user:
      - "admin": "domain.tld"

.:. TL;DR - configuration file

The complete configuration file I use can be downloaded uwot.eu/ejabberd.yml.
Replace domain.tld with a valid domain name and admin with a valid user (the one created before).
Also check that .pem files are following the same naming scheme and that all the paths are correct.

.:. Securing the connection

Create a self signed SSL cerficate:


[root@CentOS /]$ cd /usr/local/etc/ejabberd/ejabberd.yml
[root@CentOS ejabberd]$ openssl req -x509 -sha256 -nodes -days 1826 -newkey rsa:2048 -keyout ejabberd.pem -out ejabberd.pem
[root@CentOS ejabberd]$  openssl dhparam -out dhparams.pem 4096
[root@CentOS ejabberd]$ chmod 600 ejabberd.pem dhparams.pem && chown ejabberd:ejabberd ejabberd.pem dhparams.pem

4096 might be overkill but better be on the safe side, SHA-2 is also used instead of the not so secure SHA-1.
The certificate will be used to encrypt server-to-server (s2s), client-to-server (c2s), ejabberd_http and HTTP File Upload connections.
Alternatively a valid certificate issued by letsencrypt can be used, to do so create a single .pem file containing the private key followed by fullchain:


[root@CentOS /]$ cd /etc/letsencrypt/live/domain.tld
[root@CentOS /]$ cat privkey.pem fullchain.pem /usr/local/etc/ejabberd/ejabberd.pem

Also define some macro in ejabberd.yml configuration file:


define_macro:
   'TLS_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"
   'TLS_OPTIONS':
      - "no_sslv2, no_sslv3, no_tlsv1"
      - "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"
      - "no_compression"
   'DH_FILE': "/usr/local/etc/ejabberd/dhparams.pem" # generated with: openssl dhparam -out dhparams.pem 4096
   
c2s_dhfile: 'DH_FILE'
s2s_dhfile: 'DH_FILE'
c2s_ciphers: 'TLS_CIPHERS'
s2s_ciphers: 'TLS_CIPHERS'
c2s_protocol_options: 'TLS_OPTIONS'
s2s_protocol_options: 'TLS_OPTIONS'

.:. c2s aka client-to-server

This section defines how client to server connections are handled, what kind of encryption is used and a bunch of other important stuff.


listen:
  -
    port: 5222
    ip: "0.0.0.0"
    module: ejabberd_c2s
    max_stanza_size: 65536
    shaper: c2s_shaper
    access: c2s
    zlib: false
    starttls: true
    starttls_required: true
    tls_compression: false
    protocol_options: 'TLS_OPTIONS'
    dhfile: 'DH_FILE'
    ciphers: 'TLS_CIPHERS'

Zlib stream compression is disabled because it poses a security threat when used alongside STARTTLS.
The ciphers paramater in ejabberd’s config file accept every cipher supported by OpenSSL, this can be checked with openssl ciphers command.
To be sure that the selected cipher is actually being used run: openssl s_client -connect host:port -starttls xmpp
Some old insecure ciphers are also disabled.

.:. s2s aka server-to-server

XMPP protocol, just like email, is based on the concept of federation, everyone can run his own server and still be able to communicate with people using other servers. This section defines how server to server connections are handled, what kind of encryption is used and more.


s2s_use_starttls: required

listen:
  -
    port: 5269
    ip: "0.0.0.0"
    module: ejabberd_s2s_in
    max_stanza_size: 131072
    shaper: s2s_shaper
    tls_compression: false

I like to enforce STARTTLS for server-to-server connections too; certfile, dhparam and protocol options we already specified outside the s2s listener section by using s2s_dhfile, s2s_ciphers and s2s_protocol_options.

.:. HTTP File Upload

Use HTTP File Upload (XEP-0363), more info in the following link: HTTP file upload.
A SOCKS5 proxy can also be used to allow file sharing betwen clients, for more information refer to: SOCKS5 proxy.

.:. Audio and Video calls

Refer to the following post for more informations: link

.:. Legacy service and startup script

This is not needed on CentOS 7 since it uses Systemd (sigh).
To run ejabberd automatically at boot and manage it with service ejabberd _option_ copy the following script in /etc/init.d/.


#!/bin/sh
#
# ejabberd Startup script for the ejabberd XMPP Server
#
# chkconfig: - 99 10
# description: ejabberd XMPP server

# Source function library.
. /etc/init.d/functions

set -o errexit
set -o nounset

export HOME=/root

RETVAL=0

start() {
        echo ".:. Starting ejabberd..."
        ejabberdctl start
	sleep 5
        return $RETVAL      
}

stop() {
        echo ".:. Shutting down ejabberd... "
        ejabberdctl stop
	sleep 5
        return $RETVAL
}

status() {
        echo ".:. Checking ejabberd status:"
        ejabberdctl status
        return $RETVAL
}

restart() {
        echo ".:. Restarting ejabberd..."
        ejabberdctl restart
	sleep 5
        return $RETVAL
}

case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    status)
        status
        ;;
    restart)
        restart
        ;;
    *)
        echo "Usage: ejabberd {start|stop|status|restart}"
        exit 1
        ;;
esac
exit $RETVAL

…and:


[root@CentOS ~]$ chkconfig --add ejabberd
### run ejabberd at system startup
[root@CentOS ~]$ chkconfig ejabberd on|off
### manage ejabberd service
[root@CentOS ~]$ service ejabberd {start|stop|status|restart}

.:. Systemd unit script

Ejabberd source code also include a systemd unit script, it is named ejabberd.service and can be found in the root of the tar archive.
To install it simply copy it in the right directory:


cp ejabberd.service /etc/systemd/system/multi-user.target.wants/

…and:


### run ejabberd at system startup
[root@CentOS ~]$ systemctl enable|disable ejabberd
### manage ejabberd service
[root@CentOS ~]$ ejabberdctl {start|stop|status|restart}

.:. Logrotate logs

In case it is not installed by default install logrotate.


[root@CentOS ~]$ yum install logrotate

Define how the rotation will happen:


[root@CentOS ~]$ vi /etc/logrotate.d/ejabberd
---
/usr/local/var/log/ejabberd/ejabberd.log {
     weekly
     missingok
     rotate 10
     compress
     delaycompress
     ifempty
     nomail
     sharedscripts
     postrotate /usr/local/sbin/ejabberdctl --node ejabberd reopen-log > /dev/null
     endscript
}

.:. Troubleshooting

If for some reason ejabberd fail to start check out this page: Erlang cookie Erlang cookie file (placed in /root/ in my case) must be chmodded to 600 and owned by ejabberd user and group.
Another useful resource is the official installation and configuration guide: Ejabberd documentation.

.:. Automating ejabberd upgrading procedure with Ansible

Refer to: Ansible Ejabberd upgrade procudure.

.:. Clients

The one I suggest to use is Conversations on Android, ChatSecure on iOS and Gajim on non botnet devices.
Everyone of these three clients are free open source libre software and works really well with ejabberd.