Self-hosting chronicle: Discord replacement (XMPP / Galène)
Many social circles have been scrambling to figure out an alternative to Discord following their recent assertion that their users' privacy is equivocable. Mine have been no exception, and so I've been tinkering with getting a replacement set up.
Text chat: XMPP
Among many options we found, we weighed Stoat, IRC, and Matrix. We quickly settled on XMPP.
-
vs.Stoat: We wanted something well-documented. It was hard to tell if Stoat is based on standard protocols or if it's entirely bespoke.
-
vs. IRC: We all love IRC, but wanted something with a bit more bells and whistles.
-
vs. Matrix: Too many complaints about difficulty with setup. From reading HN conversations, it also seems Matrix is amidst major changes in history resolution and A/V integrations, while also struggling with funding. I suggested we go with something tried-and-true, and watch for more signs of Matrix's maturity.
In XMPP's favor specifically, we counted:
- The existing groupchat standard is well-supported. There are also rumblings from the 2025 XMPP Summit that a new-and-improved version is in the works.
- Federation gives us a lot of flexibility: we can start with a single-server and later disperse our communities once we're more comfortable, with relative ease. If we later make friends on other servers, it also makes it easier for them to join us in our chats.
- A lot of us are also programmers, and XMPP's extensibility model was aesthetically compelling to us. As long as we're adopting a technology, it'd be nice to feel it's inclined to contributions.
Setting up Snikket
It was fortunately presumed that I would be the one to implement the details of our plan, so my self-appointment has (so far) gone without comment.
I settled on Snikket. Their stated goal is to be beginner-friendly with sensible defaults, and it has a messaging app whose UX is comparable to Signal. My goal was to spend a few nights of fiddling and onboard a few friends by the weekend. This Snikket made quite easy with their quick start guide.
I want to have a single reverse-proxy running on my machine, so I deviated a bit from the quickstart guide. Here's what I did:
VPS provisioning
I bought a VPS running AlmaLinux 9 from ColoCrossing. I'm not sure I'd recommend them: my order was approved during their business hours, but it took nearly 24hr after ordering to finally have my server provisioned correctly.
I followed the Linode guides for server setup and fail2ban. With a little websearching, I also learned from the firewalld docs how to create a firewalld service to allow XMPP through.
Then, I configured Angie as my reverse proxy, setting it to listen for all hosts *.<domain>. I also want to use Angie's built-in ACME plugin for certificate renewals. Angie's docs are pretty explanatory for this: a few lines of config later, and we were set up for DNS challenges from LetsEncrypt.
DNS
I already own a domain with Porkbun, so I configured some DNS records:
- For Snikket, I configured an
Arecord for*.<domain>pointing at my VPS, thenCNAMErecords pointinggroups.xmpp.<domain>andshare.xmpp.<domain>atxmpp.<domain>. - Angie's ACME plugin comes with a DNS server, so I configured DNS delegation using
NSrecords for_acme-challenge.groups.xmpp.<domain>,_acme-challenge.share.xmpp.<domain>,_acme-challenge.xmpp.<domain>, and_acme-challenge.<domain>, all pointing toacme-challenge.<domain>.
Snikket, for real
With the preamble done, I followed the rest of the Snikket quick-start, swapping podman in place of docker.
At this point, I had an XMPP account and was able to log into my https://xmpp.<domain> web admin portal. I invited a friend, and we were able to message each other via the Snikket Android app.
Final notes
Future work
My secondary goal is to have an XMPP server that lets me get my hands dirty with tweaks and customization. Fortunately, Snikket is a wrapper around Prosody and done in a way that it's easy to map in additional /snikket/prosody/*.conf.lua files.
Circles
Snikket's "circles" feature leave a bit to be desired. I guess their main goal is to populate the contact list for newly-invited users. They do that fine, but the groupchat functionality is annoying.
First, the upside: if you create a groupchat via a circle, all circle members will be auto-invited to it, including users who are newly added to the circle.
Unfortunately, the circle and groupchat invitations happen as soon as a new user creates their account -- before they have a chance to log into an XMPP client. That sequencing means there's a 4 year-old issue where circle chats break when a new user joins the server, until they log into an XMPP client.
Jingle
When we initially chose XMPP, we thought the Jingle spec and Jitsi Meet implied a very tightly integrated A/V group call feature, like Discord. This doesn't seem to be the case: in fact, it appears Snikket/XMPP doesn't really support the concept of Discord-like persistent A/V rooms.
We're very interested in adding functionality along these lines to a client, and perhaps trying to integrate it with Jingle.
XMPP Clients
The XMPP website has a helpful page that illustrates how tough it is to pick a "good client." On mobile, the Snikket apps are the only free apps with acceptable UI. On desktop, we've picked Converse.js which has comparatively excellent support for XMPP standards. Moreover, it has all of its internals bundled nicely in a library which could be such a sweet starting point for a custom UI.
A/V chat: Galène
As mentioned above, it's not clear to us how to get a Discord-like experience for A/V hangouts. Our ideal requirements would be:
- Persistent A/V "rooms" that support drop-in participation
- Good client support across desktop and mobile
- Screen sharing
- Low latency and low resource usage
- Good scaling up to ~10 participants with A/V enabled
We chose Galene because it fit most of these, had good documentation (both for operators and developers), and was easy to set up following the repo instructions.
We're making three big equivocations:
- Galene has persistent rooms, but they're not integrated with anything; to allow flexibility, we've enabled automatic subgroups
- It supports LDAP integration, but I'm not hosting a publicly accessible IdP, so there's no source-of-truth for identity: we're just using user-set nicknames
- Because we're using user-set nicknames, there's a single common password
If you've seen the Galene UI (their homepage has a demo), then this is probably well-illustrated by the welcome text currently at video@xmpp.<domain>:
**Joining:**
* The default room is ~
* You can join/create secret subrooms by typing ~/subroomNameHere
**Logins:**
* username: anything you want
* password is always >
**Inviting:**
* Feel free to share the PW with anyone I've played games with; I'm eventually going to extend my home instance of https://kanidm.com/
* If you want to avoid sharing the PW with someone, you can type /invite in the room chat to get a 24h invite link
Not done yet
We did it! In a week, we found our escape hatch from Discord. We're going to play with it and learn a lot in the next couple of weeks, and probably have a really messy breakup with Discord as people negotiate their own transitions.
Still, this setup is a very obvious stopgap. We've only met most of our critical requirements, and barely. But it puts us in the driver's seat to make a lot of improvements. In 2026, perhaps the most valuable quality of a decision is that it puts you in control of your destiny.