TPTito Pinardo Gutiérrez
← Back to the rack
Español
1U · MATRIXSynapse · Element · E2EE In production

Slot 6 of 25 in the rack · In production

1U unit · Communication

Matrix · my messaging server

Truly private: no sign-ups, no federation

A chat server of my own, closed: nobody can sign up and it doesn't talk to other servers. From my phone I talk to Hermes, get the homelab's important alerts end-to-end encrypted and hear about messages from this site.

Why my own
Server alerts talk about my infrastructure and Hermes reads my email. None of that had to go through a third-party service.
How it's set up
Synapse with its PostgreSQL in one container, Element in another: the stateful one isn't mixed with a static site. Official packages, no Docker.
Who writes
Each bot with its own user and no admin rights: the alerts one, Hermes and this site's. If one falls, it doesn't drag the others down.
  • 0open sign-ups and federated servers
  • E2EEin the alert rooms, checked on the events
  • 3bots, each with its own user and room
  • 5/5failed alerts nobody had noticed

Its code, in the open Public version of the repo: no IPs or secrets.

Inside the messaging

Hermes, the server's alerts and this site's messages all arrive here. Pick a flow.

Hermesvisiting the unit

Click a part or pick a flow. If you came from a skill, the parts where I use it are already lit.

      I talk to Hermes
      1. Element on my phone — I type in Element on my phone, away from home and without VPN.
      2. Cloudflare — Cloudflare gets the request.
      3. Traefik — Traefik passes it to the Matrix server.
      4. Client API only — Only the client API exists: it's what the phone uses.
      5. Synapse — Synapse receives the message.
      6. Local PostgreSQL — It stores it in its PostgreSQL.
      7. Synapse — And delivers it to the room's devices.
      8. Assistant room — The assistant's room.
      9. Hermes — Hermes gets it and starts its turn.
      A serious alert
      1. Monitoring — Monitoring spots a high-severity problem.
      2. Alerts relay — It hands it to the local relay, with a shared secret so nobody on the network can inject fake alerts.
      3. Device keys — The relay encrypts it with its device's keys.
      4. Synapse — Synapse stores and delivers it encrypted: only the room's devices can read it.
      5. Alerts room — It reaches the alerts room.
      6. Element on my phone — My phone buzzes.
      Someone writes to me on the site
      1. This site — Someone sends this site's contact form.
      2. This site's bot — The site's own bot sends the notice.
      3. Synapse — Synapse confirms with an event ID. Without that confirmation, the site retries.
      4. Site room — It reaches the site's room.
      5. Element on my phone — I see it on my phone.
      Someone pokes at the server
      1. Element on my phone — Someone on the internet opens the messaging domain.
      2. Traefik — Traefik receives it.
      3. Test page blocked — They look for the «it works» page: they get a 404.
      4. Federation off — They try to talk as another Matrix server: that service doesn't exist.
      5. Sign-ups closed — They try to sign up: sign-ups are closed. All they see is the login screen.
      Ansible builds the server
      1. Configuration on top — Ansible deploys a settings file next to the configuration the package generated, without touching its keys.
      2. Sign-ups closed — Sign-ups closed and the registration secret, from the vault.
      3. Synapse — Synapse starts and merges both configurations.
      4. Local PostgreSQL — Connected to its local PostgreSQL.
      5. Element Web — Element is deployed in its own minimal container.
      1. Element on my phone

        From here I talk to Hermes and get alerts, with no VPN.

      2. Cloudflare

        In front of the messaging domain.

      3. Traefik

        The public door: it only lets through what the client needs.

        • Traefik
      4. Monitoring

        Sends its serious problems to the alerts room.

        • Zabbix
      5. Security

        Sends high-level events, grouped so as not to flood.

        • Wazuh
      6. This site

        Sends a notice when someone writes through the contact form.

      7. Element Web

        The web client, served by nginx from a minimal container. Stateless: it can be rebuilt in minutes.

        • Linux (Debian, Alpine, Arch)
      8. Test page blocked

        Synapse ships an «it works» page that can't be switched off. A Traefik rule rewrites it into a 404 without touching what the client needs.

        • Traefik
        • Linux / SSH hardening
      9. Client API only

        The server only serves what the apps use. It doesn't even expose the part that talks to other servers.

        • Linux / SSH hardening
      10. Federation off

        It isn't a filter: the federation service doesn't exist. No other server can talk to this one.

        • Linux / SSH hardening
      11. Synapse

        The reference Matrix server, from the official package.

        • Linux (Debian, Alpine, Arch)
      12. Local PostgreSQL

        History, rooms and device keys. Local because it's Synapse's alone: nothing to share.

        • PostgreSQL
      13. Configuration on top

        Ansible doesn't touch the main file, which holds the keys the package generated: it only deploys a settings file Synapse merges in.

        • Ansible
      14. Sign-ups closed

        Accounts can only be created with a secret kept encrypted in the Ansible vault.

        • Ansible
        • Linux / SSH hardening
      15. Alerts relay

        A daemon with its own Matrix device. Monitoring tools hand it the alert and it sends it truly encrypted.

        • Python
      16. Device keys

        Encrypting needs a device with live sessions. That's why the relay syncs all the time: if I join a room later, it finds out.

        • Python
      17. Hermes

        The assistant, with its own user. It only answers in the room I first invited it to.

        • Python
      18. This site's bot

        A user of its own that can only write in its room. If someone compromised the site, the token is good for nothing else.

      19. Alerts room

        Only what's serious: high severity in monitoring and level 12 or above in security.

      20. Assistant room

        Where I talk to Hermes.

      21. Rooms by topic

        One per watcher (backups, certificates, network…), plus email and events. Each only speaks when there's something.

      22. Site room

        Messages from the contact form.

      What I use from my skill tree

      Pick a skill: the parts where I use it light up on the map and I tell you what for.

      Terraform
      Creates the Synapse and Element containers.
      Ansible
      Deploys Synapse with its configuration on top of the package's, closes sign-ups and keeps the registration secret encrypted.
      LXC
      Synapse and Element in separate containers: the stateful one isn't mixed with the static one.
      Linux (Debian, Alpine, Arch)
      Official packages and nginx with systemd, no Docker.
      Traefik
      The public door, with a dedicated rule that hides Synapse's test page.
      Zabbix
      Sends its high-severity problems to the alerts room through the relay.
      Wazuh
      Sends its level 12+ events, grouped to avoid repeats.
      Linux / SSH hardening
      Closed sign-ups, non-existent federation, client API only, hidden test page and bots without admin.
      Python
      The alerts relay with matrix-nio: persistent session, continuous sync and a local endpoint protected with a secret.
      PostgreSQL
      Synapse's database, local and its own.

      Incident log

      Real failures. Read the symptom, pick the cause you think it was and see how it was fixed.

      1. #1

        An alert channel that had never worked

        17-09-2026 · alerts

        The serious-alerts channel to Matrix had been set up for two days. The relay was running and tested. But checking the real history, all 5 of its alerts had failed.

        What do you think was going on?

        Show the fix
        Cause
        When the channel was created in Zabbix, the «problem», «recovery» and «update» templates were missing. With no text to generate, Zabbix never invoked the script, and the error only showed in its history.
        Fix
        Add the three templates in the same format as the old channel and test with a real alert, not with «the bot is on».
        Lesson
        A component being on doesn't mean it works. To know whether an alert channel works, check whether the alerts arrived.
      2. #2

        «Unable to decrypt message»

        17-09-2026 · encryption

        A voice reply from Hermes showed up in Element as «Unable to decrypt message», on phone and PC. Restarting the bot didn't fix it: the next encrypted messages failed too.

        What do you think was going on?

        Show the fix
        Cause
        A script that sent a message «from outside» opened the bot's encryption store while the process was still running, and left its session state corrupted on disk. Every restart loaded the same broken state.
        Fix
        Quarantine the corrupted store (without deleting it) and let the bot log in with a brand-new device. I verified it again in Element; that voice note was lost.
        Lesson
        Encryption state belongs to a single process. Never touch it without stopping the service and checking it really stopped.
      3. #3

        «Server not found», just created

        15-09-2026 · DNS

        I had just created the web client's public record. It resolved fine on any public DNS, but from home the browser said it didn't exist.

        What do you think was going on?

        Show the fix
        Cause
        Someone had asked for that name before it existed, and the home DNS kept the negative answer. Restarting the service isn't enough: that cache is saved to disk and reloaded.
        Fix
        Flush the cache through its API. Since then, creating a new record has one more step in the procedure.
        Lesson
        Caches keep the «no»s too. And a persistent cache survives the restart that should clear it.

      How it was built

      1. 15-09-2026

        Client and server

        • Element Web in a minimal container with nginx.
        • Synapse with its PostgreSQL in another, private: no sign-ups, no federation.
        • All created with Terraform and configured with Ansible.
      2. 16-17-09-2026

        Encrypted alerts

        • A relay with its own device to send end-to-end encrypted alerts.
        • Monitoring and security connected, with stricter thresholds than the old channel: only what needs checking.
        • The channel truly fixed after checking its history.
      3. September and October 2026

        A space for the house

        • Topic rooms for Hermes and its watchers.
        • This site's own bot, in its room.
      4. 15 and 18-10-2026

        Tidier alerts and a diagnosis

        • Separate rooms for CI/CD and CrowdSec: incidents only gets what needs looking at.
        • A bot that, when asked, walks a service's chain (DNS, Cloudflare, router, the door, the container) and names the first point that fails. All with read-only permissions.

      Decisions (and why)

      • Which server

        Synapse, the reference one

        The most mature and documented. The classic argument for the alternative, using less, doesn't matter on this server.

      • Public or behind the VPN?

        Public, but closed

        The goal was to use it from my phone without VPN. That's balanced with closed sign-ups, federation truly off and only the client API exposed.

      • One bot for everything or one per job

        One per job, no admin

        The alerts relay, Hermes and the site's bot have separate users. If one is compromised, it can only write in its own place.

      What I learned

      • Check alert channels by what arrives, not by what's switched on.
      • End-to-end encryption has state: treat it as a single-process database.
      • Public doesn't have to mean open: what isn't served can't be attacked.

      What's next

      • «Recovered» notices in the alerts room, to close each problem.
      • Backups of the bots' key stores, with the service stopped.

      Shall we talk?

      I can walk through any of these decisions in detail in an interview.

      Download CV Get in touch ← Back to the rack

      Rebuilt from my documentation: no domains, rooms, users or addresses.