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.
Why XMPP/Jabber?
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.
So how do we do it?
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...
DNS
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.
Docker
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!
Config files passed through
--- 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.

What's next?
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
Additional reading.
https://gsvd.dev/blog/my-prosody-setup-with-docker-compose


