SimpleX Chat: a private messenger and your own server

SimpleX Chat: a private messenger with no account and your own SMP server Docker
SimpleX Chat is a messenger with no account and no phone number. How SMP queues work, how it differs from Matrix, and how to run your own message and file servers on a VPS.

SimpleX Chat is a messenger with no account. There is no phone number, no email, and not even a random user ID the server could use to build a contact list. The conversation lives on the participants’ phones. The server only delivers an already encrypted message to the recipient’s queue and drops it once the recipient has collected it.

Below is how that works, how it differs from Matrix and from an ordinary messenger with an account, which apps to install, and how to run your own delivery server (SMP) and file server (XFTP) on a small VPS. A done-for-you setup is described separately: a private SimpleX Chat messenger on your own VPS.

What SimpleX Chat is

SimpleX Chat is an open messenger for Android, iPhone and desktop. The project states the goal directly: a network without user identifiers. A contact, a group and the history live in the app database on the device. Network servers are relays: they accept a packet and hand it to whoever knows the queue address.

Messages are encrypted on the devices. The protocol description uses Double Ratchet plus an extra encryption layer, so compromising one key should not open the whole past and future conversation. The project also says it added post-quantum protection; the details are on the FAQ and in their note on encryption. The server sees ciphertext, not the message text.

Your own server is optional. The mobile apps already list the simplex.im relays. You want your own SMP when the queue “send to me here” should sit on a machine you administer. The other person can still receive messages on any other SimpleX server, including a public one. The network is built that way: clients on different servers talk to each other.

How chat works without an account

An ordinary messenger has a user. The server knows that this user exists, who is in their contacts and which chats they belong to. SimpleX has no such object. There is a message queue on a relay. The queue address looks like an smp:// link: a server certificate fingerprint, an optional password for creating queues, and the server name or IP.

The recipient chooses which SMP server holds their queue for this contact. The sender does not open an account with you: their app places an encrypted packet at the queue address it already knows. Your app periodically collects packets from its queues. Once a packet is on the device, the relay no longer needs it.

A queue can be moved. A contact card or a group member has a control to change the receiving address. That matters: if you set your own server in the app settings, new contacts will use it. Existing ones stay on the previous relays until you move them by hand. That is how the SMP server documentation describes it.

A new connection starts with a one-time link or a QR code. The link carries public keys and can be used once. You can pass it through any channel you already have. If you want a stable address so many people can write to you, the profile can create a SimpleX address: that is not a one-time link. Incognito mode for a new contact substitutes a random name and does not show your usual profile.

SimpleX, Matrix and a regular messenger

The three designs promise different things. They are easy to mix up: “your own server” in Matrix and “your own server” in SimpleX are not the same thing.

  • A regular messenger stores the account, the contact list and often the history itself. A phone number or email is the identifier people use to find you.
  • Matrix also gives you your own server, but that server has accounts, rooms and message history. That is convenient for a team and for several devices. The server knows who is in the rooms.
  • SimpleX does not store the conversation as a mailbox. Undelivered packets sit on the relay for a while. There is no “user on the server” profile. There is also no familiar sync of one account across a phone, a tablet and two computers.

SimpleX does link a phone and a computer, but that is not a cloud account. The phone profile opens on the computer when both devices are on the same local network. The project deliberately does not put the same database on several full phones: their write-up of multi-device messengers with Double Ratchet says that setup almost always breaks some encryption property. The current official position is to link the phone and the computer on one network, or to keep separate profiles.

There are no read receipts (“read at 14:41”) in SimpleX, and the FAQ says they are not planned even as an option. One check mark means the relay accepted the packet. Two mean it reached the other person’s device. The other person can turn off delivery notifications.

What the server still sees

The sentence “the server does not know who you talk to” is easy to read as stronger than it is. The relay does not receive the message text or the contact name. It has no user directory. The machine the packets pass through does see that traffic exists.

  • The relay of your queue sees that the queue is alive, when messages are collected from it, and roughly how much. It does not see which queue on someone else’s server sent the message.
  • If your app talks to the other person’s server directly, that server sees your IP. From the protocol version that includes private routing, the client can hand the packet to a relay it already knows, and that relay forwards it. The destination server then does not see your address. The project FAQ describes this. It is enabled in the app network settings and needs the servers along the path to support it.
  • The server operator and the data center see encrypted SimpleX traffic to and from the server IP. That traffic does not assemble into the conversation.
  • Push notifications to the phone go through simplex.im infrastructure by default. The app can switch delivery to periodic polling: the client then asks your SMP itself and does not depend on their push server. Polling uses more battery than push.

Asked to “hand over a user’s conversation”, the operator of your SMP has almost nothing to give: there is no account and no chat archive. Undelivered packets that have not expired are still on disk. That is ciphertext of particular queues, not a chat log. The project policy is to store as little metadata as possible; they publish transparency reports on their site.

Apps

Install the client from the simplex.chat downloads page. The current links are collected there, and they change less often than mirrors.

The app database is encrypted with a passphrase you choose. The developers cannot recover it. If the phrase is lost, the profile, contacts and messages on that device cannot be opened. Move to a new phone with the built-in SimpleX transfer, not by copying the app folder through a phone cloud backup: the project says that kind of transfer breaks the database.

Your own server: SMP and XFTP

Chat needs an SMP server. That is the queue relay. The Docker image is simplexchat/smp-server, and the source is in the simplexmq repository. The protocol runs over its own TLS. In the manual Docker layout the public port is 5223. The automatic layout also starts Caddy: the server page and the protocol are then available on 443 as well, and 5223 stays as a fallback. The automatic layout needs ports 80 and 443 free.

Files, photos and voice messages use a separate protocol, XFTP. A file is split into fixed-size chunks, the chunks are encrypted, and they can travel through different relays. The recipient assembles the file locally. The sender does not have to stay online while the file is downloaded: the relay keeps it for a limited time. The XFTP documentation currently says 48 hours, or until the sender deletes it. The image is simplexchat/xftp-server. The guide is Hosting your own XFTP Server.

On one VPS the two services must not share the same public port. A convenient pair from the official examples is SMP in manual mode on 5223 and XFTP on 443. If SMP was installed automatically with Caddy, 443 is already taken, and XFTP has to move to another address or another public port. The steps below are the first pair: 5223 for messages and 443 for files.

Calls are WebRTC, not SMP. By default the app uses stun.simplex.im and turn.simplex.im. Your own TURN is for the case when call media should also pass through your machine. How to run it is in “Your own call server”. Chat and files do not need it.

A server password, if you set one, only protects creating new queues or uploading new files. Sending into a queue that already exists does not need that password. The password becomes part of an address like smp://fingerprint:password@server, and the people you allow to create queues on your server have it. People who only write to you do not need it.

Domain and VPS

A domain is optional. In ADDR for SMP and XFTP, in --realm for TURN, and in the lines you paste into the app, put the server’s public IP, for example 203.0.113.10. DNS records are not required in that case. A domain is more convenient if the IP changes later: the name stays in the certificate and in the address already saved in the app. The SMP documentation also says clients will use the server name in invitation links. A server with no name and no page makes those links fall back to the simplex: scheme, which a browser will not open. Queues at smp://fingerprint@203.0.113.10:5223 still work.

A domain is easy to register at Timeweb, and the machine itself can be a small VPS at Timeweb Cloud. A personal SMP and XFTP with a small file store is fine on 1 vCPU, 1 GB of memory and 10–20 GB of disk. The XFTP quota is set separately and must not exceed the disk.

The system is Ubuntu LTS. The project tests the official systemd install script on Ubuntu. The Docker image is less tied to that note, and that is what the steps below use. Install Docker Engine and the Compose plugin from the Docker documentation: engine install and Compose for Linux.

DNS records

If ADDR is an IP, do not create records. For a domain, create two A records pointing at the VPS IPv4 address. If you have IPv6, add an AAAA for each name.

  • smp.example.com is SMP, port 5223.
  • files.example.com is XFTP, port 443.

The names can be your own. Until the record is visible from outside, the client will not open the server address. Check it with dig smp.example.com A. Manual SMP does not need a web server or port 80: the server issues its protocol certificate itself during initialization.

Installing SMP

This is the manual option from the documentation: no Caddy, protocol on port 5223. The full automatic option with a Let’s Encrypt certificate and a server page on 443 is in Hosting your own SMP Server, section Docker: Automatic setup. Use that if 443 is free and you want the server page. XFTP will not also fit on 443 of the same VPS.

Open the ports. Do not close SSH, or you will lose the server.

ufw allow OpenSSH
ufw allow 5223/tcp
ufw allow 443/tcp
ufw enable

Create the directory and docker-compose.yml. In ADDR write the domain or the server’s public IP. Without a domain that is the IP, the same 203.0.113.10, and you skip the DNS section.

mkdir -p ~/simplex/smp && cd ~/simplex/smp
name: SimpleX Chat - smp-server

services:
  smp-server:
    image: simplexchat/smp-server:latest
    environment:
      WEB_MANUAL: "1"
      ADDR: smp.example.com
    volumes:
      - ./smp_configs:/etc/opt/simplex
      - ./smp_state:/var/opt/simplex
    ports:
      - 5223:5223
    restart: unless-stopped

Start it in the background with docker compose up -d. In the logs (docker compose logs) the server prints the fingerprint and an address like smp://fingerprint@smp.example.com. Copy the whole address. Nearby is the path to the CA key, usually /etc/opt/simplex/ca.key inside the container, which on the host is a file in smp_configs. The CA key lets you issue a new server certificate without changing the server identity or breaking queues that already exist. Copy it somewhere safe and delete it from the server. If the key stays next to the working certificate, a copy of the VPS disk can sign a new certificate in your name.

The image usually enables the queue journal during initialization. Without it, queues and undelivered messages disappear when the container restarts. In the configuration example from the documentation, undelivered messages are kept for 21 days (expire_messages_days). That is not a chat archive. A packet that has been delivered to the client does not remain on the server as history.

The server source is AGPL. If you change the code and let other people use that server, you have to offer them the changes. The source_code field in smp-server.ini exists for that. An unmodified image points at the project repository.

Installing XFTP

Use a separate directory and port 443. In ADDR put files.example.com or the same public IP as SMP. QUOTA is the storage ceiling. Set it below the free disk. A password for uploading new files is optional: the documentation sets it in file-server.ini, section AUTH, parameter create_password.

mkdir -p ~/simplex/xftp && cd ~/simplex/xftp
name: SimpleX Chat - xftp-server

services:
  xftp-server:
    image: simplexchat/xftp-server:latest
    environment:
      ADDR: files.example.com
      QUOTA: 20gb
    volumes:
      - ./xftp_configs:/etc/opt/simplex-xftp
      - ./xftp_state:/var/opt/simplex-xftp
      - ./xftp_files:/srv/xftp
    ports:
      - 443:443
    restart: unless-stopped

Again docker compose up -d, and take the xftp://fingerprint@files.example.com address from the log. Keep the file server CA key the same way as the SMP key: a copy off the server, and remove it from the VPS disk.

The official systemd script installs both SMP and XFTP as one program and checks its own checksum. The command and the current hash are on the SMP and XFTP pages. A hash copied into someone else’s article goes stale, so it is not repeated here: compare the line on simplex.chat on the day you install.

Server address in the app

In the app: Settings, Network & servers, the SMP list and a separate XFTP list. Paste the addresses the container printed, including the fingerprint. The fingerprint is what stops a substituted server: the client checks the certificate against the one written in the link.

For a more stable connection open Settings, Network & servers, Network settings, and set Ping interval to 120 seconds. The default there is 1200 seconds, and with that gap the connection freezes more often.

After you save, new one-time links and new contacts can receive messages through your SMP. Old conversations stay on the previous servers until you change the receiving address on the contact card. It is worth messaging a test contact from a second device first and checking that both check marks arrive.

Port 5223 is sometimes closed on guest Wi-Fi and on office networks. If a test message fails on that Wi-Fi and succeeds from your home network, the port filter is the cause, not the encryption. The setup service therefore publishes the server on ports those networks usually leave open. On a home or mobile network the standard 5223 is enough.

Invitations, groups and calls

A new chat starts with the button that creates a one-time link. Send it to the person or show the QR next to them. They paste the link into the app search, or open it once the app is installed. The connection is established once both apps have been online at the same time at least once. After that you can write in turn. You do not have to be online together.

A SimpleX group is not a room on a server with an archive. The members’ clients know who is in the group. The server still sees queues, not “the sales chat”. Group roles are set in the app. You can delete a message on the other person’s side only if both of you enabled “delete for everyone” and you are within a day of sending. There are few reactions. A reply to a specific message exists.

Voice and video calls go through WebRTC. During a call the app shows whether the media is end-to-end encrypted. On an old Android it may not be, if the system WebView cannot encrypt the stream: updating WebView usually fixes that. If the call never connects, the FAQ suggests checking that stun.simplex.im and turn.simplex.im are reachable. Until you replace them with your own TURN, the call depends on those hosts even when messages already go through your SMP.

A picture may not open at once. Until the file has finished downloading, the preview has no mark of a complete download. If the recipient does not collect the file within two days, XFTP will no longer serve it.

Your own call server

Voice and video in SimpleX go through WebRTC. While the app still uses the simplex.im servers, call media depends on them, even if messages already go through your SMP. Your own STUN/TURN is a separate piece, the coturn program. Chat and files do not need it.

The TURN password must not contain characters that break a turn: link. This command leaves 24 random characters with no slash, plus or equals sign:

openssl rand -base64 24 | tr -d '/+=' | cut -c1-24

Keep that string. In the examples below it is written as PASSWORD. The username is simplex. The name or IP clients use to see the server is turn.example.com. That can be a separate A record on the same VPS, or the IP itself. The signaling port is 3478, both TCP and UDP. The audio itself uses a wide UDP range, 49152–65535. Open that too, or the call “connects” and stays silent.

ufw allow 3478/tcp
ufw allow 3478/udp
ufw allow 49152:65535/udp

The coturn container runs on the host network, not behind a separate Docker address. Otherwise the return media ports never reach the phone. Keep the directory separate from SMP and XFTP.

mkdir -p ~/simplex/turn && cd ~/simplex/turn
services:
  coturn:
    image: coturn/coturn:latest
    container_name: simplex-turn
    restart: unless-stopped
    network_mode: host
    command: >
      -n
      --lt-cred-mech
      --fingerprint
      --no-tls
      --no-dtls
      --realm=turn.example.com
      --user=simplex:PASSWORD
      --listening-port=3478
      --min-port=49152
      --max-port=65535
      --log-file=stdout

docker compose up -d. The -n flag means “no config file, everything is on the command line”. --lt-cred-mech turns on login with a username and password. --no-tls and --no-dtls leave TURN itself on port 3478 without its own certificate. WebRTC still encrypts the audio between the phones: during a call the app shows whether end-to-end encryption is on. Coturn sees that a call exists and the participants’ addresses. It does not decrypt the media while that encryption is on.

In the app open Settings, then Privacy & security, then WebRTC ICE servers. Turn off the default servers and add three lines. Substitute your own password and host.

turn:simplex:PASSWORD@turn.example.com:3478?transport=udp
turn:simplex:PASSWORD@turn.example.com:3478?transport=tcp
stun:turn.example.com:3478

The first line is the usual path for a call, over UDP. The second is the same TURN over TCP, if UDP does not reach the server. The third is STUN: the clients learn their external address. After saving, call between two of your own devices, once on the home network and once on mobile. If there is no “in a call” status at all, check 3478. If the status is there and there is no audio, check the 49152–65535/udp range.

Backups and updates

You need copies of both the phone and the server. They are different things.

  • The app: the built-in export or profile transfer, and the database passphrase. Without the phrase the copy is useless. A casual copy of the folder from the phone’s cloud is a bad method. The project warns against it.
  • SMP: the smp_configs and smp_state directories, plus the CA key you took off the server.
  • XFTP: xftp_configs, xftp_state and xftp_files. Files older than two days are no longer useful to the recipient, but unfinished downloads may still be there.

Stop the containers before copying directories (docker compose stop), or the journal on disk can tear. After the copy, docker compose start.

To update an image:

docker compose pull
docker compose up -d

SMP first, then XFTP, each in its own directory. After the update, send yourself a test message from a second device. If a queue does not come back, do not delete smp_state: that is the queue journal. Keep a copy taken before pull within reach while you update.

Common questions

Can you use SimpleX without your own server?

Yes. The app already knows the public relays. Your own server only changes where your receiving queues live, and where files you send through your own XFTP are stored. The other person can stay on the public servers.

Can the hosting provider read message text?

No. The VPS disk holds ciphertext of undelivered packets and XFTP file chunks. Conversation keys never reach the server. The provider can see that the server is yours and that clients connect to it.

Why can’t you sign in with the same account on a second phone?

There is no account. The database is one and it is tied to the device. A second phone is either a profile transfer from the old phone, or a new profile and new one-time links. A computer can open the phone profile temporarily if both are on the same network.

What happens if the VPS is powered off?

New messages will not be accepted into queues on that SMP while the server is off. Conversation that was already delivered stays in the app. Contacts on other servers keep working. When the server comes back, the queue journal restores the queues that were saved. Messages that were not collected and that expired do not come back.

Do you need a domain if you have an IP?

No. In ADDR for SMP and XFTP, in --realm for TURN, and in the addresses for the app, put the server’s public IP instead of a name. DNS is not required. A domain is useful so you do not have to rewrite addresses in the app when the IP changes, and so invitations open as ordinary https links instead of the simplex: scheme, which a browser does not know what to do with.

See also

  • SimpleX Chat set up on a VPS covers SMP, files and calls when you do not want to run the server yourself.
  • Matrix Synapse is your own messenger with accounts, rooms and history on the server.
  • Vaultwarden keeps Bitwarden app passwords on your own server, separate from chat.

Categories: Docker Security Web apps
Subscribe to new posts (RSS)

No email, no trackers — just the update feed.

Also available in Russian

Rate this article
Leave a comment