All the work happens in chat. Agreements with clients, passwords someone dropped in “just for a minute”, admin panel screenshots, a decision made six months ago. And all of it sits on someone else’s servers: on free Slack, older history disappears behind a paywall; in Teams, behind licences; and in Telegram groups nobody remembers who else is still in there.
It hurts most when someone leaves the team. You have to remove them by hand from a dozen groups, and a copy of every conversation stays on their phone anyway. If the account was personal, you have no control over it at all.
Your own chat server fixes that. The server is yours, the database is yours, and login goes through your own user directory. Disable an account in one place and access is gone.
What we build
The typical setup we deploy on a dedicated OVH or Hetzner server, or into your existing Proxmox:
- Chat — Mattermost (usually) or Rocket.Chat in its own VM or LXC container.
- Database — PostgreSQL for Mattermost, a MongoDB replica set for Rocket.Chat.
- Files — on local disk or in S3-compatible storage (MinIO, Hetzner or OVH Object Storage). If your team shares a lot of screenshots and PDFs, S3 is the better fit: the VM disk doesn’t keep growing and the file backup lives separately.
- Login — via SSO: Keycloak, Authentik, or your LDAP/Active Directory.
- External access — Nginx or Traefik with a Let’s Encrypt certificate, fail2ban and rate limiting on the login page.
- Integrations — channels for Prometheus/Alertmanager alerts, GitLab CI messages and deploy notices.
- Backups and monitoring — a daily database dump and file copy to separate storage, plus availability and certificate expiry checks.
Mattermost or Rocket.Chat — an honest comparison
Mattermost runs on PostgreSQL and ships as a single binary. It’s simpler to maintain: an upgrade means swapping the version and letting it run its own database migrations. It works very well with GitLab — a ready-made integration, slash commands, merge request and pipeline notifications. For technical teams it’s usually the best choice.
It keeps data in plain tables, so when needed you can pull an answer straight out of the database with SQL. One thing worth knowing: a deleted message is only flagged as deleted in the database, it doesn’t actually disappear. Great for audits. A surprise for anyone who thought “deleted means gone”.
Rocket.Chat gives you more out of the box: omnichannel customer support, video calls, more UI options. But it’s heavier. It needs a MongoDB replica set even on a single server, and upgrading MongoDB across major versions has to go step by step, through every version in between.
Both have free editions, but the vendors have changed the terms more than once in recent years: what’s included, how many users, which features moved to paid plans. So we don’t promise anything up front — before we start, we check the current terms against your headcount and the features you need. For example, in Mattermost generic OpenID Connect, SAML and LDAP login have historically been in the paid editions, while the free one supported GitLab login. That decides how exactly we wire up SSO.
When a self-hosted chat is NOT worth it: if you’re five people, nobody ever needs to be cut off, and nobody needs three years of history. The server and upkeep won’t pay for themselves.
Push notifications, SSO and offboarding
Push notifications are the least obvious part. The official mobile apps receive pushes through Apple’s and Google’s services, and those only accept them from the app’s publisher. So your server sends notifications through the vendor’s push proxy, which forwards them to Apple or Google. That means you depend on a third-party service and its terms. To keep message content from leaving your server, we enable a mode where the push carries only an ID and the phone fetches the text from your server. A fully self-run push proxy is possible, but it means building your own mobile apps — usually overkill for a small team.
That leads to a second trade-off: you can’t hide the chat behind a VPN or a shared login gateway in front of the site. Mobile and desktop clients talk to the API directly. So we protect the application itself: SSO with a second factor, fail2ban, rate limits.
SSO is set up so people log in with the same account they use for email or GitLab. Before switching over, we link existing accounts by email so nobody ends up with a duplicate. We also double-check the site URL in the config: if an internal address is left there, SSO login and links in push notifications simply won’t work.
Offboarding: disable the person in the directory and they can’t log in again. Sessions that are already open live until they expire, though, so we set sensible session lengths and write a short checklist for the day someone leaves.
Integrations. Mattermost accepts Slack-format webhooks, so Alertmanager can send alerts without any extra plugins:
receivers:
- name: chat-alerts
slack_configs:
- api_url_file: /etc/alertmanager/chat_webhook
channel: alerts
send_resolved: true
title: '{{ .CommonLabels.alertname }}: {{ .Status }}'
GitLab CI posts to a channel the same way — a broken pipeline, a successful deploy.
Backups and upgrades. The database dump and the file copy go separately to different storage, and restores are tested. Upgrades follow a plan: VM snapshot first, then the new version, then a check of login, pushes and webhooks.
What you get
- A team chat on your own server, with your domain and TLS.
- SSO login and a clear procedure for when someone leaves.
- Mobile and desktop apps with working notifications.
- Channels with monitoring alerts and CI events.
- Daily backups of the database and files, with tested restores.
- Monitoring of availability, disk space and certificate expiry.
- Documentation: what lives where, how to upgrade, how to restore from backup.
- Full admin access — everything stays with you, even if we stop working together.
If your work has sprawled across Telegram groups or you’ve hit Slack’s limits, get in touch. Tell us how many people you are and what you use today, and we’ll suggest what to deploy and how to move over.