Problem

I wanted a private messaging service that I could run myself instead of depending only on public platforms.

At the same time, I did not want to expose more of the server than necessary or create a setup that would be hard to maintain later. I needed something that would let me publish Matrix properly, keep the service behind a reverse proxy, and still be simple enough to troubleshoot.

What I built

I built a self-hosted Matrix / Synapse server on Debian 12.

The setup uses Docker for the application layer, PostgreSQL for the database, and HAProxy to publish the service on the standard web ports. I also documented the domain structure so the server can use a cleaner Matrix identity while still serving Synapse from a dedicated subdomain.

I wrote reusable setup files for:

  • Docker Compose
  • Synapse configuration
  • HAProxy reverse proxy
  • beginner-friendly setup notes for the full flow

Tools used

  • Debian 12 for the host system
  • Docker and Docker Compose for the application stack
  • Matrix Synapse for the homeserver
  • PostgreSQL for the database backend
  • HAProxy for reverse proxy and public HTTPS entry point
  • Cloudflare DNS for domain records and delegation

Key results

  • published Matrix through 443 without exposing the Synapse app port directly
  • kept PostgreSQL and the internal Synapse listener off the public interface
  • documented .well-known discovery for cleaner Matrix user IDs
  • created reusable setup files so the deployment can be rebuilt more easily
  • turned the project into a cleaner public documentation package for GitHub

What this shows

  • I can deploy and document a self-hosted messaging service with multiple moving parts
  • I understand the difference between internal ports, public ports, and reverse proxy routing
  • I can make infrastructure decisions based on maintainability, not just on getting the service online

What I learned

This project reinforced how important it is to define the domain model early.

In Matrix, small choices like server_name, reverse proxy behavior, and which ports stay public affect the whole deployment. It also made clear that good documentation is part of the build, especially when the setup needs to be repeated later or shared publicly.