Self-Hosting SimpleX Servers (SMP + XFTP)
Self-Hosting SimpleX Servers (SMP + XFTP)
Section titled “Self-Hosting SimpleX Servers (SMP + XFTP)”SimpleX Chat has no central servers and no user identifiers. Messages and files flow through relays that anyone can run. There are two kinds:
- SMP server (Simple Messaging Protocol) — carries messages via anonymous queues and holds pending messages temporarily. Default port 5223 (recent clients also try 443).
- XFTP server (the file-transfer protocol) — stores encrypted file chunks that auto-expire. Default port 443.
By default the app uses SimpleX’s preset operators (you’ll see SimpleX Chat and Flux under Network & servers → Preset servers). Running your own relays gives a community independence from those presets, keeps metadata on infrastructure you control, and survives upstream outages.
This guide covers:
- Does self-hosting keep traffic on my servers? — the routing model.
- Use a community server in your client — point your app at an existing relay.
- Self-host your own relays — stand up SMP + XFTP.
- What can the operator see? and a verification checklist.
Does self-hosting keep traffic on my servers?
Section titled “Does self-hosting keep traffic on my servers?”Yes — if both people’s connection was created using only your relays, the message and file content flows only through your servers. There is no central SimpleX server it has to pass through.
Why: there’s no “network” in the middle. Each user picks which server receives their messages.
- Your inbound messages land in a queue on your chosen SMP server.
- Your contact’s inbound messages land in a queue on their chosen SMP server.
- When you send, your client delivers to the recipient’s receiving queue.
So if both people configure your SMP server to receive, both queues live on your relay and every message between them stays on it. Files work the same way: the sender uploads chunks to the sender’s XFTP server and the recipient downloads from there — both on your XFTP means files stay on your relay too.
To actually achieve “only my servers”:
- Both devices add your SMP + XFTP and enable use for new connections and use to receive.
- Disable the preset operators — otherwise new queues (and the private-routing hop) may pick a preset server.
- Move existing receiving addresses manually or create new connections. New contacts use the new selection automatically; an existing contact can be migrated with Change receiving address, one connection at a time.
Use a community server in your client
Section titled “Use a community server in your client”A SimpleX server address looks like this:
smp://<fingerprint>:<password>@smp.example.orgxftp://<fingerprint>:<password>@xftp.example.orgThe :<password> part is present only on password-protected relays (most
community relays are). The address — password included — is what lets you create
queues / upload files. Treat the full address as a shared secret.
Steps (mobile / desktop)
Section titled “Steps (mobile / desktop)”- Open Settings → Network & servers.
- Under Messages and files, tap Your servers.
- Tap Add server → Enter server manually (or Scan server QR code if your admin shared a QR).
- Paste the
smp://…address. Repeat Add server for thexftp://…address. SimpleX files each into the right list automatically. - Open each server you added and set how it’s used:
- Use for new connections — new contacts/groups get created on it.
- Use to receive — the app accepts messages/files routed through it.
- (Optional) To use only your community relays, open the Preset servers (SimpleX Chat, Flux) and toggle them off — but keep at least one enabled server that can receive, or you’ll stop getting messages.
- Tap Test servers to verify the app can reach them, then save.
Self-host your own relays
Section titled “Self-host your own relays”What you need
Section titled “What you need”- A host with one or two public IPs and these free public TCP ports: 5223 (SMP) and 443 (both SMP-on-443 and XFTP want 443 — see below).
- Docker + Docker Compose.
- DNS records pointing at your IP(s), DNS-only / not proxied.
- A little disk for XFTP chunks (transient, auto-expire; a quota caps it).
Why SMP wants 443 (the layout decision)
Section titled “Why SMP wants 443 (the layout decision)”Recent SimpleX clients connect to SMP on port 443 by default, falling back to 5223. XFTP’s native port is also 443. Two 443 services can’t share one IP, so you have a choice:
| Layout | SMP | XFTP | Trade-off |
|---|---|---|---|
| Two IPs (recommended) | IP-A:5223 + IP-A:443 | IP-B:443 | Both reachable on 443; needs a 2nd public IP |
| One IP (simplest) | IP:5223 only | IP:443 | Official layout, but clients on networks that block 5223 can’t reach SMP |
The docker-compose.yml below shows the two-IP layout. For one IP, drop the
second SMP port line and point both DNS records at the same IP.
docker-compose.yml
Section titled “docker-compose.yml”name: simplex-server
services: smp-server: image: simplexchat/smp-server:v6.5.2 # example tested pin; review upstream releases container_name: simplex-smp-server restart: unless-stopped environment: ADDR: smp.example.org # your SMP FQDN PASS: ${SMP_PASSWORD} # queue-creation password WEB_MANUAL: "1" # see gotcha below — load-bearing volumes: - ./data/smp/config:/etc/opt/simplex # CA key + fingerprint (BACK UP) - ./data/smp/state:/var/opt/simplex ports: - "203.0.113.10:5223:5223" # bind ONE public IP, not 0.0.0.0 - "203.0.113.10:443:443" # SMP also on 443 (drop this for one-IP setups)
xftp-server: image: simplexchat/xftp-server:v6.5.2 # example tested pin; review upstream releases container_name: simplex-xftp-server restart: unless-stopped environment: ADDR: xftp.example.org QUOTA: 100gb # refuse uploads past this PASS: ${XFTP_PASSWORD} volumes: - ./data/xftp/config:/etc/opt/simplex-xftp # fingerprint (BACK UP) - ./data/xftp/state:/var/opt/simplex-xftp - ./data/xftp/files:/srv/xftp # encrypted chunks (QUOTA applies) ports: - "203.0.113.11:443:443" # XFTP on its OWN IP's 443Set a strong password once (it goes in the client address, so keep it URL-safe):
printf 'SMP_PASSWORD=%s\nXFTP_PASSWORD=%s\n' \ "$(openssl rand -hex 24)" "$(openssl rand -hex 24)" > .envdocker compose up -dGet your server addresses
Section titled “Get your server addresses”On first start each server generates its CA, certificate, and fingerprint:
cat data/smp/config/fingerprintcat data/xftp/config/fingerprintAssemble the addresses you hand to clients (note the :PASS):
smp://<smp-fingerprint>:<SIMPLEX_PASSWORD>@smp.example.orgxftp://<xftp-fingerprint>:<SIMPLEX_PASSWORD>@xftp.example.orgCreate A records for each relay, DNS-only / not proxied (grey cloud in Cloudflare — orange breaks it):
| Name | Type | Value | Proxy |
|---|---|---|---|
smp.example.org | A | 203.0.113.10 | DNS only |
xftp.example.org | A | 203.0.113.11 | DNS only |
(One-IP setup: both point at the same address.)
Open the firewall
Section titled “Open the firewall”Publishing a Docker port is not always enough. On hosts where Docker’s
DOCKER-USER chain ends in a default-deny (a common hardening pattern), the relay
listens but the internet can’t reach it — you must explicitly allow the pre-DNAT
public IP:port, before the DROP:
iptables -I DOCKER-USER -p tcp -m conntrack --ctorigdst 203.0.113.10 --ctorigdstport 5223 -j ACCEPTiptables -I DOCKER-USER -p tcp -m conntrack --ctorigdst 203.0.113.10 --ctorigdstport 443 -j ACCEPT # SMP-on-443iptables -I DOCKER-USER -p tcp -m conntrack --ctorigdst 203.0.113.11 --ctorigdstport 443 -j ACCEPT # XFTPnetfilter-persistent save # persist across rebootsOn a plain ufw/firewalld host without a default-deny DOCKER-USER chain, just
open the ports. Re-apply after any Docker reinstall — Docker rebuilds
DOCKER-USER and drops your rules until they’re restored.
What can the operator actually see?
Section titled “What can the operator actually see?”Less than a conventional messaging server, but not nothing. A live relay inspection showed the following current behavior; treat it as an observation, not a promise that future versions or added host logging cannot change it:
- In the inspected application logs/store: no source IPs, user identities, or
plaintext content. Store records contained opaque queue/file IDs and protocol
material (
CREATE/SECURE/FNEWrecords). - At the network layer you can observe the source IP of a connection with
tcpdump/sson the host — but it isn’t written to any log, and a client using Tor, a SOCKS proxy, or private routing shows you only a relay/proxy IP, not the device. - The protocol records do not directly identify a user, reveal plaintext, or encode a social graph. A host or network operator can still observe source IP, timing, connection duration, and traffic size and may correlate those signals.
If you add connection-IP logging for abuse handling, document the retention and privacy impact; host-level firewall or flow logging changes the observation above.
Verification & validation
Section titled “Verification & validation”Run these after standing a relay up (and keep the list in your runbook). Your own machine often can’t test the public path if it reaches the host over a VPN/tailnet — test from a true external vantage.
Reachability & identity
# TCP reachable from off-site (or use https://check-host.net/check-tcp):bash -c '</dev/tcp/smp.example.org/5223 && echo SMP-5223-OK'bash -c '</dev/tcp/smp.example.org/443 && echo SMP-443-OK'bash -c '</dev/tcp/xftp.example.org/443 && echo XFTP-OK'
# the CA fingerprint the server SERVES must equal the one in your address# (detects MITM / wrong address):echo | openssl s_client -connect smp.example.org:443 -servername smp.example.org -showcerts 2>/dev/null \ | awk '/BEGIN/{n++} n==2' | openssl x509 -outform DER | openssl dgst -sha256 -binary | openssl base64Functional (in a client)
- Add both servers, disable presets, and make a brand-new contact between
two devices — proves your relays alone carry traffic. Confirm on the host:
wc -l data/smp/state/smp-server-store.loggrows (CREATE/SECURE). - Send a multi-MB file (small files/images go inline over SMP, so they never
touch XFTP). Confirm
FNEW/FPUTappear indata/xftp/state/file-server-store.log. - Add the SMP address once plain (defaults to 443) and once with explicit
:5223— both should pass Test servers. - Test from a restrictive network (cellular / corporate) to confirm 443 works.
Password / security
- Add the server address without the
:password@part → creating a queue must fail (proves it’s not an open relay). ss -tlnp— confirm the relays bind only the intended public IP(s), not0.0.0.0.
Resilience
docker compose restart→ fingerprint unchanged, queues restored.- Reboot the host → firewall rules reload (
netfilter-persistent) and relays return. - Backup/restore drill (most important): back up
data/{smp,xftp}/config/, restore into a fresh container, confirm the same fingerprint comes up. Losing the CA key = losing the server identity = every client breaks.
Operational notes
Section titled “Operational notes”- Back up
data/{smp,xftp}/config/off-box — CA private key + fingerprint = the server identity. - Quota:
QUOTAcaps stored XFTP chunks; files auto-expire (~48 h by default, configurable). Raise any time anddocker compose up -d. - Cert rotation: rotate the TLS cert ~every 3 months with the offline CA key
(
smp-server cert); the fingerprint/identity is preserved, so addresses don’t change. - Password vs open: setting
PASSrestricts queue/file creation to people you give the address to. Omit it only if you intend a public-good relay. - WAN dependency: a relay on a public IP is unreachable while that WAN link is down — unlike HTTP services, it can’t fall back through a tunnel.
- Updates: use explicit tested version tags or digests, record the prior references for rollback, then pull and recreate deliberately.
Related
Section titled “Related”- SimpleX Chat (community guide) — joining and using IrregularChat on SimpleX
- Secure Messaging Comparison — SimpleX vs Signal, Matrix, Session
- Self-Hosted SimpleLogin (2026) — another public-IP-on-a-/29 service guide
- Docker — container basics
- Upstream: Hosting your own SMP server · Hosting your own XFTP server