With Discord in talks with
Palantir,
it's time to move on from having my chats controlled by a platform that's only
going to continue to go down hill. We've been through many a service through the
years, from things like AIM, MSN Messanger, Gtalk, and
mumble, to Skype, eventually Discord, and probably
other platforms that I've forgotten about. I'm well versed in self hosting at
this point, and am done with changing platforms. It's time for self hosting a
chat service.
While XMPP is a very old spec in terms of when it came out, it's been wildly
used since by many people, and kept alive with new specs. It's also common to
run an ICE/STUN/TURN server next to it for voice and video. It's allows for
federation, but doesn't require it (Lookin at you Matrix), it's relatively
easy to self host, with limited resources needed, and only a bit of docker
knowledge to make it easy.
While I'm running my own server that I'll mention more about at the end, I
encourage anyone to self host if you can. That takes control back of your data,
and lets you have control of reliability, ect. While my way isn't the only way,
this is how I did it...
This has been the absolute worst part for me, and I'm sure there's some sort
of issue here, but it's working well for me currently. You'll want a domain
name, not a subdomain for hosting the core XMPP service, or you'll have
additional steps and headaches with DNS, but if you know better than I, you
don't need me to tell you this. Here's what I have for DNS.

I'm not going to go over this as I won't at all claim to be an expert, but this
is what I've found works great, and seems to follow the documentation for
several IRC servers.
services:
prosody:
image: prosodyim/prosody:13.0
container_name: xmpp
restart: unless-stopped
pull_policy: always
ports:
- "5222:5222" # C2S STARTLS
- "5269:5269" # S2S STARTTLS
- "5280:5280" # HTTP (goes to CADDY)
- "5223:5223" # legacy
- "5000:5000" # file transfer proxy
volumes:
- data:/var/lib/prosody
- modules:/ets/prosody/modules
- ./prosody.cfg.lua:/etc/prosody/prosody.cfg.lua:ro
- certs:/etc/prosody/certs:ro
coturn:
image: coturn/coturn:4.9.0
container_name: coturn
restart: unless-stopped
network_mode: host
volumes:
- ./turnserver.conf:/etc/coturn/turnserver.conf:ro
tmpfs:
- /var/lib/coturn
volumes:
certs: # TLS certs
data: # most server data
modules: # prosody modules. Can be omitted if you want to use a stock setup
If you know what a compose file looks like, this all should be self-explanitory.
We run both a prosody server (xmpp/jabber), and a coturn server in tandum to
offer voice/video/screenshare to xmpp clients, and the core spec doesn't allow
for that. Coturn give us that for free!
--- procody.cfg.lua
modules_enabled = {
-- Core
"roster"; -- Manage and store client rosters
"saslauth"; -- Authentication using SASL
"tls"; -- Support for SSL/TLS encryption
"dialback"; -- Dialback support for server-to-server identity verification
"disco"; -- Service Discovery support
"ping"; -- XMPP Ping reply support
"register"; -- No clue
"time"; -- Reply to “What time is it?” requests
"uptime"; -- Reply to uptime requests
"version"; -- Reply to software version requests
-- Security
"blocklist";
-- Multi-device & mobile
"carbons"; "csi_simple";
"smacks"; -- Stream Management (reliable delivery)
"cloud_notify"; -- Push notifications for mobile
-- Message archive
"mam";
-- User profiles & presence
"vcard_legacy"; "pep"; "bookmarks";
-- Admin
"admin_shell";
"admin_adhoc"; -- Allows administration via an XMPP client that supports ad-hoc commands
-- Coturn
"external_services";
"turn_external";
-- Contact info
"server_info";
"server_contact_info";
-- Compliance
"bosh"; -- Enable mod_bosh
"websocket"; -- Enable mod_websocket
"pubsub_serverinfo";
"seclabels";
}
-- -----------------------------------------------------------------
-- Connection-level rate limiting
-- -----------------------------------------------------------------
limits = {
c2s = {
rate = "10kb/s";
};
s2sin = {
rate = "30kb/s";
};
}
--
-- -----------------------------------------------------------------
-- muc_mam - Message archiving for Multi-User Chat - XEP-0313
-- -----------------------------------------------------------------
muc_log_by_default = false
muc_log_presences = false
log_all_rooms = false
muc_log_expires_after = "2w"
c2s_require_encryption = true
s2s_require_encryption = true
s2s_secure_auth = true
authentication = "internal_hashed"
allow_registration = false
archive_expires_after = "1y"
default_archive_policy = true
http_interfaces = { "*" }
http_ports = { 5280 }
https_ports = { } -- reverse proxied through caddy on port 80
------------------------------------------------------------------------
VirtualHost "kittie.gay"
turn_external_host = "kittie.gay"
turn_external_port = 3478
turn_external_secret = "TURN SECRET GOES HERE"
admins = { "katelin@kittie.gay" }
disco_items = {
{"kittie.gay", "primary servers"};
{"proxy.kittie.gay", "proxy service for clients that need it"};
}
-- -----------------------------------------------------------------
-- Contact info - XEP-0157
-- -----------------------------------------------------------------
server_info = {
abuse = { "mailto:abuse@kittie.gay", "xmpp:admin@kittie.gay" };
admin = { "mailto:admin@kittie.gay", "xmpp:admin@kittie.gay" };
security = { "xmpp:katelin@kittie.gay" };
support = { "xmpp:support@muc.kittie.gay?join" };
}
-- -----------------------------------------------------------------
--- Set up a MUC (multi-user chat) room server
-- -----------------------------------------------------------------
Component "conference.kittie.gay" "muc"
modules_enabled = { "muc_mam"; "mam"; }
restrict_room_creation = "local"
Component "upload.kittie.gay" "http_file_share"
http_file_share_size_limit = 10485760 -- 10 MB
http_file_share_expires_after = 2592000 -- 30 days
http_host = "kittie.gay"
http_external_url = "https://kittie.gay"
modules_disabled = {
"s2s";
}
Component "proxy.kittie.gay" "proxy65"
proxy65_address = "xmpp.kittie.gay"
modules_disabled = {
"s2s";
}
------------------------------------------------------------------------
Include "conf.d/*.cfg.lua"
#turnserver.conf
listening-port=3478
tls-listening-port=5349
min-port=49152
max-port=49200
relay-threads=2
realm=kittie.gay
use-auth-secret
static-auth-secret=TURN_SECRET_GOES_HERE
no-multicast-peers
no-cli
no-tlsv1
no-tlsv1_1
denied-peer-ip=10.0.0.0-10.255.255.255
denied-peer-ip=172.16.0.0-172.31.255.255
denied-peer-ip=192.168.0.0-192.168.255.255
log-file=stdout
With these files, the only thing missing is certs. I'll leave that one up to
you, as everyone does theirs differently, and if you're running something on
port 80/443, it can be a bit different. Check out some of the additional reading
links for how others have done this.
This server with the configs above, covers 95% of compliance.

Well, host your own, or let me know if you want an account! I'd gladly make
anyone on, as I have registrations disabled for abuse reasons, but I want people
to come over and say hi, federated or otherwise. I'm always around at help [at]
kittie.gay, obviously with a symbol for @, but bot spam is crazy these days. You
can also find me on XMPP at katelin [at] kittie.gay as well as the other usual suspects.
I'll have a follow up blog post recommending different clients for different
platforms. I want to do a little more testing before I make that post though!
Happy chatting <3
https://gsvd.dev/blog/my-prosody-setup-with-docker-compose
https://blog.dmcc.io/journal/xmpp-turn-stun-coturn-prosody/
https://compliance.conversations.im
https://github.com/jjj333-p/configs/blob/main/prosody.cfg.lua
https://github.com/monal-im/Monal/wiki/Considerations-for-XMPP-server-admins