Files
xmpp-server-prosody/SKILL.md
T
2026-09-06 13:51:11 +00:00

7.4 KiB

name, title, description
name title description
xmpp-server-prosody XMPP Server Deployment (Prosody) Deploy federated XMPP (Prosody in Docker) behind a proxy.

XMPP Server Deployment (Prosody)

Deploy a self-hosted federated XMPP messenger (family/team) with Prosody in Docker, exposed through a public VPS reverse proxy. Produced from the ICQ project (chat.nixg.ru) on the bigbox/vps02 infrastructure.

When to use

  • User wants a self-hosted messenger (family, team) with iOS/Android clients and federation (talking to the whole XMPP network).
  • Choosing XMPP: it is the federated standard (like email). Do not attempt to "clone Telegram/MTProto" — it is closed, centralised, non-federated. XMPP is the right answer when federation matters.
  • Snikket vs Prosody: Snikket server is easier but assumes direct 443 access and is finicky behind a reverse proxy. Prosody is the right choice when going through a Caddy/reverse-proxy + WireGuard setup.

Architecture (this environment)

[Clients: Monal/Snikket/Conversations iOS+Android]
        │ c2s 5222 (TLS)
        ▼
[bigbox] Prosody Docker (/opt/icq) — no public IP
        │ s2s 5269 (federation) · http 5280 (websocket/BOSH)
        ▼
[vps02] Caddy (host-network, reverse_proxy 10.8.0.2:5280) + iptables DNAT 5222/5269 → 10.8.0.2
        ▲
        └─ WireGuard wg0 (10.8.0.x)

DNS records (critical for federation)

A      chat.nixg.ru            → public IP of vps02
SRV    _xmpp-client._tcp.chat.nixg.ru  → chat.nixg.ru:5222  (priority 0, weight 5)
SRV    _xmpp-server._tcp.chat.nixg.ru  → chat.nixg.ru:5269  (priority 0, weight 5)
A      conference.chat.nixg.ru → public IP (MUC component host)
  • Without SRV records, other servers cannot find you → federation silently fails. For a standard XMPP domain hosted at a subdomain, prosodyctl suggests using SRV to redirect; that is fine.
  • A-record alone works for clients hardcoded to the domain, but SRV is what the federated network uses.

docker-compose.yml

services:
  prosody:
    image: prosody/prosody:latest
    container_name: icq-prosody
    restart: unless-stopped
    hostname: chat.nixg.ru
    volumes:
      - ./data:/var/lib/prosody
      - ./config:/etc/prosody
      - ./certs:/etc/prosody/certs
      - ./modules:/etc/prosody/modules
      - ./logs:/var/log/prosody
    ports:
      - "5222:5222"   # c2s clients
      - "5269:5269"   # s2s federation
      - "5280:5280"   # BOSH/websocket (behind Caddy)
      # - "5281:5281" # https — SKIP unless you have a cert wired

Remove the obsolete version: attribute (Compose v2 warns).

prosody.cfg.lua pitfall checklist

  • modules_enabled must NOT contain muc — MUC is a Component (Component "conference.chat.nixg.ru" "muc"). Loading muc as a module errors out.
  • muc_mam loads only on a MUC component, not on the host.
  • log block must be in the GLOBAL section, above any VirtualHost/Component, or prosodyctl check config complains.
  • Community modules NOT in the stock image (prosody/prosody:latest): http_upload, smacks, s2s_bidi, xmpp_component. They come from the prosody-modules community repo — either install them or leave them out. HTTP Upload (XEP-0363, needed for bot file delivery) requires installing mod_http_upload manually.
  • prosodyctl register <user> <domain> <pass> works inside the container; do NOT use -it (fails "cannot attach stdin"). Data dir is URL-encoded: /var/lib/prosody/chat%2enixg%2eru/accounts/.
  • https 5281 bind error ("No certificate present") is benign — the stock image tries to bind it by default. Disable by not mapping the port; it does not break c2s/s2s/http.
  • Prosody 13.0 != 0.11 for external components: component_ports was REMOVED (listener is hardcoded 5347); a module-less Component "jid" block is required to raise the listener; component_interfaces must be in the GLOBAL section; log = {...} may only appear ONCE in the config. mod_privilege (XEP-0356) needs the TIP version (promise API) and must be enabled on BOTH the component block and the VirtualHost. Full recipe + gotchas: references/prosody-13-parallel-stand.md.
  • Validate: docker exec icq-prosody prosodyctl check config → "All checks passed".
  • allow_registration = true enables in-band registration — flip off after family accounts are created.

Exposing through vps02 (Caddy + WireGuard + iptables)

Caddy on vps02 runs network_mode: host with a plain Caddyfile. Add:

chat.nixg.ru {
    reverse_proxy 10.8.0.2:5280 {
        header_up Host {host}
        header_up X-Forwarded-Proto https
    }
}

Caddy auto-issues Let's Encrypt TLS. Reload: docker exec caddy caddy reload --config /etc/caddy/Caddyfile.

For non-HTTP ports (5222 c2s, 5269 s2s) Caddy cannot proxy them — use iptables DNAT on vps02. Existing pattern (mirrors the 8443 rule already present):

sudo iptables -t nat -A PREROUTING -p tcp --dport 5222 -j DNAT --to-destination 10.8.0.2:5222
sudo iptables -t nat -A PREROUTING -p tcp --dport 5269 -j DNAT --to-destination 10.8.0.2:5269
sudo iptables -I INPUT -p tcp --dport 5222 -j ACCEPT
sudo iptables -I INPUT -p tcp --dport 5269 -j ACCEPT
sudo iptables -I FORWARD 1 -d 10.8.0.2 -p tcp --dport 5222 -j ACCEPT
sudo iptables -I FORWARD 1 -d 10.8.0.2 -p tcp --dport 5269 -j ACCEPT
sudo iptables -t nat -A POSTROUTING -o wg0 -p tcp --dport 5222 -j MASQUERADE
sudo iptables -t nat -A POSTROUTING -o wg0 -p tcp --dport 5269 -j MASQUERADE
# persist:
sudo sh -c "iptables-save > /etc/iptables/rules.v4"
  • FORWARD policy is DROP by default on vps02 — you MUST add FORWARD ACCEPT rules (the 8443 precedent has them). DNAT alone is not enough.
  • Save via sudo sh -c "iptables-save > /etc/iptables/rules.v4" — plain sudo iptables-save > file fails (redirect runs as your user, not root). netfilter-persistent service is enabled.

Testing the external path — hairpin NAT trap

  • Do NOT test a host's public ports from the same host (or from a host that routes back into it). ip route get <pub-ip> shows <local> → traffic never leaves the box, DNAT never fires. Connection refused from such tests is a FALSE NEGATIVE.
  • Validate the path instead:
    • From vps02 → 10.8.0.2:5222 over WG (real path, with tcpdump on bigbox wg0 shows the TCP handshake).
    • From an EXTERNAL host (phone on mobile data, another VPS): nc -vz chat.nixg.ru 5222.
  • If both internal paths work but an external probe fails, suspect the provider's cloud firewall panel (Timeweb etc.) — ports 5222/5269 are commonly closed there by default. Ask the user to open them in the provider panel.
  • 443 via Caddy IS externally testable from anywhere; a working 443 + working WG path to 5222 usually means only the provider firewall is in the way.

Bots (slixmpp) and file delivery

  • An XMPP bot is just a second user account (JID) — no separate Bot API. Python slixmpp is the live, mature library.
  • To send files (e.g. a book-download bot), the server needs HTTP Upload (XEP-0363) — community module mod_http_upload (not in stock image). The client sends the file to the upload component; the recipient gets a link.
  • Bot pattern: user messages book@chat.nixg.ru → bot queries an OPDS catalog (Flibusta et al.) → downloads epub/fb2 → uploads via HTTP Upload → sends link.

References

  • references/vps02-bigbox-wg-access.md — exact vps02/bigbox network topology, Caddyfile, iptables persistence and hairpin-test evidence.