Unlimited Privacy: Create Your Own Encrypted Chat with PidginChat and OMEMO (In Just 5 Minutes!)
Your own alternative to Signal without the need of a phone number or linked device limits. Full control from GUI or Terminal.
In an era where almost every message we send passes through some company’s servers, Signal has become the gold standard for private communication. But… What is Signal and what is it used for around the world? Well, Signal is a secure messaging app (free and open-source) focused on end-to-end (E2E) encryption. It is widely used by journalists, activists, technical teams, and anyone who wants to protect their conversations from providers, operators, and attackers on public networks. It offers encrypted calls and messages, security number verification between contacts, and a clean design that popularized end-to-end encrypted messaging on a global scale.
Signal has earned its reputation by merit: It democratized E2E encryption for messages with a clean interface, the transparency of open-source code, and the Double Ratchet algorithm that forever changed secure messaging.
But even Signal has its limitations, some of them are:
Limit of 5 linked devices (which could be particularly frustrating for multi-device users)
Centralized servers: You depend completely on Signal’s infrastructure.
Identity based on phone number, which complicates anonymity
For me, the first point was the deciding factor. I needed something that could:
Be used on as many devices as I wanted/needed
Operate from a text interface (for example, on servers)
And still maintain encryption at Signal’s level
That’s when I rediscovered the XMPP protocol, specifically through PidginChat + OMEMO, which motivated me to write this article about my setup and how to replicate it, plus some variants and other useful information.
Without further ado, let’s get to it!
🧩 Under the Hood: XMPP (feat. PidginChat) + OMEMO
XMPP: The Backbone of Open Messaging
XMPP (Extensible Messaging and Presence Protocol) is an open standard for messaging: the same protocol that powered Google Talk and Facebook Chat in their early days… It’s federated just like email: anyone can host a server, and servers communicate with each other.
OMEMO: Signal-Level Security, Federated
OMEMO is an encryption layer for XMPP that implements the same Double Ratchet algorithm used by Signal. It offers support for offline messages, multi-device synchronization, and forward secrecy.
What is forward secrecy? Also known as perfect forward secrecy, it’s basically a cryptographic implementation that means if a long-term encryption key were to be compromised today, your past messages would still be protected; OMEMO/Double Ratchet constantly rotates keys and discards the old ones; each message (or batch of messages) is protected with fresh and ephemeral cryptographic material.
In other words: OMEMO enables us the same gold-standard cryptography as Signal, without needing to depend on (nor be on) Signal’s server.
💜 Pidgin & PidginChat
Pidgin is one of the oldest and most respected open-source chat clients. It was born in the early 2000s as Gaim, and evolved into what we know today as a multi-protocol application powered by libpurple, a plugin-based library that supports dozens of messaging protocols (IRC, XMPP, ICQ, and more). For many GNU/Linux users, Pidgin was (and remains) essential for talking to people who have a presence on different messaging networks (proprietary and open alike).
PidginChat, on the other hand, is a modern XMPP service hosted and managed by the community that maintains Pidgin. It uses a lightweight XMPP server that anyone can join, making it perfect if you want a ready-to-use network in a configuration scoped to be similar to Signal after setting up the proper encryption needed (without having to set up your own server from the start)
Quick Registration on PidginChat
Short and concise: Go to https://pidgin.im/about/pidginchat/ and follow the instructions.
Elaborating:
Basically, you’ll need to create an account on their JetBrains Hub instance (https://hub.imfreedom.org/) by logging in with your preferred method; that will automatically create your PidginChat account (you can change your assigned username later right there in their web interface)
Note your XMPP address (JID): it looks like username@pidginchat.com where username refers to the username assigned in the JetBrains Hub instance.
Use it to log in with any XMPP client: I’ll recommend you some below.
Keep in mind: You can register with a disposable email and/or a pseudonym. You can also anonymize your JID by deleting metadata in the JetBrains Hub panel (after registration) or, if you prefer, you can later migrate to your own server as mentioned above.
🖥️ XMPP Clients: Choose Your Platform
There’s no one-size-fits-all: Choose the one that best fits your workflow:
My Particular Case: Secure Messaging Between Servers
I started researching this topic because I wanted to find an option to establish an E2E encrypted chat via command line between servers on my VPN/LAN. No graphical interface: just the terminal.
That led me to find Profanity, a TUI (terminal user interface) XMPP client that supports OMEMO once you compile it correctly with the proper plugin set. We’ll deviate a bit into this tangent (feel free to skip this section if you wish) and here I’ll write up the guide on how exactly I made it work with Homebrew libraries on AlmaLinux 10; a GNU/Linux distribution compatible with Red Hat Enterprise Linux (RHEL) 10.x and other derivatives of it like RockyLinux, for example.
Let’s see…
How to Install Profanity with OMEMO Support on AlmaLinux 10 (and other RHEL-compatible distros)

Profanity is a fast and minimalist XMPP client with support for OMEMO, OTR, and PGP. Compiling it with all dependencies on a modern RHEL-like distro (AlmaLinux 10 as an example) can be tricky, which is why I’m documenting a reproducible process here as a reference for posterity.
NOTE: The commands you’ll see below are meant to be executed in a bash shell.
Step 1: Prepare the Base System
Install development tools and kernel headers:
sudo dnf -y groupinstall "Development Tools"
sudo dnf -y install glibc-devel glibc-headers libstdc++-devel binutils kernel-headers pkgconf-pkg-configThis ensures we can create executables with /usr/bin/gcc (otherwise the configure step might fail with a message like: “C compiler cannot create executables”).
Step 2: Install Linuxbrew / Homebrew (if you don’t have it installed already)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"The script explains what it will do and pauses before doing it. After installation, add brew to your PATH (the same STDOUT after installation will give you the instructions on how to do so) and install gcc from its repositories with:
brew install gccStep 3: Install Dependencies via Linuxbrew
brew install autoconf automake libtool pkgconf autoconf-archive readline ncurses glib curl libxml2 openssl@3 sqlite libstrophe libsignal-protocol-c libgcrypt libgpg-errorThis brings the libraries that Profanity needs for OMEMO and more (ncurses, cryptography, XMPP, etc.).
Step 4: Ensure pkg-config Finds GLib
Some installations need explicit hints for GLib’s .pc files:
export PKG_CONFIG_PATH="$(brew --prefix)/lib/pkgconfig:$(brew --prefix)/share/pkgconfig:$(brew --cellar glib)/*/lib/pkgconfig:$(brew --cellar glib)/*/share/pkgconfig:${PKG_CONFIG_PATH:-}"Test:
pkg-config --modversion glib-2.0Expected: 2.xx.x
If you see “not found”:
brew reinstall pkgconfStep 5: Prepare Build Flags with brew paths
RLB="$(brew --prefix readline)"
NCB="$(brew --prefix ncurses)"
GCR="$(brew --prefix libgcrypt)"
GGE="$(brew --prefix libgpg-error)"
SIG="$(brew --prefix libsignal-protocol-c)"
GLB="$(brew --prefix glib)"
HB="$(brew --prefix)"
export CC=/usr/bin/gcc
export CXX=/usr/bin/g++
unset CFLAGS CXXFLAGS LIBRARY_PATH CPATH
export CPPFLAGS="-I$RLB/include -I$NCB/include -I$GCR/include -I$GGE/include -I$SIG/include -I$GLB/include/glib-2.0 -I$GLB/lib/glib-2.0/include"
export LDFLAGS="-L$RLB/lib -L$NCB/lib -L$GCR/lib -L$GGE/lib -L$SIG/lib -L$GLB/lib -Wl,-rpath,$HB/lib -Wl,-rpath,$NCB/lib -Wl,-rpath,$GCR/lib -Wl,-rpath,$GGE/lib -Wl,-rpath,$RLB/lib -Wl,-rpath,$SIG/lib -Wl,-rpath,$GLB/lib"This helps the configure step find:
libsignal-protocol-c
libgcrypt + libgpg-error
readline + ncurses
GLib
Step 6: Get the Profanity Source Code
cd ~
[ -d profanity ] && (cd profanity && git fetch -p && git reset --hard origin/master) || git clone https://github.com/profanity-im/profanity
cd profanity
git clean -xfd
rm -f config.cacheStep 7: Initialize the Build System
./bootstrap.shThis generates configure and other files via autotools.
Step 8: Configure with OMEMO Support
Add these extra flags to try to avoid a common cURL warning that seems to be affecting the profanity build right now (it might be fixed later by the community):
LIBS="$(pkg-config --libs libsignal-protocol-c) -lgpg-error -lgcrypt -lncursesw -lreadline"
CFLAGS="-Wno-error -Wno-error=attribute-warning"
CXXFLAGS="-Wno-error -Wno-error=attribute-warning"
./configure --enable-omemo --prefix="$HOME/.local"Step 9: Compile
make -j"$(nproc)"Did you get a cURL error like curl_easy_setopt expects a long argument? Apply this small patch and recompile:
sed -i 's/\(CURLOPT_TIMEOUT,\s*\)2)/\12L)/' src/common.c
make clean
make -j"$(nproc)"Step 10: Install and Validate
If there were no more errors, we can install profanity at user level and validate at the same time with:
make install
~/.local/bin/profanity -vExpected:
Profanity 0.15.x
Features: OMEMO, libsignal-protocol-c, ncurses, readline
Done! Profanity is installed! Start it with:
~/.local/bin/profanityCommon Troubleshooting
I’ll summarize it in a table for you below:
Back to the main topic…
🧭 (Optional) Host Your Own XMPP Server
It’s possible (and often recommended) to host your own XMPP server (for example, using prosody or ejabberd); By doing this, you control the metadata, retention policies, and physical location of the service, as well as its availability, access, rules, and features. In a later tutorial, we’ll explore the step-by-step setup of a self-hosted XMPP server for those curious about the topic.
🔐 So… How Secure Is This?
Let’s be clear and transparent:
The cryptography between both options is equivalent, but real-world privacy depends on where (and how) your server is hosted. If you self-host or use a trusted provider, you get a security level very close to Signal’s without centralization.
Some additional (and somewhat basic) privacy tips
Register with a pseudonym; Take advantage that unlike Signal, for example in this case PidginChat does not require a phone number.
Delete the vCard data generated in the JetBrains Hub instance (if you choose to set up with PidginChat) and/or change your JID later.
Consider using a VPN when registering on the hub for greater privacy.
If your client allows it, force TLS and configure it to use port 443 instead of the default port (I think it’s 5222) to connect. Note, this does not interfere with other processes running on your client device.
Addendum: OMEMO Fingerprint Verification
Like Signal, OMEMO uses fingerprints per device. To avoid man-in-the-middle attacks, verify the fingerprints with your contacts. In almost any XMPP client, you’ll find OMEMO details in the contact’s information. Compare those fingerprints out-of-band (i.e., through a trusted channel different from the messenger, like a phone call or exchanging a QR code); Once verified, manually mark the device as trusted.
That said, for practicality, you can allow blind trust between devices (many XMPP clients do this by default, so you don’t have to manually verify keys before starting to chat to avoid friction), but anyhow, it’s always good to review their cryptographic fingerprints in one way or another.
Similarly, it’s very common for XMPP clients to have an icon or indication (usually a padlock icon in graphical clients, or the OMEMO status in text clients) within the conversation window to signal (pun not intended) whether that conversation is being end-to-end encrypted or not; So… always check the user interface: Verify that the indicator (graphical or not) shows that encryption is enabled for the conversation.
As long as encryption is enabled, messages are E2E protected and authenticated.
🧐 Closing thoughts
I’m not here to lightly claim that this setup is more secure than Signal; Signal is still unbeatable for its simplicity and network effect (many people use it); But setting up XMPP + OMEMO is a different kind of security: a scenario where you can own the infrastructure (your own server) and this brings freedoms like:
Chat privately within your own network (or even with yourself just to pass messages between devices)
Depending on the platform, integrate with automation scripts
Connect any number of devices
Run a chat system that never sends telemetry to third parties
Signal gave us easy-to-use encrypted messaging. XMPP + OMEMO gives us sovereign encryption: The ability to control, host, and understand everything that comprises our communications stack. You can start easily with PidginChat and evolve to a self-hosted service later.
This setup represents privacy by design and autonomy by architecture.
🛠️ Mentioned Tools
PidginChat: https://pidgin.im/about/pidginchat
Gajim: https://gajim.org
Monal: https://monal-im.org
Converse.js: https://conversejs.org
Conversations: https://conversations.im
Profanity: https://profanity-im.github.io
Homebrew: https://brew.sh
Until next time! Don’t forget to subscribe and share.







