Skip to main content

End-to-End Encrypted Messaging on WordPress

· 8 min read
Creator of Better Messages

Most WordPress messaging plugins store every message in plaintext in the database. Anyone with access to the database — a site admin, a host's support engineer, an attacker who exfiltrates a database backup — can read every conversation. For most sites that is acceptable. For some sites — therapists with clients, lawyers with opposing parties, journalists with sources, founders discussing acquisition terms — it is not. Better Messages 2.13 introduced optional per-thread end-to-end encryption: messages are encrypted in the sender's browser, stay encrypted at rest, and are decrypted in the recipient's browser. The database holds ciphertext only.

This post covers how the encryption works, what stays encrypted versus what does not, the trade-offs, and how to enable it.

What end-to-end means here#

End-to-end encryption means the encryption keys never leave the participants' browsers. The server stores ciphertext and public keys. It has no way to decrypt messages.

Concretely for a Better Messages thread with E2E enabled:

  • The first time a member uses encryption they set an encryption password — a separate secret, not their WordPress password. Their browser generates a keypair from it: the public key goes to the server as-is, and the private key is encrypted with a key derived from that password (PBKDF2) before a copy is uploaded too. The server holds the encrypted blob and cannot open it.
  • A symmetric thread key is generated for the thread, encrypted to every participant's public key, and stored — once per participant — in the database.
  • Each new message is encrypted with the thread key in the sender's browser before it is sent to the server.
  • Every recipient's browser decrypts the thread key with their private key, then decrypts each message with the thread key.

The database stores: ciphertext message bodies, encrypted per-participant copies of the thread key, public keys, and each member's password-wrapped private key. The database does not store: any usable private key, any encryption password, any cleartext message body for an encrypted thread.

That password-wrapped copy is what makes a second device work — sign in elsewhere, enter the encryption password, and the key is restored locally. It is also the one thing that cannot be reset for you, which the next section gets to.

What stays encrypted#

DataEncrypted at rest
Message body textyes
File attachments and voice messages uploaded inside the encrypted threadyes
Replies, edits, mentions (they are message bodies)yes
Reactionsno — the emoji and who picked it are stored in the clear
Thread participant listno
Thread title / subjectno
Timestampsno
User identitiesno
Read stateno

The trade-off: an admin with database access can still see who talked to whom and when. They cannot see what was said.

What you give up to use E2E#

End-to-end encryption is a trade-off. Some features have to be turned off for an E2E thread because they require the server to read message content:

  • Search — message search is a plain LIKE over the stored bodies, so it matches ciphertext against your search term and finds nothing. The in-conversation search skips encrypted payloads outright. Encrypted messages are simply not searchable, on the server or in the browser.
  • Email notification preview — email notifications for E2E threads carry a generic "new encrypted message" line, not the message body.
  • AI bots in the thread — AI bots are server-side actors. They cannot participate in an E2E thread.
  • Server-side moderation hooks — pre-moderation, bad-words filter, content scanning, and the bot-detection layer all bypass E2E threads.

For most threads on most sites, none of these trade-offs matter. For the threads that need encryption, those compromises are usually exactly what you want.

Per-thread, not per-site#

E2E is enabled per thread, not per site. The same Better Messages installation can host:

  • Public chat rooms (no encryption, full search and moderation)
  • Standard private threads (no encryption, full features)
  • E2E-encrypted private threads (encryption on, server-side features off for those threads)

Encryption is chosen when the conversation is created, not switched on later: the new-conversation screen carries an End-to-end encryption toggle, and whatever it says when the first message goes out is what that thread is, permanently. There is no way to encrypt an existing plain thread, and no way to decrypt an encrypted one — the server never had the cleartext to convert.

The toggle is hidden when a recipient cannot take part: AI chat bots always, and guests unless Allow Guest Users is on.

Enabling E2E on the site#

Encryption is available on the WebSocket version. Two things to configure:

  1. Better Messages → Settings → Messaging → Encryption → Enable End-to-End Encryption — global on / off for the encryption capability. When off, the encryption toggle on individual threads is hidden. The same section also carries Enable Browser Database Encryption, which is a separate thing — see Browser database encryption.
  2. The three switches beneath it decide how it behaves: Enabled by Default pre-checks encryption on new conversations, Force for All Conversations makes it compulsory and cannot be set until the first is on, and Allow Guest Users lets guests take part. AI chat bots are always excluded.

There is no per-role control over who may start an encrypted conversation — the settings are site-wide. Once encryption is on, the conversation's own toggle is what each member uses.

Performance#

Encryption adds CPU work on the sender and receiver, not on the server. For typical text messages on modern devices the overhead is imperceptible. For large attachments the encryption pass is noticeable but still under a second.

The plugin lazy-loads the crypto bundle on the first encrypted thread interaction, so the page-weight cost is only paid by users who actually use the feature.

Who needs this#

E2E is the right tool when:

  • You handle data that has a legal classification (HIPAA-adjacent health info, attorney-client privilege, PII under GDPR Article 9).
  • You handle data whose disclosure would cause direct harm (journalists with sources, security teams with vulnerability reports).
  • You promise users that no one — including you — can read their messages.

E2E is over-engineering when:

  • Your site is a community / marketplace / LMS and the conversations are not unusually sensitive.
  • You want server-side search across all threads.
  • You rely on AI bots, moderation, or bad-words filtering for every thread.

If only some threads in a community fit the first category, that is exactly the right shape: leave E2E disabled by default and let the relevant roles toggle it on per thread.

Frequently asked questions#

Can I recover messages if I lose my private key?#

Clearing browser storage is not the problem — the key is restored from the server copy with your encryption password, which is exactly what happens when you sign in on a new device. Losing the password is the problem, and no one can reset it for you: the server never had it.

If it is gone, Reset encryption generates a fresh keypair under a new password. Threads you were already in stay unreadable until another participant comes online and re-shares that conversation's key with your new public key. In a one-on-one thread with someone who has also lost their password, the history is gone for good. This is the trade-off of true end-to-end encryption.

Does it work with the mobile app?#

Yes. The app restores the key the same way the web messenger does — you are asked for the encryption password once on that device, and it decrypts the server-held copy locally. There is no separate key-transfer step to run.

Is encryption FIPS-validated?#

The browser's Web Crypto API uses NIST-standard primitives (P-256, AES-GCM). It is not FIPS-validated as a cryptographic module. If FIPS validation is a contractual requirement, talk to support before relying on Better Messages E2E.

Can I export an encrypted thread for compliance / discovery?#

Not in readable form. The plugin plugs into WordPress's own Tools → Export Personal Data, which runs on the server — so an encrypted thread comes out as ciphertext. There is no in-messenger "export decrypted thread" button either. If a discovery obligation is foreseeable, that is a reason to leave those threads unencrypted rather than to plan on exporting them later.

What happens if a new member joins an existing encrypted thread?#

When a new participant joins, every existing participant's browser re-encrypts the thread key to the new participant's public key. The new member can read messages sent after their join, not historical ones — unless an existing participant explicitly shares historical content.

Does it work with file uploads?#

Yes — file attachments in an E2E thread are encrypted before upload. The server stores ciphertext. Recipients decrypt the file on download.

See also#

Install Better Messages from WordPress.org →