Zuna is a fully self-hosted, end-to-end encrypted chat and voice service. Run your own server, own your data, and communicate safely with others using a modern and powerful desktop and mobile client apps.
|
Join the Zuna community on Discord Get updates, ask questions, share feedback, and help shape the project. |
Warning
Zuna is currently in early alpha. Core functionality is constantly improving and breaking changes (including full database wipes!) are all but guaranteed. We expect we'll be able to move to a little bit more stable beta releases in a few months. Thank you for your patience and in the meantime - we're happy to accept any contributions that would help speed things up!
- Features
- Screenshots
- Motivation
- Core Philosophy
- Architecture
- Self-Hosting with Zuna Install
- Self-Hosting with Docker
- Contributing
- License
- Fully end-to-end encrypted message, voice and screensharing transport
- Beautiful and modern desktop application
- 7tv integration for animated emotes
- Code mode
- Push notifications
- Encrypted file and photo sharing
- Completely self-hosted through Docker Compose
- Editing/deleting messages
- Pinning messages
- PWA for web and mobile use
- Basic text and voice channels akin to Discord (with screenshare on the way!)
- Mature LiveKit voice/screensharing platform integration (1 on 1, calling)
- Per-app audio/video screenshare for all major platforms (Linux, Windows, MacOS)
- User administrative roles and/or visible ranks
- Complete administration web panel
- Calling (i.e. LiveKit) support in the iOS/Android apps
- True 1-click installation scripts instead of Docker
- Better file encryption/transport protocol allowing for big files over 512MB+
- In-app video attachment streaming
- Voice attachments
Note
π The Zuna mobile app is now available in early access on TestFlight! Join the public beta using the link below. Android support is planned once the iOS version matures.
Zuna was not conceived as just another platform, but as a response to a growing and tangible need. Across both the European Union and the United States, there has been a noticeable shift toward increased regulation of online speech, often justified through broadly appealing but frequently insincere narratives about protecting children online.
While safeguarding minors is a legitimate concern, many of the proposed measures - such as mandatory age verification systems and sweeping initiatives like so-called βchat controlβ - introduce serious risks to privacy, security, and the fundamental right to communicate freely. These policies, if implemented as currently envisioned, could undermine encryption, expose sensitive user data, and create precedents for broader surveillance. Zuna emerges in this context as an attempt to preserve a space where individuals can communicate safely, privately, and without undue interference, resisting a trajectory that increasingly conflates safety with control.
Why not rely on existing, mature open-source end-to-end encrypted chat platforms? In many cases, you absolutely can - and for some users, they are an excellent choice. However, for us (and, we suspect, for many others), these solutions fell short in key areas such as simplicity, user experience, and feature design. Too often, they either prioritize technical robustness at the expense of usability or replicate familiar patterns without rethinking them.
Zuna is not intended - at least at this stage - to compete directly with established platforms such as Signal or Matrix in terms of protocol maturity, formal verification, or encryption rigor. This is, in part, a deliberate choice rather than a shortcoming. From the outset, our guiding principle has been to strike a careful balance between security and usability.
Many secure communication tools achieve strong guarantees but at the cost of added complexity, friction, or user burden. Zuna takes a different approach: we aim to provide a high baseline of meaningful, practical security while minimizing the need for constant user intervention or technical understanding. Instead of expecting users to actively manage intricate settings or workflows, we focus on delivering essential protections by default, in a way that feels seamless and unobtrusive.
Self-hosting fundamentally changes the threat model, and Zuna is designed to take advantage of that. By allowing users to operate their own infrastructure - or rely on instances they trust - we can reduce the need for certain defensive measures that are essential in large, centralized platforms. In those environments, providers must assume adversarial conditions at scale, often leading to complex safeguards that introduce friction or limit usability.
Our objective is not to compromise on security, but to make it more accessible - ensuring that users benefit from robust protections without being overwhelmed or discouraged from using them altogether.
By default, running a Zuna server is intentionally straightforward: a single Linux instance and a supported database - such as SQLite, MariaDB, or PostgreSQL - are sufficient to get started. This simplicity, however, assumes the use of our cloud gateway relay (gateway.zuna.chat). That component handles certain external integrations, most notably enabling push notifications for PWA and mobile.
Due to Push Notification restrictions, delivering push notifications on PWAs or mobile devices requires integration with our third-party infrastructure. As a result, using Zuna PWA or Zuna Mobile applications in its standard form depends on our gateway relay. While this introduces a limited external dependency, it allows most users to deploy and operate Zuna with minimal setup complexity.
Our cloud gateway is designed to be fully stateless, keep absolutely NO DATA or telemetry about you or your server and is fully auditable through GitHub by design.
For those who prefer a fully independent setup, it is possible to replace our gateway relay with your own. This involves configuring a custom relay service and distributing a properly modified version of the PWA web application and/or mobile builds through TestFlight or Google Testing platform. This approach offers greater control and removes reliance on our infrastructure, but it is significantly more complex and requires custom app builds for your server specifically. This process is currently undocumented as it may change in the future and we generally don't recommend this approach for UX reasons. Of course, if you choose to only allow desktop applications to connect with your server then this process becomes significantly easier since desktop apps don't have strict platform limitations when it comes to showing notifications.
TBD.
Zuna Install will be a 1-click solution to setup, configure and install Zuna server with all required dependencies on a fresh Linux server installation. For now, Docker is your next best option.
The easiest way to run a Zuna server is with Docker Compose. The provided setup bundles zuna-server, MariaDB 11, and LiveKit (for voice calls and screen sharing) into a single stack.
- Docker 24+ and Docker Compose v2
- Ports
25510,7880,7881, and UDP50000-50100range open in your firewall (default compose setup)
# 1. Clone the repository
git clone https://github.com/zuna-app/zuna.git
cd zuna
# 2. Create a .env file with your own secrets
cp .env.example .env # then edit .env!
# 3. Build and start all services
docker compose up -d --buildOn first boot, zuna-server automatically:
- Writes
/data/config.tomlinto thezuna-datavolume - Generates and persists
server_idin[server] - Generates an Ed25519 server keypair
- Generates self-signed TLS files (when
TLS_AUTO_GENERATE=true)
The server is ready when you see zuna-server log starting server.
All values below can be set in .env at repository root. Compose reads .env and injects values into container environment according to docker-compose.yml.
The defaults are for local development. Change secrets before any public deployment.
| Variable | Default | Description |
|---|---|---|
MYSQL_ROOT_PASSWORD |
changeme |
MariaDB root password |
MYSQL_DATABASE |
zuna |
Database name |
MYSQL_USER |
zuna |
Application database user |
MYSQL_PASSWORD |
zunapass |
Application database user password |
| Variable | Default | Description |
|---|---|---|
LIVEKIT_API_KEY |
zunakey |
Must match between livekit-init and zuna-server |
LIVEKIT_API_SECRET |
zunaS3cr3tChangeme123 |
Keep private |
LIVEKIT_HOST |
1.2.3.4 |
LiveKit Host |
| Variable | Default | Description |
|---|---|---|
TLS_AUTO_GENERATE |
true |
Whether the server auto-generates self-signed TLS cert/key |
# MariaDB
# Root password for the zuna-mysql container (not used by the app directly).
MYSQL_ROOT_PASSWORD=changeme
# Database name, app user and password - must match across all services.
MYSQL_DATABASE=zuna
MYSQL_USER=zuna
MYSQL_PASSWORD=zunapass
# LiveKit
# API key and secret shared between the LiveKit server and zuna-server.
# Use a long random string for the secret in production. (at least 32 characters)
LIVEKIT_API_KEY=lk_api_key
LIVEKIT_API_SECRET=zunaS3cr3tChangeme123
# LIVEKIT_HOST and LIVEKIT_PORT specify the public address where the LiveKit server is reachable by Zuna and clients.
LIVEKIT_HOST=1.2.3.4
LIVEKIT_PORT=7880
# TLS Configuration
# Important: Change TLS_PUBLIC_ADDRESS to your server's public IP or domain name
# even if auto-generating TLS certificates, otherwise the generated certificates will not be valid for your server's address.
TLS_PUBLIC_ADDRESS=
# For production, set TLS_AUTO_GENERATE to false and provide your own TLS certificates.
TLS_AUTO_GENERATE=trueAfter first start, config lives in the named volume zuna-data at /data/config.toml.
Inspect it with:
docker run --rm -v zuna_zuna-data:/data alpine cat /data/config.tomlCommon keys to change:
| Key | Description |
|---|---|
[server].name |
Public display name of your server |
[server].password |
Optional join password - set it to restrict who can register |
[server].logo |
Path to a PNG/GIF logo file (mount a custom one into zuna-data) |
[tls].auto_generate |
true generates self-signed certs; set false and provide your own cert/key |
[tls].public_address |
Public IP or hostname used by TLS generation and clients |
[gateway].address |
Address of the push-notification gateway (defaults to gateway.zuna.chat) |
[sevenTv].enabled |
Enable/disable 7TV animated emotes |
[livekit].enabled |
Toggled automatically based on whether LIVEKIT_API_KEY is set |
Note
config.toml is created once. Changing .env later does not fully regenerate server config. Edit /data/config.toml directly (or recreate the volume).
All exposed ports are configured in docker-compose.yml using the HOST:CONTAINER format. The container-side port must match what the service is actually listening on; only the host-side number needs to change.
| Service | Variable | Default host port | What it does |
|---|---|---|---|
zuna-server |
- | 25510 |
Zuna REST + WebSocket API |
livekit |
- | 7880 |
LiveKit HTTP / WebSocket API |
livekit |
- | 7881 |
LiveKit RTC TCP |
livekit |
- | 50000-50100/udp |
WebRTC media (range UDP) |
Example - run zuna-server on port 9090 instead of 25510:
# docker-compose.yml
zuna-server:
ports:
- "9090:25510" # host:containerExample - run LiveKit API on port 8888:
# docker-compose.yml
livekit:
ports:
- "8888:7880" # host:container
- "7881:7881"
- "50000-50100:50000-50100/udp"Important
If you change the LiveKit API port on the host side you do not need to update [livekit].port in config.toml - that value refers to the port inside the Docker network, which is always 7880. Only change it if you also change the container-side port mapping (i.e. "XXXX:XXXX" where both numbers differ).
For a production deployment you should:
- Set a strong
[server].passwordinconfig.tomlto prevent open registration - Set
[tls].auto_generate = falseand provide real certificates (e.g. via Let's Encrypt) - Set
[tls].public_addressto your domain or public IP - In
docker-compose.yml, update thelivekit.yamlblock insidelivekit-initto setuse_external_ip: trueif LiveKit is behind NAT - Ensure your firewall exposes UDP media ports configured for LiveKit (default here:
50000/udp)
x509: cannot parse IP address of length 0Setpublic_addressinconfig.tomlto a valid IP/domain and restart.
Contributions are very welcome since Zuna is currently maintained by just two devs. We're doing what we can to provide the best possible open messaging platform that is easy to understand, extend and use, but we can't do it all alone. Please open an issue before submitting a pull request for non-trivial changes so the approach can be discussed first.
Zuna is released under the GNU Affero General Public License v3.0 (AGPL-3.0).
This means you are free to use, modify, and distribute Zuna, but any modified version that is made available over a network must also be distributed under the same license with its source code made available. See the LICENSE file for the full text.
TL;DR - run it, fork it, improve it. Just keep it open.







