What the Zulip docs require
The requirements page lists a short set of minimums. Each figure below is the docs' own, with the scope they give it.
| Item | Figure in the docs | Scope |
|---|---|---|
| Operating system | Ubuntu 22.04, 24.04, 26.04; Debian 12, 13 | Supported for production; Docker image for other platforms |
| CPU architecture | x86-64 or aarch64 | All installs |
| Memory | At least 2 GB; swap required under 5 GB (2 GB of swap recommended) | Fewer than 100 users: 1 CPU and 2 GB with swap is sufficient |
| Memory and CPU | 4 GB RAM and 2 CPUs | 100+ users |
| Disk | 10 GB free, roughly a 25 GB disk with the OS | A server with dozens of users; SSD recommended |
| Network | A hostname in DNS; inbound HTTPS; outbound SMTP, usually 587 | All installs |
For larger organizations the scalability section adds RAM by daily active users: 4 GB from 25, 8 GB from 100, 16 GB for the application plus 16 GB for the database from 400, and 32 GB plus 32 GB from 2,000. The docs call this guidance conservative and suggest a separate database host from hundreds of daily actives, though not as a requirement. Database disk is put at 10 GB per million messages plus 1 GB per thousand users, for installs where most messages have fewer than 100 recipients. CPU is rarely the limit, they say, but they recommend against highly CPU-limited machines for organizations with hundreds of users.
Why a whole machine fits the installer
The installer expects to be the only thing on the system. It installs system packages with apt, nginx, PostgreSQL and Redis among them, and configures them for its own use, and the docs say they cannot support an install that shares the box with other services. A dedicated server is that box: the processors, memory and drives of one physical machine belong to one tenant, and that tenant holds root.
- Small team: a Moonshot cartridge with NVMe, such as the Xeon E3-1585L v5 (16 to 64 GB) or the 16-core Xeon D-1587 with up to 128 GB, clears the 100-user line with room for swap and history.
- Hundreds of daily actives: a single- or dual-socket rack server, where memory runs to hundreds of gigabytes and the database has its own disks.
- Rack servers carry an out-of-band console (HPE iLO, Dell iDRAC or a Supermicro BMC), so a bad firewall rule or a failed OS upgrade is recoverable without a ticket. Most cartridges have power control only.
- The port is unmetered, and a racked machine is handed over in about 30 minutes once payment is approved.
The same box can be wiped and reinstalled from the control panel, which is useful for a dress rehearsal: install, restore last night's backup, check it, then go live. The Linux dedicated servers page lists the distributions on offer.
Firewall before the installer
Zulip needs inbound 443. Port 80 is optional in the docs, since Zulip only redirects HTTP to HTTPS, but we leave it open because Let's Encrypt's HTTP check arrives there. Port 4369 (epmd, the Erlang service under RabbitMQ) cannot bind only to localhost, and the docs say a firewall should protect it; a default-deny policy does that. IPv6 is added at checkout at no charge, so switch ufw's IPv6 handling on before the first allow rule: ufw only writes IPv6 copies of rules added while IPV6=yes.
sudo sed -i 's/^IPV6=.*/IPV6=yes/' /etc/default/ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable
sudo ufw status verbose
Add 25/tcp only if you turn on Zulip's incoming email integration. Outbound HTTPS must stay open: Zulip uses it for link previews and the push notification service.
Running the installer
- Point an A record (and an AAAA record if you took IPv6) for the hostname, such as
chat.example.com, at the server. Certbot needs the name to resolve publicly. - On Ubuntu, enable the universe repository. Debian skips this step.
- Download the release tarball into a temporary directory and unpack it. The docs publish sha256sums if you want to verify it.
- Run the installer as root with your flags. It takes a few minutes and is idempotent: fix the cause of a failure and run it again.
# Ubuntu only (skip on Debian):
sudo add-apt-repository universe
sudo apt update
cd $(mktemp -d)
curl -fLO https://download.zulip.com/server/zulip-server-latest.tar.gz
tar -xf zulip-server-latest.tar.gz
sudo ./zulip-server-*/scripts/setup/install --certbot \
--email=it-team@example.com --hostname=chat.example.com
| Flag | What it does |
|---|---|
| A real address for whoever runs the server; users see it as the contact in automated emails and error pages | |
| --hostname | The domain users type; it becomes EXTERNAL_HOST in /etc/zulip/settings.py |
| --certbot | Requests a Let's Encrypt certificate, then installs a cron job that renews it; requires --hostname and --email |
| --push-notifications | Registers for the Mobile Push Notification Service; prompts you to accept its Terms of Service |
| --self-signed-cert | For tests only, or behind a reverse proxy |
The one-time organization link
When the installer finishes it prints a secure, one-time-use organization creation link. Opening it walks you through creating the organization and your own account in it. Our advice: open it in the same session, before you announce the hostname, and keep it out of tickets and chat logs. Our reasoning is simple: whoever opens the link first creates the organization and its first account, so that should be you.
If the link is lost, generate a fresh one on the server with su zulip -c '/home/zulip/deployments/current/manage.py generate_realm_creation_link'. The same command creates further organizations, each on its own subdomain.
Outgoing email is not optional
Zulip sends mail to confirm new users' addresses, for message notifications and for password resets, so SMTP has to work before you invite anyone. Fill in the 'Outgoing email (SMTP) settings' section of /etc/zulip/settings.py: EMAIL_HOST, EMAIL_HOST_USER, usually EMAIL_PORT = 587 with EMAIL_USE_TLS = True, and the noreply addresses.
The password does not go in settings.py. Zulip keeps secrets out of that file by policy: add the line email_password = ... to /etc/zulip/zulip-secrets.conf with an editor, never via echo: a password typed into a command lands in the history file and is visible to ps while it runs. Leave the file's restrictive permissions as the installer set them. Then test with su zulip -c '/home/zulip/deployments/current/manage.py send_test_email you@example.com' (two mails should arrive, from the default and the noreply address) and restart with su zulip -c '/home/zulip/deployments/current/scripts/restart-server'. The docs recommend a transactional mail service over an inbox product; the provider must accept mail from every address Zulip sends as.
Backups with manage.py backup
manage.py backup writes one .tar.gz with everything needed to restore the server: the database, /etc/zulip including its secrets, and uploaded files kept on local disk. Without --output the tarball lands in /tmp. It leaves out /var/log/zulip, uploads on the S3 backend and the SSL certificates, which the docs leave out as security-sensitive: a Certbot certificate is simply issued again on the replacement host once the DNS name points at it, and one you supplied yourself is yours to keep a copy of. A restore needs the same base OS, the same Zulip version and the same PostgreSQL version as the backup, then scripts/setup/restore-backup as root.
Because the archive holds the secrets, write it to a directory only the zulip user can read, and log each run to a file. This cron entry is the whole mechanism; copy the archives off the machine yourself, and pruning old ones is yours too.
sudo install -d -m 700 -o zulip -g zulip /var/backups/zulip
echo '30 3 * * * zulip /home/zulip/deployments/current/manage.py backup --output=/var/backups/zulip/zulip-$(date +\%F).tar.gz > /var/backups/zulip/backup-$(date +\%F).log 2>&1' | sudo tee /etc/cron.d/zulip-backup
The docs recommend a disaster recovery test after setting up backups. A reinstall-and-restore on a spare server is that test.
Upgrading Zulip and the OS
Read the upgrade notes for every release between yours and the target, download the new tarball the same way, and run /home/zulip/deployments/current/scripts/upgrade-zulip zulip-server-latest.tar.gz as root. The script runs apt-get upgrade, installs new dependencies, stops Zulip, applies the configuration, runs database migrations and starts the new version; the docs put downtime under 30 seconds unless a release notes an expensive migration. Output goes to /var/log/zulip/upgrade.log. Minor releases roll back by moving the symlinks under /home/zulip/deployments; upgrades do not modify settings.py, so compare it to the new template after a major upgrade.
Operating system upgrades have their own procedure. Going from Ubuntu 22.04 to 24.04, for example, means first bringing Zulip to a release that supports 24.04, taking a backup, and purging rabbitmq-server before do-release-upgrade. Follow the docs' section for your exact pair of releases.
Push notifications, plans and the licence
Apple's and Google's push models do not let a self-hosted server notify the Zulip mobile apps directly, so notifications go through Zulip's Mobile Push Notification Service; with Zulip Server 12.0 or later and a current app they are end-to-end encrypted. Register with --push-notifications at install, or later: set ZULIP_SERVICE_PUSH_NOTIFICATIONS = True in settings.py, restart, and run manage.py register_server as the zulip user. Registered servers send basic metadata, plus usage statistics unless you install with --no-submit-usage-statistics or set ZULIP_SERVICE_SUBMIT_USAGE_STATISTICS = False in /etc/zulip/settings.py.
On plans, the docs say one thing: organizations with more than 10 users must move to a paid plan, or the free Community plan if eligible, to use the push service. The server itself is distributed under the Apache 2.0 licence.
Questions
How much memory does a Zulip server need?
The minimum is 2 GB with swap, which the docs call sufficient below 100 users. From 100 users they ask for 4 GB and 2 CPUs. Their scalability guidance goes to 8 GB from 100 daily actives and 16 GB for the application plus 16 GB for the database from 400.
Can Zulip share the server with other applications?
The docs advise against it. The installer pulls in and configures nginx, PostgreSQL and Redis for itself and assumes nothing else is running, and an install on a shared box is outside what they support. Give Zulip its own machine, or use their Docker image.
Do Zulip's mobile apps notify users of a self-hosted server?
Yes, once the server is registered with Zulip's Mobile Push Notification Service. It needs outbound HTTPS. Organizations with more than 10 users need a paid plan, or the free Community plan if they qualify, to use it.
Is self-hosted Zulip free?
The server software is under the Apache 2.0 licence. The plan question applies to the push notification service, which the docs tie to organization size above 10 users.
Can I move an existing chat workspace to Zulip?
The install guide links importers for Slack, Mattermost and Rocket.Chat, among others, and says to stop after installing and follow the import steps instead of creating an organization. If you are still choosing, we also cover Mattermost, Rocket.Chat and a Matrix server.