SSD Nodes Learn 🎉 VPS from $5.50/mo
Guides Matt ConnorBy Matt Connor

Self-host a SimpleX chat server on a VPS

Run your own SimpleX SMP relay on a VPS: pinned install, the fingerprint clients need, ports, an unprivileged service user, backups, TLS, and its threat model.

What a self-hosted SimpleX chat server does

To self-host a SimpleX chat server you run one daemon on a VPS: smp-server, the relay for SMP (simplex messaging protocol). It holds the message queues your contacts write to and read from. A second, optional daemon called xftp-server relays file transfers. Both come from the same project, simplexmq, and each one is a single binary, a config file, and an append-only log.

This is written for the operator, not the app user. The relay holds no accounts, no contact lists and no chat history. It holds queues, some undelivered ciphertext, and a certificate that identifies it. What you take on is uptime, a little disk, and the metadata that passes through your box.

Every command, path, port and flag below comes from the project's own documentation: the SMP server hosting page, the XFTP server page, and the protocol security document. Where a number matters, the page it came from is named next to it.

Why a network with no user identifiers still needs relays

SimpleX has no usernames, no phone numbers and no account IDs. A contact is a unidirectional queue: an address on some relay that one side writes to and the other side reads from. Two of your contacts share no identifier that a server could join up.

Those queues still have to live somewhere, for a plain reason. Two phones are rarely online at the same second. Something has to accept a message now and hold it until the other device asks. That is the whole job of an SMP relay. It also means the two devices never connect to each other, so neither one learns the other's IP (internet protocol) address. The relay absorbs that exposure instead.

The relay's hostname is part of the queue address, so it is inside every invitation link you hand out from it. Keep that in mind when you read the threat model near the end.

What a relay can and cannot see

The project states this as a threat model in protocol/security.md, and it is worth reading before you install anything, because after this guide that relay is yours. A relay, including one fully controlled by an attacker, cannot learn the contents or the type of messages, cannot undetectably add, duplicate or corrupt individual messages, and cannot break the end-to-end encryption with an active attack.

The same page lists what a relay can do. It can learn when a queue recipient is online. It can count how many messages pass through a queue. It can learn a recipient's IP address. It can drop every future message in a queue, or lie about the state of that queue.

So the split is clean. Confidentiality is the client's job and self-hosting does not touch it. Metadata and availability are the relay operator's job, and self-hosting hands both of them to you.

What you need before you start

  • A VPS running Ubuntu 22.04 or 24.04. The project publishes release binaries built for exactly those two, in x86-64 and aarch64.
  • A domain name with an A record pointing at the VPS, plus an AAAA record if you have IPv6. The docs use smp1.example.com as the example.
  • Root or sudo access, and a second SSH session open while you touch the firewall.
  • Somewhere off the box to store a backup, because the config directory is the server's identity.

On an ARM instance take the aarch64 asset instead of x86-64. Nothing else in this guide changes, and the choice between ARM and x86 VPS plans is about price and per-core speed, not about whether this software runs.

Install a pinned release, not "latest"

The project ships an install script that pulls the current release and registers a simplex-servers-update command. It works. Pin the version anyway: a relay whose binary changes under you is a relay you cannot reason about when something breaks.

As of August 2026 the current simplexmq release is v6.5.0, published on 29 April 2026. Check the releases page for the tag you want, then use that tag everywhere below.

sudo useradd -m smp
sudo install -d -o smp -g smp -m 755 /etc/opt/simplex /var/opt/simplex

useradd -m smp sets no password, so nobody logs in as smp directly. Create the two directories yourself before running anything else, because /etc/opt belongs to root and is mode 755, which leaves the smp user nowhere to write its own config directory.

VER=v6.5.0
curl -fL "https://github.com/simplex-chat/simplexmq/releases/download/$VER/smp-server-ubuntu-24_04-x86-64" -o /tmp/smp-server
sha256sum /tmp/smp-server

Compare that hash against the SHA2-256 checksums published in the release notes for the same tag. The project also signs release checksums with the SimpleX Chat key FB44AF81A45BDE327319797C85107E357D4A17FC, documented on the server page, so you can verify the signature rather than trusting the page you read the hash from.

sudo install -m 755 -o root -g root /tmp/smp-server /usr/local/bin/smp-server

Install it root-owned on purpose. The service runs as smp, so a compromise of the service cannot rewrite the binary it starts from.

Initialise the server, and the two secrets it prints

sudo su smp -c "smp-server init --yes --store-log --daily-stats --no-password --fqdn=smp1.example.com"
  • --store-log (-l) writes an append-only log of queues to /var/opt/simplex/smp-server-store.log, so the relay survives a restart. Without it a restart throws away every queue, which means every contact routed through you stops working.
  • --daily-stats (-s) writes counters in CSV form to /var/opt/simplex/smp-server-stats.daily.log.
  • --fqdn puts your domain into the generated certificate. Use --ip instead if you have no domain.
  • --no-password lets anyone create a queue on your relay. To keep it private, set create_password under [AUTH] in /etc/opt/simplex/smp-server.ini after init, rather than passing --password here, because a command line is visible in your shell history and in the process list while it runs.

Init generates a certificate and prints the two values you must keep. The first is the fingerprint, a base64 string that is also written to /etc/opt/simplex/fingerprint. The second is the full server address, which is the fingerprint plus your hostname. Copy both now.

Init also creates /etc/opt/simplex/ca.key, and the docs tell you to move that file to offline storage. The reason is worth stating: clients pin the fingerprint of that certificate authority, so anyone holding ca.key can issue a fresh server certificate that your clients accept as yours. You only need it back to rotate the server certificate later with smp-server cert.

Treat init as a one-time step. The fingerprint in your address comes from the authority it generates, so regenerating that authority gives you a different address and orphans the one you handed out.

Run it under systemd as an unprivileged user

Write /etc/systemd/system/smp-server.service, exactly as the docs give it:

[Unit]
Description=SMP server systemd service

[Service]
User=smp
Group=smp
Type=simple
ExecStart=/usr/local/bin/smp-server start +RTS -N -RTS
ExecStopPost=/usr/bin/env sh -c '[ -e "/var/opt/simplex/smp-server-store.log" ] && cp "/var/opt/simplex/smp-server-store.log" "/var/opt/simplex/smp-server-store.log.bak"'
LimitNOFILE=65535
KillSignal=SIGINT
TimeoutStopSec=infinity

[Install]
WantedBy=multi-user.target

The upstream unit also carries AmbientCapabilities=CAP_NET_BIND_SERVICE. That line exists because the process runs as smp, and ports below 1024 are closed to a non-root process, so without it the daemon cannot bind 80 or 443. Add it if you serve those ports. LimitNOFILE=65535 matters because every subscribed client holds an open TCP connection, and the default limit is far below what a busy relay needs. ExecStopPost copies the store log to a .bak file on every stop, which gives you one free rollback point.

sudo systemctl daemon-reload
sudo systemctl enable --now smp-server
sudo systemctl status smp-server
sudo journalctl -fu smp-server

A healthy start logs the server address. Then confirm the sockets are actually open:

sudo ss -tlnp | grep -E ':(443|5223)'

Both lines should name smp-server. Running the daemon under its own account with no sudo rights is the same habit described in per-service accounts on a VPS, and it is what stops a bug in one network daemon from becoming a root shell.

Which ports to open, and one to keep closed

The docs list three: 5223/tcp, 443/tcp and 80/tcp. Port 5223 is the SMP transport. The shipped config sets port: 5223,443 under [TRANSPORT], so the same protocol also answers on 443, which matters because many restrictive networks allow outbound 443 and nothing else. Port 80 is only needed for the optional information page and its redirect to HTTPS.

sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 5223/tcp
sudo ufw enable

Do not open 5224. That is the control port, and the docs reach it from the box itself with nc 127.0.0.1 5224. It prints server state and deletes queues, so it belongs on loopback with the admin and user passwords set under [AUTH]. If you are new to the tool, the basics of ufw on a VPS covers rule order and how to avoid locking yourself out.

One more control catches people out. Most providers run a network firewall in the panel, separate from ufw on the box. A port can be open in ufw and still dropped before it reaches you.

The server address your clients need

smp://<fingerprint>[:<password>]@<public_hostname>[,<onion_hostname>]

That string is the entire client-side configuration. Paste it into the app's server settings, or let someone scan the QR code the app shows for it. The docs note that the QR includes the password, so a person who scans it can receive messages through your server too.

One documented behaviour surprises everyone. Adding your server in the app only affects contacts you make from that point on. Existing contacts stay on the relays their queues were created on, and they do not migrate. That is also why you cannot switch a relay off the day after you replace it.

Adding an XFTP file relay

XFTP (SimpleX file transfer protocol) is the file half of the network, and it is a separate daemon with its own address. Per the project's XFTP announcement, relays have no file metadata at all: they see individual chunks, each 256kb, 1mb or 4mb, with access authorised by anonymous credentials. A sender can spread the chunks of one file across several relays, so your box holds pieces, not files.

sudo useradd -m xftp
sudo install -d -o xftp -g xftp -m 755 /etc/opt/simplex-xftp /var/opt/simplex-xftp /srv/xftp
curl -fL "https://github.com/simplex-chat/simplexmq/releases/download/$VER/xftp-server-ubuntu-24_04-x86-64" -o /tmp/xftp-server
sudo install -m 755 -o root -g root /tmp/xftp-server /usr/local/bin/xftp-server
sudo su xftp -c "xftp-server init -l --fqdn=xftp1.example.com -q '20gb' -p /srv/xftp/"

Its config lives in /etc/opt/simplex-xftp/, its state in /var/opt/simplex-xftp/, and the file chunks in whatever -p names. The systemd unit is the same shape with User=xftp and ExecStart=/usr/local/bin/xftp-server start +RTS -N -RTS. Init prints an xftp:// address in the same format as the SMP one, with its own fingerprint in /etc/opt/simplex-xftp/fingerprint.

There is a collision to plan for. The XFTP server's documented port is 443, and the SMP config also lists 443. Two processes cannot bind the same port on the same address, so on one VPS something has to give. The simplest fix is to set port: 5223 in the SMP [TRANSPORT] section and leave 443 to the file relay, at the cost of the 443 fallback for clients on restrictive networks. The alternatives are a second IP address on the same VPS, or a second VPS.

Size the quota honestly. -q '20gb' is a promise about disk you have. The file relay is the part that eats disk and bandwidth. The message relay barely touches either.

What lives on disk, and what a backup restores

Two directories matter. /etc/opt/simplex/ is the identity: smp-server.ini, the server certificate and key, ca.key, and fingerprint. /var/opt/simplex/ is the state: smp-server-store.log holds the queues and, when restore_messages: on, the undelivered messages, alongside the daily stats file.

sudo systemctl stop smp-server
sudo tar czf /root/simplex-backup.tgz -C / etc/opt/simplex var/opt/simplex
sudo chmod 600 /root/simplex-backup.tgz
sudo systemctl start smp-server

Be clear about what that archive is. It is not a message archive: the queued items are ciphertext for keys the relay never held, and the shipped [STORE_LOG] config expires messages after 21 days anyway. It is a copy of the server's identity, ca.key included, so anyone who takes the file can present themselves as your relay to your contacts. Encrypt it and keep it off the box.

The payoff is restore. Put /etc/opt/simplex back on a fresh VPS, point the same DNS name at it, and the fingerprint is unchanged, so every address you handed out still works. Lose that directory and there is no recovery: a new install means a new fingerprint, which means a new address, which means every contact routed through your relay is gone.

TLS: two certificates doing different jobs

The SMP transport does not use a public certificate authority. Init generates a private authority and a server certificate, and the fingerprint of that authority travels inside the server address. The client checks what the server presents against that pinned fingerprint, which is what the project describes as protecting the client to server connection against machine-in-the-middle attacks. There is no ACME (automatic certificate management environment) client to run on that port, and rotation is a manual smp-server cert run with SMP_SERVER_CFG_PATH set.

The optional information page is the other certificate. Its [WEB] section names static_path, https: 443, cert: /etc/opt/simplex/web.crt and key: /etc/opt/simplex/web.key. A browser has never heard of your private authority, so this is the one place a publicly trusted certificate belongs. The docs' Docker quick start puts Caddy in front of the server for exactly that, and issues the certificate automatically.

Reaching the relay over Tor

The docs include a Tor section that installs Tor from the Tor Project repository and adds a hidden service in /etc/tor/torrc:

SOCKSPort 0
HiddenServiceNonAnonymousMode 1
HiddenServiceSingleHopMode 1
HiddenServiceDir /var/lib/tor/simplex-smp/
HiddenServicePort 5223 localhost:5223
HiddenServicePort 443 localhost:443

Read the two mode lines carefully. Single hop and non-anonymous mean the relay's own location is not hidden. The onion address is fast and it gives clients a way in that never reveals their IP address to you, but the server itself stays findable at its public IP. The onion hostname from /var/lib/tor/simplex-smp/hostname goes on the end of the server address after a comma. If you want the server's location hidden as well, that is a different configuration, and running a real onion service on a VPS covers the trade. The difference in what each tool hides is the subject of Tor compared with a VPN, and it applies here directly.

Threat model: what self-hosting changes

What it buys you. The metadata (which queues exist, when they are read, which addresses connect) sits on a machine you control, and you set how long any of it is kept. You are also not part of a large pool that can be requested in one go.

What it does not buy you, stated plainly:

  • The encryption is unchanged. Messages were end to end encrypted before you built this and they are end to end encrypted after. Self-hosting is a metadata decision, not a cryptography decision.
  • Your VPS provider sees the traffic to your IP address and holds your billing details. You moved trust from a messaging operator to a hosting operator. You did not remove it.
  • Your relay is a small crowd. If it serves one household, then connecting to it identifies that household, and its hostname sits inside every invitation link you send from it. A busy public relay hides you better in that one respect, and this is the real trade.
  • Availability is now yours. A full disk or a dead box means messages stop being delivered, and your contacts have no way to route around you.

The same reasoning applies to any private service you put on a box you own, whether that is this relay or a WireGuard VPN on your own VPS. You are choosing which party sees the metadata. You are not making it disappear.

When it does not work

The service starts and immediately stops. Read sudo journalctl -u smp-server -n 50. A bind failure names the port it could not take. Then run sudo ss -tlnp | grep :443 to see which process is already holding it, which on a fresh box is usually nginx, Caddy, or the XFTP server you installed an hour ago.

Init cannot write its config. Running smp-server init as the smp user before /etc/opt/simplex exists gives a permission error, because /etc/opt is owned by root. Create the directory with the right owner first, then re-run init.

Clients cannot reach the relay. Check the name resolves to the right address with dig +short smp1.example.com. Then test the port from your laptop, not from the server: nc -vz smp1.example.com 5223. A connection that fails from outside while ss shows the socket open on the box points at the provider's network firewall, which is a separate control from ufw.

A contact cannot connect through your relay. The fingerprint in the address you shared must match the current contents of /etc/opt/simplex/fingerprint. If you set create_password under [AUTH], the address must carry that password too, or the client is not allowed to create a queue.

Nothing moved after you added the server in the app. That is intended. Only new contacts use the newly added relay. Existing contacts keep the queues they already have.

FAQ

Does self-hosting a SimpleX server make my messages more secure?

No, and that is by design. SimpleX encrypts messages end to end between devices, so the relay never has the keys, whoever runs it. Self-hosting changes who observes the metadata around those messages: which queues exist, when they are read, and which IP addresses connect. It is a metadata decision. If your reason for self-hosting is stronger encryption, the encryption was already there.

What can a SimpleX relay operator actually see?

The project's protocol/security.md sets this out. A relay cannot read message contents or types, cannot undetectably alter individual messages, and cannot break the end-to-end encryption with an active attack. It can see when a queue's recipient is online, count messages passing through a queue, learn a recipient's IP address, drop future messages in a queue, or lie about that queue's state. Those are the powers you hold once the relay is yours.

Do I need a domain name and a TLS certificate?

You need a domain for a usable setup, and smp-server init accepts --ip if you truly have none. You do not need a certificate from a public authority for the messaging port: init generates its own authority, and the client pins the fingerprint that appears in your smp:// address. A publicly trusted certificate is only needed for the optional web information page, configured as cert and key in the [WEB] section of smp-server.ini.

What happens if I lose /etc/opt/simplex?

Every address you handed out stops working. That directory holds the certificate authority whose fingerprint is embedded in your server address, so a rebuild produces a different fingerprint and therefore a different server. Contacts whose queues live on that relay cannot be repaired from the client side. Back the directory up encrypted and off the box, and store ca.key offline as the docs instruct, since whoever holds it can impersonate your relay.

Can I run the SMP relay and the XFTP file relay on the same VPS?

Yes, with one conflict to settle. The XFTP server's documented port is 443 and the default SMP config lists port: 5223,443, so both want the same socket. Give 443 to one of them: set port: 5223 for the SMP server, or move the file relay to a second IP address or a second VPS. Also size the storage quota to disk you really have, because the file relay is the component that consumes disk and bandwidth.

#simplex#privacy#messaging#self-hosting#vps