Why you’d do this (and why it’s a bit of a rite of passage)
Matrix is an open, decentralized communication protocol. Element is the most common client for it. Self‑hosting them is appealing when you want:
- Ownership over your comms stack
- Your own domain + branding
- Data residency / compliance control
- The satisfaction of running real infrastructure
It’s also… a small pile of moving parts: DNS, TLS, reverse proxies, federation ports, well‑known files, and making sure your homeserver doesn’t melt the moment you enable registration.
This guide walks you through a practical, beginner‑friendly setup using Docker Compose and Synapse (the reference Matrix homeserver).
What you’ll build
By the end you’ll have:
- A Matrix homeserver at:
matrix.yourdomain.com - Element Web at:
chat.yourdomain.com - HTTPS everywhere (Let’s Encrypt)
- A working client login and room chat
Assumptions
- You own a domain, e.g.
yourdomain.com - You have a VPS/server (Ubuntu/Debian‑like)
- You can open ports 80 and 443 to the server
Step 0: DNS (don’t skip this, future‑you will cry)
Create DNS records:
-
matrix.yourdomain.com→ your server IP (A/AAAA) -
chat.yourdomain.com→ your server IP (A/AAAA)
You can put both behind the same reverse proxy; we’ll do that.
Step 1: Install Docker + Compose
On most modern distros:
sudo apt update
sudo apt install -y docker.io docker-compose-plugin
sudo systemctl enable --now docker
sudo usermod -aG docker $USER
Log out and back in so your user is in the docker group.
Step 2: Pick a reverse proxy (Caddy keeps your blood pressure lower)
You can do this with Nginx + Certbot, Traefik, etc. For a clean tutorial, Caddy is hard to beat: automatic TLS, simple config.
We’ll run:
-
caddy(reverse proxy + TLS) -
synapse(Matrix homeserver) -
element-web(web client) -
postgres(recommended DB for Synapse)
Create a working directory:
mkdir -p ~/matrix-stack
cd ~/matrix-stack
Step 3: Generate Synapse config
Synapse needs a config file + keys.
Create a docker-compose.yml we’ll fill in (don’t run it yet):
services:
postgres:
image: postgres:16
environment:
POSTGRES_USER: synapse
POSTGRES_PASSWORD: CHANGE_ME_STRONG
POSTGRES_DB: synapse
volumes:
- ./data/postgres:/var/lib/postgresql/data
restart: unless-stopped
synapse:
image: matrixdotorg/synapse:latest
environment:
SYNAPSE_SERVER_NAME: yourdomain.com
SYNAPSE_REPORT_STATS: "no"
volumes:
- ./data/synapse:/data
depends_on:
- postgres
restart: unless-stopped
element-web:
image: vectorim/element-web:latest
volumes:
- ./data/element/config.json:/app/config.json:ro
restart: unless-stopped
caddy:
image: caddy:2
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./data/caddy:/data
- ./data/caddy-config:/config
restart: unless-stopped
Now generate Synapse config and keys:
docker run -it --rm \
-v "$(pwd)/data/synapse:/data" \
-e SYNAPSE_SERVER_NAME=yourdomain.com \
-e SYNAPSE_REPORT_STATS=no \
matrixdotorg/synapse:latest generate
This creates data/synapse/homeserver.yaml and signing keys.
Configure Synapse to use Postgres
Edit data/synapse/homeserver.yaml and set the database section (replace the default sqlite3):
database:
name: psycopg2
args:
user: synapse
password: "CHANGE_ME_STRONG"
database: synapse
host: postgres
cp_min: 5
cp_max: 10
Also set the public base URL:
public_baseurl: "https://matrix.yourdomain.com/"
Step 4: Configure Element Web
Create data/element/config.json:
{
"default_server_config": {
"m.homeserver": {
"base_url": "https://matrix.yourdomain.com",
"server_name": "yourdomain.com"
}
},
"disable_custom_urls": true,
"disable_guests": true,
"brand": "Your Matrix"
}
Step 5: Reverse proxy + TLS with Caddy
Create Caddyfile:
matrix.yourdomain.com {
reverse_proxy synapse:8008
}
chat.yourdomain.com {
reverse_proxy element-web:80
}
Synapse listens on 8008 inside the container by default.
Start everything:
docker compose up -d
Check logs if needed:
docker compose logs -f --tail=200
Step 6: Create your first user
Exec into the synapse container and register a user.
docker compose exec synapse register_new_matrix_user \
-c /data/homeserver.yaml \
http://localhost:8008
Follow the prompts (username/password). Then open:
- Element Web:
https://chat.yourdomain.com
Log in with:
- username:
@youruser:yourdomain.com
Step 7 (important): Lock it down a bit
Self‑hosting chat is fun until it becomes a spam magnet.
Consider:
- Disable open registration unless you truly want it
- Enable reCAPTCHA / token‑based registration if you must allow signups
- Keep Synapse updated
- Monitor disk space (media can grow fast)
- Back up Postgres + Synapse keys (
data/synapse)
In homeserver.yaml, check:
enable_registration: false
Federation and “well‑known” (optional, but good to know)
If you want @user:yourdomain.com to work nicely and support federation cleanly, you’ll eventually run into:
.well-known/matrix/server.well-known/matrix/client- Potentially opening port 8448 (depends on your setup)
This is where the “I just wanted to chat with friends” journey turns into “I am now operating a small internet service.” Congrats.
If you want a follow‑up post, I can provide a clean, battle‑tested .well-known + federation configuration.
The honest part: this is a headache you don’t have to keep
If you love tinkering, self‑hosting Matrix is rewarding.
If you’d rather skip the yak‑shaving and still get:
- Matrix + Element on your own domain
- A managed path to add a bot and interact with it
- A smoother “it just works” experience
…you can save yourself the whole weekend by using https://clawship.app.
Clawship is built to help you spin up your stack faster and add your own bot interaction without reinventing the infrastructure wheel. You focus on the community and automation, not the reverse proxy sudoku.
Troubleshooting quick hits
- Caddy won’t get certificates → check DNS points to the server and ports 80/443 are open
-
Synapse errors on DB → verify Postgres credentials match in
homeserver.yaml -
Element loads but can’t login → confirm Element config points at
https://matrix.yourdomain.com - Media storage exploding → set retention policies and monitor disk usage
What’s next
If you want, I can write a follow‑up tutorial on:
- Federation +
.well-knowndone correctly - Adding a Matrix bot (and safe bot permissions)
- Moderation + anti‑spam strategy
- Backups and disaster recovery
And if you’d rather just ship: https://clawship.app.
Top comments (0)