Safeguard
Supply Chain Security•

XMPP Explained: RFC 6120, TLS, SASL, OMEMO and Secure Self-Hosting

XMPP is the IETF's federated chat protocol. How streams, TLS, SASL SCRAM, dialback and OMEMO work, what servers still see, and how to self-host safely.

Safeguard Security Team
41 min read

XMPP (the Extensible Messaging and Presence Protocol) is an open, federated protocol for chat and presence: anyone can run a server, and users on different servers talk to each other the way email users do. The core is IETF standard RFC 6120, transport security is TLS with SASL login (SCRAM by preference), and end to end encryption is an add-on called OMEMO (XEP-0384). To self-host it securely, require TLS on every connection, turn off plaintext and open registration, rate limit, set retention, keep the server patched, and switch OMEMO on in clients rather than assuming it is on.

XMPP started life as Jabber in January 1999. Its core has been through two rounds of IETF review and several mature open source servers exist, but much of its security depends on choices in a server config file and on which extensions each client implements. This guide goes layer by layer, from the XML stream up to OMEMO, then walks through hardening a self-hosted server.

If you are weighing XMPP against Matrix, read this alongside our self-hosted Matrix homeserver guide. For the cryptography behind OMEMO and its relatives, our end to end encrypted messaging explainer covers the Double Ratchet and MLS in more depth than we repeat here.

Key takeaways

  • XMPP is defined by the IETF in RFC 6120 (core), RFC 6121 (instant messaging and presence) and RFC 7622 (address format). Everything else, including group chat, message archives, file upload and OMEMO, is an XMPP Extension Protocol (XEP) published by the XMPP Standards Foundation (XSF).
  • Every connection is a long-lived XML stream. Three stanza types carry all traffic: message, presence and iq.
  • Transport security is TLS (STARTTLS on ports 5222 and 5269, or direct TLS per XEP-0368), then SASL authentication. RFC 6120 makes SCRAM-SHA-1 and SCRAM-SHA-1-PLUS the mandatory-to-implement password mechanisms; modern servers add SCRAM-SHA-256 and TLS channel binding.
  • Servers authenticate each other with certificates (SASL EXTERNAL) or with Server Dialback (XEP-0220), which the XSF itself calls weak identity verification when DNSSEC is not used.
  • OMEMO gives end to end encryption with X3DH and the Double Ratchet, per device. It does not hide metadata: servers still see who talks to whom, when, from how many devices, and your contact list.
  • XEP-0384 is still Experimental (version 0.9.1, 6 April 2026), and many clients still speak an older OMEMO namespace. Check that your clients interoperate before relying on it.
  • Most real-world XMPP abuse comes from open registration on federated servers. Invite-only registration, rate limits and server-side blocking are the main controls.

What XMPP is and where it came from

XMPP is a protocol for exchanging small pieces of structured XML in near real time between network endpoints. Its first and best known use is instant messaging and presence (whether a contact is online), but the same plumbing has carried publish-subscribe feeds, device control, game chat and voice and video signalling.

According to the XSF's own history page, Jeremie Miller announced Jabber in January 1999 as an open technology for instant messaging and presence, and an open source server called jabberd, several clients and libraries followed that year. In August 1999 the Jabber community pledged support for the IETF standards process. The IETF formed an XMPP working group, and in October 2004 it published RFC 3920 and RFC 3921 as Proposed Standards. In March 2011 those were replaced by RFC 6120 (core) and RFC 6121 (instant messaging and presence). The address format, first split out as RFC 6122, was updated again in September 2015 as RFC 7622 to use the PRECIS framework for internationalized strings. RFC 7590, also from June 2015, tightened the TLS rules.

Timeline

DateEventSource
January 1999Jeremie Miller announces Jabber; jabberd open source server followsxmpp.org history
August 1999Jabber community pledges support for the IETF processxmpp.org history
October 2004RFC 3920 (core) and RFC 3921 (IM and presence) publishedRFC 3920, xmpp.org history
March 2011RFC 6120 and RFC 6121 obsolete 3920 and 3921RFC 6120, RFC 6121
March 2011RFC 6122 address format (stringprep based)RFC 6122
June 2015RFC 7590: stronger TLS recommendations for XMPPRFC 7590
September 2015RFC 7622 obsoletes RFC 6122 (PRECIS based JIDs)RFC 7622
October 2015First OMEMO draft (XEP-0384 version 0.0.1)XEP-0384 revision history
December 2016XEP-0384 version 0.1 accepted by the XMPP CouncilXEP-0384 revision history
2019XEP-0368 direct TLS SRV records reaches version 1.1.0, StableXEP-0368
March 2025Prosody 13.0.0 adds RFC 9266 tls-exporter channel binding and full DANE for s2sProsody release notes
April 2026XEP-0384 version 0.9.1, still ExperimentalXEP-0384

Who maintains XMPP today

The IETF owns the core RFCs. Everything built on top is published by the XMPP Standards Foundation as numbered XEPs. Per XEP-0001 and the XSF standards process page, anyone can submit a ProtoXEP; the XMPP Council, a technical committee elected by XSF members, decides whether to accept it. Accepted documents move through Experimental, Proposed, Stable and Final, or end up Deferred, Rejected, Deprecated, Obsolete or Retracted.

Those labels matter. For an Experimental XEP, the XSF advises production systems to consider carefully before deploying, and OMEMO (XEP-0384) is still Experimental. Message Archive Management (XEP-0313), Stream Management (XEP-0198) and Multi-User Chat (XEP-0045) are Stable; In-Band Registration (XEP-0077) is Final.

How streams, stanzas and JIDs work

Addresses: JIDs

Every XMPP entity has a Jabber ID, or JID. RFC 7622 defines it as up to three parts:

localpart@domainpart/resourcepart
juliet@example.com/balcony
  • The domainpart is the server, for example example.com. It is the only required part, and it is how federation finds the right server.
  • The localpart is the account, like the user in an email address. RFC 7622 limits each part to 1,023 bytes and maps the localpart through the PRECIS UsernameCaseMapped profile, so different Unicode spellings of the same name are compared consistently.
  • The resourcepart names a specific connected session, typically one per device or client. A "bare JID" (juliet@example.com) is the account; a "full JID" (juliet@example.com/balcony) is one session of it.

RFC 7622 has a security section on address spoofing for a reason: lookalike Unicode characters in a domain or localpart can fool people. Clients should display JIDs carefully, and admins should think about which characters they allow in new account names.

Streams

An XMPP session is two XML streams, one in each direction, over one TCP connection. The client opens with a stream:stream element, the server answers with its own opening tag and a list of stream features (for example STARTTLS and the SASL mechanisms on offer). The stream stays open for as long as the session lives. Everything that follows is a child element of that root.

Because the stream is one huge XML document, RFC 6120 restricts the XML you may send: comments, processing instructions, DTD subsets and entity references are banned, and a server that sees them closes the stream with a restricted-xml error. Unrestricted XML parsing is a well known source of entity expansion and denial of service bugs, so strict enforcement matters.

Stanzas

RFC 6120 defines exactly three kinds of stanza:

StanzaWhat it doesExample use
messagePush a payload to an entity, fire and forgetChat message, group chat line, notification
presenceBroadcast availability, and manage subscriptionsOnline, away, "may I see your status?"
iq (info/query)Request and response, with type get, set, result or errorFetch roster, upload slot request, discovery

RFC 6121 builds instant messaging on top: the roster (contact list stored on the server), presence subscriptions (a contact must approve before you see their status), message types such as chat and groupchat, and the server's routing rules for bare versus full JIDs.

Extensions add new child elements in their own XML namespace inside these three stanza types. An OMEMO encrypted message is still a message stanza; the ciphertext sits in an encrypted element in the urn:xmpp:omemo:2 namespace. This extensibility is why XMPP has lasted 27 years, and why two clients can both "support XMPP" yet disagree on encryption or file transfer.

How connections authenticate

There are two kinds of connection: client to server (c2s) and server to server (s2s). Both follow the same pattern: find the server, set up TLS, then authenticate.

Step 1: find the server with DNS

RFC 6120 resolves a domain through DNS SRV records. A client connecting for example.net looks up _xmpp-client._tcp.example.net; a server opening a federation link looks up _xmpp-server._tcp.example.net. If no SRV record exists, the fallback is a plain A or AAAA lookup on the IANA registered ports: 5222 for clients, 5269 for servers.

XEP-0368 (Stable, version 1.1.0) adds _xmpps-client._tcp and _xmpps-server._tcp records for direct TLS, where TLS starts immediately instead of being upgraded with STARTTLS. Its rules: STARTTLS must not be used inside a direct TLS connection, SNI must be set to the JID's domain, and if ALPN is used the protocol names are xmpp-client and xmpp-server. The spec notes two practical gains: direct TLS saves round trips, and on port 443 without ALPN it is indistinguishable from HTTPS, which helps clients behind restrictive firewalls.

_xmpp-client._tcp.example.com.  3600 IN SRV 0 5 5222 xmpp.example.com.
_xmpps-client._tcp.example.com. 3600 IN SRV 0 5 5223 xmpp.example.com.
_xmpp-server._tcp.example.com.  3600 IN SRV 0 5 5269 xmpp.example.com.

XEP-0156 also lets web clients discover BOSH and WebSocket endpoints over HTTPS, which is how browser based clients connect without raw TCP.

Step 2: TLS

With STARTTLS, the receiving server lists starttls in its stream features, the client asks to upgrade, both sides run the TLS handshake on the same TCP connection, and then restart the XML stream from scratch. RFC 6120 says no further XML may be sent until TLS completes, and old stream state is discarded, which closes the door on injecting data before the upgrade.

The weak point of STARTTLS is the moment before TLS starts. An attacker in the path can strip the starttls feature from the server's advertisement. The defence is policy: clients and servers that refuse to continue without TLS. That is what RFC 7590 asks for. Published in June 2015, it applies the general TLS best practices of BCP 195 to XMPP, covering protocol versions, fallback, TLS layer compression, session resumption, cipher suites, key lengths and forward secrecy, and it requires that implementations support and prefer the strongest options. Since RFC 7590 was written, BCP 195 itself has been updated: RFC 9325 replaced RFC 7525 in November 2022, and TLS 1.3 (RFC 8446) is now the baseline most servers should prefer.

Certificate checking matters as much as encryption. RFC 6120 originally pointed to RFC 6125 for how to match a certificate against the service name; RFC 9525 (November 2023) obsoletes RFC 6125, and Prosody's 13.0.0 release notes say it no longer checks certificate Common Names, in line with RFC 9525. In practice your certificate needs subject alternative names covering the XMPP domain itself (example.com), not only the host the SRV record points to, plus any component domains such as conference.example.com and upload.example.com. Prosody's certificate documentation is blunt about this: the certificate is for the domain users have in their JIDs.

Step 3: SASL login

After TLS, the client authenticates with SASL. The server lists mechanisms, for example:

<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
  <mechanism>SCRAM-SHA-1-PLUS</mechanism>
  <mechanism>SCRAM-SHA-1</mechanism>
  <mechanism>PLAIN</mechanism>
</mechanisms>

What each mechanism means for security:

MechanismHow the password is usedRisk if TLS is broken or strippedNotes
PLAINSent as-is, base64 encodedPassword exposedAcceptable only inside verified TLS
DIGEST-MD5Challenge-responseWeak, deprecatedRFC 6120 replaced it as mandatory-to-implement with SCRAM
SCRAM-SHA-1 / SCRAM-SHA-256Salted challenge-response (RFC 5802, RFC 7677)Password not sent; server stores salted verifiersMutual proof: the server proves it knows the verifier too
SCRAM-...-PLUSSCRAM plus TLS channel bindingDetects a man in the middle terminating TLSNeeds tls-unique (TLS 1.2) or tls-exporter (TLS 1.3, RFC 9266)
EXTERNALIdentity from the TLS client certificateNo password at allCommon for s2s; see XEP-0178 for certificate rules

Channel binding is the piece people skip. With plain SCRAM, an attacker who can get a client to accept a bad certificate can relay the SCRAM exchange. With a -PLUS variant, the SCRAM proof is tied to the specific TLS connection, so a relayed exchange fails. RFC 6120 already says implementations should enforce channel binding even when TLS is used. RFC 9266 defines tls-exporter for TLS 1.3, because the older tls-unique binding does not exist in TLS 1.3. XEP-0440 (Stable since December 2025) lets a server advertise which channel binding types it supports, and Prosody 13.0.0 implements both tls-exporter and tls-server-end-point.

Two newer XEPs build on this: XEP-0388 (SASL2, Stable) streamlines login, and XEP-0474 (Experimental) protects the offered mechanism list so a client can detect an attacker who removed the strong options. Neither is universal yet.

On the server side, the way passwords are stored is the other half. SCRAM lets the server keep salted verifiers instead of plaintext passwords. Prosody's security page recommends hashed storage with mod_auth_internal_hashed and notes the trade-off: some legacy mechanisms need the plaintext password, so hashed storage limits which mechanisms work.

Step 4: server to server trust

Federation means your server accepts connections from servers you have never heard of. Each one claims to speak for a domain. There are two ways to check the claim.

Certificates. The remote server presents a certificate for its domain during TLS, and your server verifies it against trusted CAs, then authenticates the stream with SASL EXTERNAL. XEP-0178 gives the best practices. This is strong authentication, and with DANE (TLSA records in DNSSEC signed zones) it does not even depend on the public CA system. Prosody 13.0.0 lists full DANE support for s2s.

Server Dialback. XEP-0220 defines the older fallback. When server A connects to server B claiming to be a.example, B does not accept stanzas yet. It opens a separate connection back to the authoritative server for a.example, found through DNS, and asks it to confirm a key. If it confirms, B accepts the stream. The XSF's own abstract is candid: dialback uses DNS as the basis of identity, and without DNSSEC the result is weak identity verification. RFC 6120 noted in 2011 that most deployed servers still used dialback; today the better servers try certificates first and only fall back to dialback where allowed.

Prosody exposes this choice directly. s2s_require_encryption refuses unencrypted federation links (Prosody's documentation says there should be no need to allow them anymore). s2s_secure_auth = true requires valid certificates and never falls back to DNS based dialback. If a few domains you need to talk to have broken certificates, s2s_insecure_domains lets you make named exceptions rather than weakening the whole policy. s2s_secure_domains does the opposite: require certificate authentication for specific domains while the default stays relaxed.

Connection security at a glance

LayerStandardWhat it protectsWhat it does not
DNS SRVRFC 6120, XEP-0368Finding the right host and portSpoofed answers, unless DNSSEC
TLS (c2s, s2s)RFC 6120, RFC 7590Each hop between client and server and between serversContent on the servers themselves
SASL SCRAM -PLUSRFC 5802, RFC 7677, RFC 9266Password and TLS relay attacksA compromised server
s2s certificates or DANEXEP-0178, RFC 9525Server identity in federationWhat the remote server does with your data
DialbackXEP-0220Basic domain identityStrong identity without DNSSEC
OMEMOXEP-0384Message content end to end, per deviceMetadata, contact lists, presence

How OMEMO end to end encryption works

TLS protects each hop. A message from alice@a.example to bob@b.example crosses Alice's client, server A, server B and Bob's client. Both servers decrypt the TLS layer, see the stanza and could store it. XEP-0384 says this is exactly the gap it closes: because of XMPP's federated nature a message may pass more than one server, so users have an interest in protecting content from every intermediate host.

The building blocks

The current XEP-0384 (version 0.9.1, 6 April 2026) defines:

  • Devices. Each client instance is a device with its own numeric device ID and its own long term IdentityKey. An account with a phone and a laptop has two devices, and senders encrypt for each one.
  • Device list. Each account publishes its device IDs to a PEP node (Personal Eventing Protocol, a per-account publish-subscribe service from XEP-0163 and XEP-0060) at urn:xmpp:omemo:2:devices. Contacts subscribe, so they learn about new devices automatically.
  • Bundles. Each device publishes a bundle: its public IdentityKey, a signed PreKey with its signature, and a list of single use PreKeys. The spec says the bundle should hold around 100 PreKeys and must always hold at least 25, and that signed PreKeys should rotate between once a week and once a month.
  • X3DH. To start a session, the sender fetches a bundle and runs the X3DH key agreement (curve Curve25519/Ed25519, SHA-256, info string "OMEMO X3DH"). Every key exchange must use a single use PreKey; exchanges without one must be rejected.
  • Double Ratchet. After X3DH, messages use the Double Ratchet. XEP-0384 fixes the parameters: HKDF-SHA-256 for the root chain, HMAC-SHA-256 for chain keys, and for each message an authenticated encryption step of AES-256-CBC with HMAC-SHA-256, keyed from 80 bytes of HKDF output. It recommends keeping at most 1,000 skipped message keys per session, so a malicious sender cannot fill a device's storage.
  • Stanza Content Encryption. In the current namespace, the encrypted payload is an XEP-0420 envelope containing the elements meant only for the recipient, plus random padding, so more than just the message body can be protected.

What one encrypted message looks like

The sender generates a random key, encrypts the payload once with it, then encrypts that key separately for every recipient device and every one of its own other devices, each through that device's ratchet session. The stanza carries one ciphertext and many small key blobs, one per device, each tagged with the recipient device ID (rid). The kex flag marks a key blob that also carries a new X3DH key exchange.

That design explains what users see. A device that was offline can still decrypt a message later from the server archive, because its key blob was included at send time. A device added after a message was sent cannot decrypt that message at all.

Group chats

OMEMO works in Multi-User Chat rooms with conditions. XEP-0384 says a room that supports OMEMO must be non-anonymous (so participants can see each other's real JIDs and fetch device lists) and should be members-only. The sender fetches the member list, then each member's device list, then encrypts the message key for every device of every member. A large room means a large header and a lot of sessions, which is one reason big public XMPP rooms are rarely end to end encrypted.

Trust and verification

XEP-0384 states that trust management is out of scope, and that the quality of identity key verification determines the protection OMEMO offers. In practice, clients show each device's fingerprint and let you mark it verified, usually by scanning a QR code in person. Many clients use a "trust on first use" or "blind trust before verification" default: new devices are trusted until you verify one, after which unverified new devices are flagged. If you never verify, a malicious or compromised server could publish an extra device for a contact and your client might encrypt to it. That is the main active attack OMEMO cannot stop without user verification.

What servers still see

The spec is explicit: OMEMO does not protect against attackers who rely on metadata and traffic analysis. Here is what stays visible to your server, and to the remote server for cross-domain chats:

Visible to serversWhy
Sender and recipient JIDs, timestamps, message sizesNeeded for routing
Your roster (contact list) and subscription statesStored server side by RFC 6121
Presence: when you are online, status textBroadcast through the server
Device IDs and public bundlesPublished in PEP so others can encrypt
Group membership and room namesMUC is server side
Encrypted payloads in the archiveMAM stores them, encrypted, until expiry
File upload URLs, sizes and upload timesHTTP upload (XEP-0363) is a server component; clients that implement OMEMO media sharing (XEP-0454, Experimental) encrypt the file, but the server still stores it
Read receipts, typing notifications if not inside the envelopeDepends on client and on SCE support

Older OMEMO clients encrypt only the body element, leaving other extension elements in the clear. The XEP-0420 envelope in the current namespace fixes that, but only between clients that speak it.

The two OMEMO namespaces

This is the interoperability trap. XEP-0384's revision history shows that version 0.2 (2017) switched to the eu.siacs.conversations.axolotl namespace "currently used in the wild", and later versions moved to urn:xmpp:omemo:1 and then urn:xmpp:omemo:2 with a reworked wire format. Many deployed clients still implement the older axolotl based version, sometimes called OMEMO 0.3, and a growing number support the newer one, some both. Two clients that both advertise "OMEMO" may not be able to talk end to end. Test with the exact clients your users run, and check the XSF's software listing and each client's own documentation for which version they implement.

How federation affects spam, abuse and trust

Federation makes XMPP independent of any single company, and it is also why spam is the most common operational problem: a spammer needs only an account on any server that federates with yours.

The usual source is open registration. XEP-0077 In-Band Registration lets a client create an account over XMPP itself, with no web form. If a public server leaves it open with no rate limits, bots create accounts in bulk and send unsolicited subscription requests and messages across the whole network. Prosody's mod_register documentation puts it plainly: if you allow open registration, be sure you can handle spam accounts and abuse, or disable federation with mod_s2s.

Controls that operators actually use:

  • Invite-only registration. Prosody's mod_invites family lets admins or users create single use invitation links (prosodyctl shell invite create_account example.com). XEP-0401 (Experimental) standardises generating invitations from a client.
  • Registration throttling. Prosody's mod_register_limits supports min_seconds_between_registrations, registration_throttle_max, registration_throttle_period, an IP blocklist and an allowlist.
  • CAPTCHA forms. XEP-0158 defines CAPTCHA challenges inside XMPP data forms, for servers that must keep public sign-up.
  • Bandwidth limits. Prosody's mod_limits rate limits c2s, inbound s2s, outbound s2s and component connections, in bytes per second with a burst window.
  • User blocking and reporting. XEP-0191 lets users block JIDs, enforced by the server. XEP-0377 (Proposed) adds a reason such as spam or abuse to a block, so the user's server can learn from it.
  • Server level blocking. Admins can refuse federation with specific domains. Community maintained lists of domains and JIDs known for spam exist; treat any list as input, not as an authority.
  • Real-time block lists. ejabberd's documentation describes mod_antispam, added in release 25.07, which filters spam messages and subscription requests from remote servers using real-time block lists of known spammer JIDs and URLs, and mod_muc_rtbl, which applies a shared ban list to group chat rooms through publish-subscribe.
  • Strict s2s authentication. Requiring certificates makes it harder to impersonate a domain, though a spammer with a real domain and a free certificate still gets through. Authentication proves identity, not good behaviour.

Trust in a federated network is therefore layered. TLS and certificates tell you a connection really comes from spam.example; they say nothing about whether spam.example is honest. That judgement is made by admins and users through blocking, the same way email operators handle bad senders. Our self-hosted mail server guide covers the email equivalent, and the parallels are close.

XMPP compared with Matrix, centralised Signal-protocol apps and email

XMPPMatrixCentralised apps on the Signal protocolEmail
FederationYes, server to server over TLSYes, homeservers replicate room stateNo, one operator runs the serviceYes, SMTP between MTAs
Standards bodyIETF core, XSF extensionsMatrix.org Foundation specOperator's own protocol docsIETF
E2EE by defaultNo; OMEMO is per client, often opt-in or per contactDepends on client and room settingsYes, typically always onNo; S/MIME or OpenPGP are add-ons
E2EE designOMEMO: X3DH plus Double Ratchet per deviceOlm and Megolm (Double Ratchet derived)X3DH or successors plus Double RatchetPer-message public key encryption
Metadata visible to serversRoster, presence, routing, devices, group membershipRoom membership and event metadata replicated to every participating homeserverOperator dependent; designs vary in how much they keepHeaders, sender, recipients, subjects
Server historyMAM archive, operator sets retentionFull room history replicated across homeserversUsually minimal on serverMailboxes kept indefinitely by default
Self-hosting effortLow to moderate: one daemon, small footprintModerate to high: database, media store, more RAMNot possible for the official serviceHigh, mostly because of deliverability
Biggest operator riskOpen registration and spam; outdated modulesFederation load, media storage, room stateNot applicableSpam, blocklisting, open relays

In short, XMPP is the lightest to self-host and has the longest standards history, but its encryption depends on client choice. Matrix is heavier and replicates more data across servers; our Matrix guide covers its encryption defaults. Centralised apps make strong encryption the default at the cost of having one operator. Email remains federated and unencrypted by default. Our end to end encryption primer gives the general model if you are new to the topic.

How bridges and gateways change the security model

A bridge or gateway translates between XMPP and another network: IRC, Matrix, SMS, or a proprietary chat service. XEP-0100 describes the classic gateway pattern, in which a gateway is an XMPP component with its own domain that users register with, often handing over their credentials for the other network.

Bridges break end to end encryption by design. To translate a message into another protocol, the bridge must see the plaintext. If you send an OMEMO message to a contact who is really a bridged user, either your client refuses (the bridge has no OMEMO device) or you fall back to unencrypted delivery to the bridge. Some bridges hold an OMEMO identity themselves and decrypt on arrival, which means the bridge operator can read everything.

Things to check before running or trusting a bridge:

  • Who operates it. A public bridge run by a stranger sees your messages and often your credentials for the other network.
  • How it authenticates to your server. XMPP components connect with a shared secret over XEP-0114 (Active). Prosody listens for components on port 5347, documented as private. Never expose that port publicly, and use a long random secret.
  • Its dependencies. Bridges are often small projects in Python, Go or Node.js with many third-party libraries. They are the part of an XMPP stack most likely to have an outdated dependency.
  • Encryption expectations. Tell users plainly that messages to bridged contacts are not end to end encrypted.

Step by step: run a hardened XMPP server

This walkthrough uses Prosody, a lightweight open source XMPP server written in Lua (MIT licence), because its documentation covers each control clearly. The Prosody download page lists 13.0.7 as the latest version at the time of writing. The ejabberd server (Erlang, GPLv2) has equivalent settings; see its configuration documentation for the matching option names. The example domain is example.com with the server on host xmpp.example.com.

Replace every domain, path and address with your own. Commands assume a Debian or Ubuntu host with root access.

1. Install from a current source

# Debian/Ubuntu: distribution package (may lag behind upstream)
sudo apt update
sudo apt install prosody

# Check what you got
prosodyctl about

Prosody's download page recommends adding the project's own package repository because distributions may lag behind the latest release. Record the version from prosodyctl about in your inventory for the next security release.

2. Publish DNS records

Create the SRV records from earlier, plus A and AAAA records for xmpp.example.com and for any component domains (conference.example.com, upload.example.com). Then check:

dig +short SRV _xmpp-client._tcp.example.com
dig +short SRV _xmpps-client._tcp.example.com
dig +short SRV _xmpp-server._tcp.example.com

If your zone is signed with DNSSEC, consider publishing TLSA records for DANE so other servers can verify your certificate without depending only on public CAs. Our DNSSEC explainer covers what that does and does not protect.

3. Open only the ports you use

Prosody's port documentation lists 5222 (clients), 5269 (servers) and 5281 (HTTPS) as public, and 5280 (HTTP), 5347 (external components) and 5582 (telnet console) as private. Direct TLS commonly uses 5223.

sudo ufw allow 5222/tcp   # client STARTTLS
sudo ufw allow 5223/tcp   # client direct TLS (XEP-0368)
sudo ufw allow 5269/tcp   # federation
sudo ufw allow 5281/tcp   # HTTPS: file upload, BOSH, WebSocket
# Do NOT open 5280, 5347 or 5582 to the internet

4. Get certificates and keep them renewed

Use a certificate from a recognised CA covering example.com and every component domain. With an ACME client such as certbot, Prosody's Let's Encrypt page recommends importing certificates with prosodyctl rather than loosening file permissions, and automating it with a deploy hook:

sudo certbot certonly --standalone \
  -d example.com -d xmpp.example.com \
  -d conference.example.com -d upload.example.com

# Deploy hook: re-import after every renewal
sudo tee /etc/letsencrypt/renewal-hooks/deploy/prosody.sh >/dev/null <<'EOF'
#!/bin/sh
/usr/bin/prosodyctl --root cert import /etc/letsencrypt/live
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/prosody.sh
sudo /etc/letsencrypt/renewal-hooks/deploy/prosody.sh

prosodyctl check certs

Standalone mode needs port 80 free; our Let's Encrypt automation guide covers webroot and DNS alternatives.

5. Require encryption and strong authentication

In /etc/prosody/prosody.cfg.lua, global section:

-- Transport security
c2s_require_encryption = true
s2s_require_encryption = true
s2s_secure_auth = true            -- certificates only, no dialback fallback
-- s2s_insecure_domains = { "legacy.example.org" }  -- named exceptions only

-- Never accept a plaintext password over an unencrypted stream
allow_unencrypted_plain_auth = false

-- Store salted SCRAM verifiers, not plaintext passwords
authentication = "internal_hashed"

Setting s2s_secure_auth = true cuts you off from servers with broken certificates. Watch s2s failures in your logs for a week and add narrow s2s_insecure_domains exceptions only where you must.

6. Close open registration and add limits

-- No public sign-up
allow_registration = false

modules_enabled = {
  -- core
  "roster"; "saslauth"; "tls"; "disco"; "pep"; "carbons";
  "blocklist"; "mam"; "smacks"; "limits";
  -- invite-only accounts
  "register"; "invites"; "invites_adhoc"; "invites_register";
}

-- Bandwidth limits per connection type (bytes, not bits)
limits = {
  c2s   = { rate = "10kb/s"; burst = "2s"; };
  s2sin = { rate = "30kb/s"; burst = "5s"; };
}

Then create accounts by invitation:

sudo prosodyctl shell invite create_account example.com

If you must allow public sign-up, keep allow_registration = true but add mod_register_limits with min_seconds_between_registrations and a CAPTCHA, and watch new account volume daily. The rate values above are a starting point, not a recommendation from Prosody; tune them against your own traffic.

7. Set retention for archives and uploads

-- Message Archive Management (XEP-0313)
archive_expires_after = "1w"      -- Prosody's default; choose your own policy
default_archive_policy = true

VirtualHost "example.com"

Component "conference.example.com" "muc"
  modules_enabled = { "muc_mam" }

Component "upload.example.com" "http_file_share"
  http_file_share_expires_after = "1w"
  http_file_share_size_limit = 16 * 1024 * 1024        -- 16 MiB
  http_file_share_daily_quota = 100 * 1024 * 1024      -- per user per day

Prosody's documentation says archived messages are kept for one week by default and that uploaded files match that retention. Setting archive_expires_after = "never" keeps everything forever, which is a privacy and storage decision you should make deliberately. OMEMO encrypted messages are stored as ciphertext, but routing metadata in the archive is not.

8. Check, restart and test from outside

sudo prosodyctl check config
sudo prosodyctl check dns
sudo prosodyctl check certs
sudo prosodyctl check features   # added in Prosody 13.0.0
sudo systemctl restart prosody

# Confirm STARTTLS and the certificate from another machine
openssl s_client -connect xmpp.example.com:5222 \
  -starttls xmpp -xmpphost example.com -brief </dev/null

# Confirm direct TLS with ALPN
openssl s_client -connect xmpp.example.com:5223 \
  -servername example.com -alpn xmpp-client -brief </dev/null

Look for TLS 1.3 (or at least 1.2), the right certificate chain and no plaintext fallback, then confirm a test client is offered SCRAM, ideally a -PLUS variant.

9. Turn on OMEMO in clients and verify devices

OMEMO needs no special server module beyond PEP (mod_pep, which the module list above includes), because device lists and bundles are published there. Clients decide whether to use it. In each client:

  1. Confirm OMEMO is enabled by default for new conversations, or turn it on per contact.
  2. Compare device fingerprints with each contact in person or over a channel you already trust, and mark them verified.
  3. Turn on the option to warn or refuse when a contact gains an unverified device, if your client has one.
  4. Check that your clients share an OMEMO version by sending a test message between every client type your users run.

For group chats, configure rooms as non-anonymous and members-only, as XEP-0384 requires.

10. Back up and test a restore

# Check where Prosody keeps data before you copy anything
sudo prosodyctl about | grep -i data
sudo systemctl stop prosody
sudo tar czf /root/prosody-backup-$(date +%F).tar.gz \
  /etc/prosody /var/lib/prosody
sudo systemctl start prosody

The default data path on Debian packages is /var/lib/prosody, but confirm yours from the config or the about output first. The backup contains account verifiers, rosters, archives and, if certificates are imported, private keys, so encrypt it and store it off the host. A backup does not include anyone's OMEMO private keys; those live only on devices. Test restoring onto a spare machine at least once.

ejabberd equivalents

ejabberd (Erlang, GPLv2) covers the same ground in YAML. Option names below come from the ejabberd configuration documentation; check them against the release you install.

# /etc/ejabberd/ejabberd.yml (excerpt)
listen:
  -
    port: 5222
    module: ejabberd_c2s
    starttls_required: true       # refuse clients that will not upgrade

s2s_use_starttls: required        # documented default is false
registration_timeout: 600         # seconds between sign-ups per IP or JID

modules:
  mod_register:
    access: register              # who may register at all
    ip_access: trusted_networks   # an ip type access rule
    captcha_protected: true
    password_strength: 32         # bits; the documented recommended minimum
  mod_mam: {}
  mod_http_upload: {}
  mod_http_upload_quota: {}

Two details stand out in ejabberd's documentation. First, s2s_use_starttls defaults to false "for historical reasons", which means STARTTLS is prohibited on federation links until you change it; required allows only STARTTLS connections. Second, the security guide lists port 4369 (epmd) and the Erlang distribution port (5210 by default, set with ERL_DIST_PORT) and names /var/lib/ejabberd/.erlang.cookie as a sensitive file. The Erlang cookie is effectively a cluster password: anyone who can reach the distribution port with it can control the node. Firewall both ports from the internet and protect the whole /var/lib/ejabberd/ directory, as the guide advises.

Common mistakes

  • Leaving registration open on a federated server. This is how most XMPP spam starts. Use invites, or throttle and CAPTCHA if you must stay public.
  • No rate limits. Without mod_limits or equivalent, one abusive client or remote server can flood yours.
  • Allowing plaintext or legacy auth. c2s_require_encryption = false or allowing PLAIN over unencrypted streams undoes everything else.
  • Accepting dialback everywhere. Without DNSSEC, dialback is weak identity verification by the XSF's own description. Prefer s2s_secure_auth = true with narrow exceptions.
  • Certificates that do not cover the JID domain. A certificate for only xmpp.example.com fails for users whose JIDs are @example.com.
  • Exposing component, console and cluster ports. Prosody's 5347 and 5582 are private by design, and so are ejabberd's epmd (4369) and Erlang distribution ports.
  • Keeping old federation defaults. ejabberd's s2s_use_starttls defaults to false; leaving it there means federation links run without TLS.
  • Outdated server modules. Community modules are maintained at different speeds from the core. Track both, and read release notes: Prosody 13.0.0, for example, shipped a known issue with manual TLS settings for direct TLS ports, which its release notes document.
  • Assuming E2EE is on by default. XMPP does not encrypt end to end unless both clients enable OMEMO and share a version.
  • Never verifying fingerprints. Blind trust is convenient, but it means a rogue device would be trusted too.
  • Ignoring log and archive retention. Server logs can include JIDs and IP addresses; archives keep metadata even for encrypted messages. Decide how long you keep both.
  • Trusting a public bridge with private conversations. Bridges see plaintext.

FAQ

Is XMPP the same as Jabber?

Yes, historically. Jabber was the original 1999 name of the protocol and the open source community around it. When the IETF standardised it in 2004, the protocol was published as XMPP, and that is the name in every RFC and XEP since. "Jabber" is still used informally, for example in older server names and addresses.

Is XMPP end to end encrypted?

Not by default. Every connection should be TLS encrypted, but the servers see message content unless both clients use OMEMO (or another end to end scheme such as OpenPGP for XMPP). Some clients switch OMEMO on automatically when the contact supports it; others need it turned on per conversation. Check the setting in your own client rather than assuming.

Is OMEMO the same as the Signal protocol?

It reuses the same building blocks, X3DH key agreement and the Double Ratchet, with its own parameters, wire format and device discovery through PEP. Earlier drafts of XEP-0384 depended directly on the Signal protocol library; current versions specify everything in the XEP.

What ports does XMPP use?

5222 for clients with STARTTLS, 5269 for server to server, and commonly 5223 for direct TLS. Servers often run HTTPS on 5281 or 443 for file upload, BOSH and WebSocket. Component and admin ports should stay private.

What is the difference between STARTTLS and direct TLS in XMPP?

STARTTLS opens a plain connection and upgrades it to TLS, so a client must refuse to continue if the upgrade is not offered. Direct TLS (XEP-0368) starts TLS immediately, removing the downgrade window and saving round trips. Support both, and require encryption on both.

Can my XMPP server read my messages?

Without OMEMO, yes: it sees and can archive the plaintext. With OMEMO it stores ciphertext, but it still sees who you talk to, when, your contact list, your presence and your group memberships.

Is self-hosting XMPP safer than using a public server?

It moves trust from someone else's admin to you. You control registration, retention and logs, which helps with metadata. You also take on patching, backups and abuse handling. A well run public server can be safer than a neglected private one.

How do I stop spam on my XMPP server?

Disable open registration or use invites, add registration throttling and bandwidth limits, enable user blocking, and block federating domains that send abuse. If users must be able to sign up publicly, add a CAPTCHA.

Does XMPP support group chats with encryption?

Yes, through Multi-User Chat with OMEMO, if the room is non-anonymous and ideally members-only, and every participant's client supports a compatible OMEMO version. Large public rooms are usually not encrypted end to end.

Why software supply chain hygiene still matters here

An XMPP server is a long-running internet facing daemon built from open source parts: the server itself, its TLS library, an XML parser, a database driver and, often, community modules, bridges and bots in other languages. Your protocol settings can be perfect and still be undone by an outdated library underneath, so knowing exactly which versions you run, and closing the gap when a fix exists, is part of running XMPP safely.

Fix it without waiting for upstream

Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.

1. Autonomous remediation: pull requests, not tickets

When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation categories include dependencies, licenses, security posture and findings, and each one is set separately. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.

2. Gold Open Source: the safe version in seconds, free

gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds.

3. Gold Registry: start from components that are already fixed

Gold Registry provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs and a completed license check, with signed build provenance (signed with Safeguard's key and verifiable with cosign, SLSA Build Level 3 as the design target) and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.

When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. Ask which packages it covers before you plan around it.

Your situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingSafeguard policies and gates in CI
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch
You want to start clean on new projectsPull hardened components and images from Gold Registry

One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.

See how Safeguard would help

Safeguard does not configure XMPP servers, check OMEMO fingerprints or filter spam. Where it fits is the software around them: the container image you run Prosody or ejabberd in, the repository for your bridge or bot, and the SBOM of what you deploy. Gold Open Source at gold.safeguard.sh gives free CVE intelligence with affected and fixed versions for the libraries in that stack.

The first demo is a real Safeguard CLI session. It asks the assistant to fix a vulnerable lodash version in a repository, and the CLI asks before any command that changes something, then edits the manifest and runs the tests. The same flow applies to a Node.js or Python XMPP bridge or bot repository, where outdated dependencies are most likely to hide.

Click through it at your own pace, or open it full screen.

The second demo sets up the local runner on your own machine, approves it with a device code and scans from the terminal. That is how you would scan a server image (safeguard scan --image <name>) or a deployment repository without sending the code anywhere else first.

Click through it at your own pace, or open it full screen.

The third demo connects Claude to the Safeguard MCP server with OAuth and chooses which tools Claude can use. Once connected, you can ask which of your projects carry a given library version, such as a TLS or XML parsing library your XMPP stack depends on.

Click through it at your own pace, or open it full screen.

To try it on your own stack, download Safeguard or read the CLI documentation.

Quick check

Why is Server Dialback considered weak, and what should you prefer?

XEP-0220 verifies a remote server's identity through DNS, so without DNSSEC the result is weak identity verification. Prefer certificate authentication with SASL EXTERNAL (or DANE), for example with Prosody's s2s_secure_auth = true, and allow dialback only for named exceptions.

If two friends both use OMEMO, what can their servers still see?

Sender and recipient addresses, timestamps and sizes, both contact lists, presence, device IDs and public key bundles, group memberships, and file upload metadata. OMEMO protects content, not metadata.

Sources

Last reviewed: 25 October 2026. XEP-0384 is still Experimental; check the XSF extensions page for its current version and status.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.