Matrix Synapse on Opalstack: self-host chat without Docker (no root, no systemctl)
Matrix is what you run when you want your own chat without giving a platform permanent custody of your community.
Synapse is the “reference” homeserver implementation — and it’s absolutely runnable on managed hosting as long as you treat it like an app (not a snowflake pet server).
On Opalstack, Synapse runs the way we think apps should run:
- userspace
- isolated per app
- restartable
- loggable
- upgradeable
- and readable (it’s a script, not a black box)
TL;DR
- Create a Matrix Synapse app as an Open App Stack (Proxied Port).
- Map it to a domain/site (so HTTPS works normally).
- Set
server_name+public_baseurlonce (then never change them). - Create your first admin user.
- Add
.well-knownso federation works cleanly behind port 443. - Keep it alive with a cron watchdog (every ~10 minutes).
The pitch
Most “how to host Matrix” guides assume one of these:
- root access
- Docker spaghetti
- “just systemctl it bro”
- a reverse proxy config you’re supposed to guess
That’s not how Opalstack works.
Here, Synapse runs as a userspace service under your account — same model as every other Open App Stack.
What you get from the Synapse Open App Stack
A production-ish baseline that looks like this:
- Synapse installed into a per-app Python venv
- Postgres-backed config (recommended once you’re past toy usage)
- Start/stop scripts:
~/apps/<appname>/startand~/apps/<appname>/stop - Logs where you’d expect them
- Cron watchdog for boring reliability
Where it lives on disk (so you can debug like an adult)
Your app folder looks like:
- App root:
~/apps/<appname>/ - Project dir:
~/apps/<appname>/matrix/ - Virtualenv:
~/apps/<appname>/matrix/venv/ - Config:
~/apps/<appname>/matrix/config/homeserver.yaml - Data:
~/apps/<appname>/matrix/data/ - Log:
~/apps/<appname>/matrix/synapse.log
First run checklist (do this once)
1) Set your domain (this matters)
Open:
~/apps/<appname>/matrix/config/homeserver.yaml
Set these to your real domain:
server_name: "chat.example.com"
public_baseurl: "https://chat.example.com/"
Do not change server_name later unless you intend to wipe and rebuild. (Synapse treats it as identity, not cosmetics.)
2) Start it
~/apps/<appname>/start
tail -f ~/apps/<appname>/matrix/synapse.log
Quick local sanity check:
curl -sS http://127.0.0.1:<YOUR_APP_PORT>/_matrix/client/versions
3) Create your first admin user
Synapse ships a helper for this. The pattern looks like:
~/apps/<appname>/matrix/venv/bin/register_new_matrix_user \
-c ~/apps/<appname>/matrix/config/homeserver.yaml \
-u admin -p "use-a-real-password" -a \
http://127.0.0.1:<YOUR_APP_PORT>
That gets you an initial admin without opening public registration.
Federation on managed hosting (the part everyone trips on)
By default, other Matrix servers expect to reach yours at https://<server_name>:8448/.
On shared hosting, you generally don’t expose 8448 — you run behind standard HTTPS (443). So you must tell the federation network where to find you.
That’s what .well-known is for.
Minimum: /.well-known/matrix/server
Serve this JSON from:
https://<server_name>/.well-known/matrix/server
{ "m.server": "chat.example.com:443" }
Also recommended: /.well-known/matrix/client
Serve this JSON from:
https://<server_name>/.well-known/matrix/client
{
"m.homeserver": { "base_url": "https://chat.example.com" }
}
CORS note: Clients expect CORS headers on the .well-known responses. If you’re serving these as static files, add Access-Control-Allow-Origin: *.
Voice/video (straight talk)
If you want reliable voice/video calling, you’ll need a TURN server.
On shared hosting, opening the giant UDP port ranges TURN wants is typically a non-starter. If you need TURN, use a VPS (or use an external TURN provider).
Security notes (straight talk)
- Turn off open registration unless you want spam accounts forever.
- Treat your config + signing keys as secrets.
- If you expose admin surfaces publicly, you’re responsible for access control.
- Backups aren’t optional once people care about the server.
Next up
If you want to go further after the base install:
- add SSO / OIDC
- add moderation tooling
- add retention policies
- tune Postgres pooling
- set up real monitoring/alerting
That’s what “managed hosting” should feel like: boring, inspectable, fixable.