ejabberd server guide

2023-09-16


Prerequisites:

  • A domain name
  • A server

This tutorial begins with a single virtualhost1. For example purposes, example.net is the domain. Replace all instances of example.net with your own domain.

checklist!:

Step 1: Set up the DNS records

DNS A/AAAA records and SRV records are required for each service on the XMPP server.

A records:

  • example.net
  • muc.example.net (for group chats)
  • upload.example.net (for HTTP file upload)
  • pubsub.example.net (for the pubsub node)
  • proxy.example.net (for file transfer proxy)
  • turn.example.net (for STUN/TURN)

Each pointing to the IP address of the server that is going to run Ejabberd.

Create SRV records for each, pointing to a domain that resolves to the server, as so:

(All records are in the form _service._proto.name IN SRV priority weight port target)

_xmpp-client._tcp   IN SRV  5 0 5222 example.net.
_xmpps-client._tcp  IN SRV  5 0 5223 example.net.
_xmpp-server._tcp   IN SRV  5 0 5269 example.net.
_xmpps-server._tcp  IN SRV  5 0 5270 example.net.

and

_xmpp-client._tcp.muc   IN SRV  5 0 5222 example.net.
_xmpps-client._tcp.muc  IN SRV  5 0 5223 example.net.
_xmpp-server._tcp.muc   IN SRV  5 0 5269 example.net.
_xmpps-server._tcp.muc  IN SRV  5 0 5270 example.net.

for each of the subdomains (starting with muc). Exclude turn.example.net.

Lastly, add one set of SRV records, for STUN/TURN.

_stun._udp   IN SRV  5 0 3478 turn.example.net.
_stun._tcp   IN SRV  5 0 3478 turn.example.net.
_stuns._tcp  IN SRV  5 0 5349 turn.example.net.

_turn._udp   IN SRV  5 0 3478 turn.example.net.
_turn._tcp   IN SRV  5 0 3478 turn.example.net.
_turns._tcp  IN SRV  5 0 5349 turn.example.net.

Extra info: as a result of these SRV delegation records, hosting XMPP on a server other than the one at example.net is an option (i.e. if splitting services on one domain across servers). Further info can be found at XEP-0368.

Step 2: Open the firewall ports

Open ports:

TCP: 80 443 5222 5223 5269 5270 3478 5349 49152-65535

UDP: 3478 49152-65535

80 & 443 are for the web server, 5222, 5223, 5269 and 5270 are for XMPP, and the rest are for STUN/TURN.

Step 3: Set up the web server and get your SSL certificates

  • get (an) SSL certificate(s) for your domain, as well as several subdomains, all in all:
    • example.net
    • muc.example.net
    • upload.example.net
    • pubsub.example.net
    • proxy.example.net
    • turn.example.net
  • proxypass http://127.0.0.1:5443 through to:
    • https://example.net/xmpp
    • https://example.net/.well-known/host-meta
    • https://example.net/.well-known/host-meta.json
    Make sure that for /xmpp you have what is necessary to proxy websockets too. If you’re using Nginx, increase client_max_body_size for HTTP uploads.
  • make sure the certificate files are readable and/or in a place that is readable by the ejabberd user.

To avoid using something like Nginx + Certbot, use the built-in Ejabberd acme module, but this article assumes it is desired to host other web services on the same system, in which case each HTTP service would be reverse proxied to a single HTTPS web service.

Step 4: Install Ejabberd

Finally install the system package. Ensure the build has PostgreSQL support.

Confirm the file /etc/ejabberd/ejabberd.yml exists, and is readable by the user that runs Ejabberd (almost definitely ejabberd), by, if necessary, copying over the example ejabberd.yml or wget/curling it from the github repo. If it is obtained from the repo, make sure the version corresponds to the version of Ejabberd packaged by your OS.

Step 5: Set up PostgreSQL database

A separate database should be created for each virtualhost, as this makes things clearer, and in addition, easier to migrate individual virtualhosts in the future. However, the ability to use only one, as described here, now exists as well.

Follow standard PostgreSQL installation instructions for your OS. Once this has been done, connect to the database as an admin and:

  1. create an Ejabberd database user with CREATE USER ejabberd WITH PASSWORD 'your_password';. Don’t forget to change the password, and note it down.
  2. create a database for the virtualhost with CREATE DATABASE ejabberd_example OWNER ejabberd;. Replace example with something corresponding to the virtualhost.
  3. quit the psql shell, and import the database schema from GitHub with the command curl -s https://raw.githubusercontent.com/processone/ejabberd/master/sql/pg.sql | sudo -u ejabberd psql ejabberd_example (once again replace example).

Step 6: Ejabberd configuration

Begin by replacing localhost under hosts with the virtualhost (e.g. example.net), then list the certfiles previously obtained under certfiles.

hosts:
  - example.net

certfiles:
  - "/etc/ejabberd/certs/*/*"

Now set default_db: sql at the root level of the YAML file. This should be followed by host_config and the database configuration for your virtualhost, as shown below. Customise each value to according to the setup.

host_config:
  example.net:
    sql_type: pgsql
    sql_server: "localhost"
    sql_port: 5432
    sql_username: "ejabberd"
    sql_password: "postgres_password"
    sql_database: "ejabberd_example"
    auth_method: sql
    auth_password_format: scram

Under listen, ensure all the correct services are enabled on each port, including s2s TLS on port 5270 (not default):

listen:
  -
    port: 5222
    module: ejabberd_c2s
    max_stanza_size: 262144
    shaper: c2s_shaper
    access: c2s
    starttls_required: true
  -
    port: 5223
    tls: true
    module: ejabberd_c2s
    max_stanza_size: 262144
    shaper: c2s_shaper
    access: c2s
    starttls_required: true
  -
    port: 5269
    module: ejabberd_s2s_in
    max_stanza_size: 524288
  -
    port: 5270
    tls: true
    module: ejabberd_s2s_in
    max_stanza_size: 524288

Next, enable the HTTP server and the STUN/TURN server modules. Set turn_ipv4_address and ip to the server’s IPv4 address. TLS will be off for the HTTP server as it is reverse proxied through the previously set-up web server.

  -
    port: 5443
    module: ejabberd_http
    request_handlers:
      /xmpp/admin: ejabberd_web_admin
      /xmpp/bosh: mod_bosh
      /xmpp/upload: mod_http_upload
      /xmpp/ws: ejabberd_http_ws
      /.well-known/host-meta: mod_host_meta
      /.well-known/host-meta.json: mod_host_meta
  -
    port: 3478
    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: 0.0.0.0
  -
    port: 5349
    transport: tcp
    module: ejabberd_stun
    use_turn: true
    tls: true
    turn_min_port: 49152
    turn_max_port: 65535
    ip: 0.0.0.0
    turn_ipv4_address: 0.0.0.0

Set s2s_use_starttls: required at the root.

At this point it is possible to set up some ACLs. acls are just the access control lists, set up access_rules corresponding to your needs, which will be what are passed to module settings. At the minimum an admin user is recommended. Example:

acl:
  admin:
    user: juliet@example.net
  capulet:
    - user: juliet@example.net
	- user: tybalt@example.net
  nurse:
    - user: angelica@example.net

access_rules:
  household: 
    allow: capulet
    allow: nurse

Modules

Add abuse addresses under mod_disco. It is also possible to add other contact addresses according to XEP-0157:

modules:
# ...
  mod_disco:
    server_info:
    -
      modules: all
      name: "abuse-addresses"
      urls: ["mailto:abuse@example.net"]

Add mod_host_meta:

  mod_host_meta:
    bosh_service_url: "https://@HOST@/xmpp/bosh"
    websocket_url: "wss://@HOST@/xmpp/ws"

Edit mod_mam, and change assume_mam_usage to false and default to never if it is not desireable to default to archiving messages on the server:

  mod_mam:
    db_type: sql
    assume_mam_usage: never
    default: never

Add mod_stun_disco to advertise the STUN service to clients, changing 0.0.0.0 and example.net to the server’s IP and hostname respectively:

  mod_stun_disco: 
    credentials_lifetime: 12h
    services:
        -
          host: 0.0.0.0
          port: 3478
          type: stun
          transport: udp
          restricted: false
        -
          host: 0.0.0.0
          port: 3478
          type: turn
          transport: udp
          restricted: true
        -
          host: turn.example.net
          port: 5349
          type: stuns
          transport: tcp
          restricted: false
        -
          host: turn.example.net
          port: 5349
          type: turns
          transport: tcp
          restricted: true

MUCs:

Set the host to the muc subdomain, otherwise it will attempt to use conference.example.net. Setting mam: false in default_room_options will disable server-side message archiving by default.

  mod_muc:
    host: muc.example.net
    access:
      - allow
    access_admin:
      - allow: admin
    access_create: muc_create
    access_persistent: muc_create
    access_mam:
      - allow
    default_room_options:
      mam: false

File proxy:

  mod_proxy65:
    access: local
    max_connections: 5

HTTP file upload:

  mod_http_upload:
    put_url: https://@HOST@/xmpp/upload
    docroot: /var/www/ejabberdupload
    max_size: 1073741824
    custom_headers:
      "Access-Control-Allow-Origin": "https://@HOST@"
      "Access-Control-Allow-Methods": "GET,HEAD,PUT,OPTIONS"
      "Access-Control-Allow-Headers": "Content-Type"

Create the folder for the docroot, and ensure it is owned by the ejabberd user. Change max_size (the max upload size) to whatever is preferred.

PubSub:

  mod_pubsub:
    access_createnode: pubsub_createnode
    plugins:
      - flat
      - pep
    force_node_config:
      ## Avoid buggy clients to make their bookmarks public
      storage:bookmarks:
        access_model: whitelist

Step 7: Start server and create admin user

Start the Ejabberd server!

Use sudo -u ejabberd ejabberdctl register admin example.net password to register admin@example.net with the password password.

There is a compliance tester at compliance.conversations.im to test the server. After everything has been set up correctly, optionally change the loglevel at the root of the config.

There will be an admin page accessible at https://example.net/xmpp/admin.

Extra goodies!

Web client

It is possible to set up conversejs using mod_conversejs. It may be required to update the web server config to proxy the new endpoint (/chat below).

listen:
  -
    port: 5443
    module: ejabberd_http
    request_handlers:
      /xmpp/bosh: mod_bosh
      /xmpp/ws: ejabberd_http_ws
      /chat: mod_conversejs

modules:
  mod_conversejs:
    websocket_url: "ws://@HOST@/xmpp/ws"
    bosh_service_url: "https://@HOST@/xmpp/bosh"

Further virtualhosts

For each further virtualhost a new database should be created, and added to the database part of the config. e.g.:

host_config:
  example.net:
    sql_type: pgsql
    sql_server: "localhost"
    sql_port: 5432
    sql_username: "ejabberd"
    sql_password: "postgres_password"
    sql_database: "ejabberd_net"
    auth_method: sql
    auth_password_format: scram
  example.org:
    sql_type: pgsql
    sql_server: "localhost"
    sql_port: 5432
    sql_username: "ejabberd"
    sql_password: "postgres_password"
    sql_database: "ejabberd_org"
    auth_method: sql
    auth_password_format: scram

There cannot be conflicts between declarations in the config file, so if mod_muc, mod_proxy65, mod_http_upload and mod_pubsub are declared under modules at the root, they (as well as other configuration differences between virtualhosts) must be deleted and replicated for each virtualhost under append_host_config, at the root. Example as so:

append_host_config:
  example.org:
    modules:
      mod_muc:
        host: muc.example.org
        access_create: org_users
        access_persistent: org_users
        access:
          - allow
        access_admin:
          - allow: admin
        default_room_options:
          mam: false
      mod_proxy65:
        access: org_users
        max_connections: 5
      mod_http_upload:
        access: org_users
        put_url: https://@HOST@/xmpp/upload
        docroot: /var/www/ejabberdupload
        max_size: 1073741824
        custom_headers:
          "Access-Control-Allow-Origin": "https://@HOST@"
          "Access-Control-Allow-Methods": "GET,HEAD,PUT,OPTIONS"
          "Access-Control-Allow-Headers": "Content-Type"
      mod_pubsub:
        access_createnode: org_users
        plugins:
          - flat
          - pep
        force_node_config:
          ## Avoid buggy clients to make their bookmarks public
          storage:bookmarks:
            access_model: whitelist
  example.net:
    modules:
      mod_muc:
        hosts: 
          - muc.example.net
        access_create: net_users
        access_persistent: net_users
        access:
          - allow
        access_admin:
          - allow: admin
        default_room_options:
          mam: false
      mod_proxy65:
        access: net_users
        max_connections: 5
      mod_http_upload:
        access: net_users
        put_url: https://@HOST@/xmpp/upload
        docroot: /var/www/ejabberdupload
        max_size: 1073741824
        custom_headers:
          "Access-Control-Allow-Origin": "https://@HOST@"
          "Access-Control-Allow-Methods": "GET,HEAD,PUT,OPTIONS"
          "Access-Control-Allow-Headers": "Content-Type"
      mod_pubsub:
        access_createnode: net_users
        plugins:
          - flat
          - pep
        force_node_config:
          ## Avoid buggy clients to make their bookmarks public
          storage:bookmarks:
            access_model: whitelist

As above, it is possible to disable access to certain services per virtualhost using ACLs, in order to e.g. prevent users on example.net from creating MUCs on muc.example.org.

Separate TURN server (Coturn)

In this case, change mod_stun_disco to this, and don’t enable the listen opts for STUN/TURN. Generate an auth secret and share it with the TURN server instance.

  mod_stun_disco:
    secret: "auth_secret"
    services:
      -
        host: turn.example.net
        type: stun
      -
        host: turn.example.net
        type: turn

  1. Virtualhosts allow more than one XMPP service to be run on one server. For example, one XMPP service with the domain example.net and another XMPP service with the domain example.org, both somewhat separated from each other as if they were run on different servers.↩︎