How to install Matrix Synapse and Element Web on Ubuntu or Debian
Run your own Matrix homeserver with Synapse from the official packages, PostgreSQL, Caddy for HTTPS and federation delegation, plus Element Web in the browser.
- Advanced
- 50 min read
- Updated
Tested on: Ubuntu 24.04 LTS, Ubuntu 26.04 LTS, Debian 12, Debian 13
On this page
- Prerequisites
- Step 1 — Install PostgreSQL and create the database
- Step 2 — Add the Matrix.org repository and install Synapse
- Step 3 — Point Synapse at PostgreSQL and lock down registration
- Step 4 — Configure Caddy for Synapse and delegation
- Step 5 — Open the firewall and test federation
- Step 6 — Create the first administrator
- Step 7 — Install Element Web
- Back up and restore
- Update Synapse
- Troubleshooting
- register_new_matrix_user says no registration_shared_secret is defined
- Synapse does not start: Database has incorrect collation
- The federation tester reports a .well-known or certificate error
- Element cannot reach the homeserver
- Invited users from other servers cannot join your rooms
- Next steps
Matrix is an open, federated protocol for chat and calls: users on different servers can talk to each other, much like email. Synapse is the homeserver maintained by Element, and Element Web is the browser client. This guide installs Synapse from the official packages.matrix.org repository on Ubuntu or Debian, stores its data in PostgreSQL, puts it behind Caddy for HTTPS, and uses .well-known delegation so that user IDs look like @alice:example.com while the server itself runs at matrix.example.com. You then add Element Web on its own subdomain, create the first administrator with open registration switched off, and set up backups and updates.
The setup uses three names. Choose them now:
| Name | Example | Purpose |
|---|---|---|
| Server name | example.com | Appears in every user ID and room alias. It cannot be changed later. |
| Synapse host | matrix.example.com | Where clients and other servers reach Synapse, on port 443. |
| Element Web | element.example.com | The browser client. Element recommends a different domain from the homeserver to limit the impact of cross-site scripting bugs. |
Prerequisites
- A server running Ubuntu 24.04 LTS, Ubuntu 26.04 LTS, Debian 12 or Debian 13 on x86_64 (amd64). The Matrix.org packages are built for amd64; packages.matrix.org publishes them for all four releases.
- A non-root user with
sudorights: see Secure a new Linux server and Set up SSH keys. - DNS A records (and AAAA records if you use IPv6) for
matrix.example.comandelement.example.compointing to this server. - The main domain
example.commust serve two small JSON files over HTTPS. If it points to this server, Caddy serves them in Step 4; if your website runs elsewhere, you publish the same files there. - Caddy installed as described in Caddy reverse proxy.
The Synapse project does not publish minimum requirements. The figures below are a conservative starting point for a small private server, not official numbers:
| Resource | Minimum (official) | Suggested starting point |
|---|---|---|
| CPU | Not published | 2 vCPU |
| RAM | Not published | 2 GB, or 4 GB if users join large public rooms on other servers |
| Disk | Not published | 20 GB plus uploaded media |
| Database | PostgreSQL 14 or later for current releases | The PostgreSQL package of your distribution |
Step 1 — Install PostgreSQL and create the database
Install PostgreSQL and the client library Synapse uses, generate a password, and create a database user and a database with the encoding and locale Synapse requires:
sudo apt update
sudo apt install postgresql libpq5
openssl rand -hex 24
sudo -u postgres createuser --pwprompt synapse_user
sudo -u postgres createdb --encoding=UTF8 --locale=C --template=template0 --owner=synapse_user synapseEnter the generated password twice when createuser asks for it, and keep it for Step 3. Check the new database:
sudo -u postgres psql -l | grep synapseThe line should show UTF8 encoding and C for both collation and character type. Synapse refuses to start on a database with another collation.
Step 2 — Add the Matrix.org repository and install Synapse
Synapse's documentation recommends the Matrix.org packages. Debian's own matrix-synapse package is only available for Debian 14 (forky) and unstable, and the documentation does not recommend the package in Ubuntu's archive.
sudo apt install -y lsb-release wget apt-transport-https
sudo wget -O /usr/share/keyrings/matrix-org-archive-keyring.gpg https://packages.matrix.org/debian/matrix-org-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/matrix-org-archive-keyring.gpg] https://packages.matrix.org/debian/ $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/matrix-org.list
sudo apt update
sudo apt install matrix-synapse-py3During installation the package asks two questions:
- Name of the server: enter your server name,
example.com, notmatrix.example.com. The answer is written to/etc/matrix-synapse/conf.d/server_name.yaml. - Report homeserver usage statistics: answer as you prefer. The default is no.
Check the result:
cat /etc/matrix-synapse/conf.d/server_name.yaml
sudo systemctl status matrix-synapse --no-pagerThe package starts Synapse straight away with a temporary SQLite database. No accounts exist yet, so switching to PostgreSQL in the next step loses nothing.
Step 3 — Point Synapse at PostgreSQL and lock down registration
The main configuration lives in /etc/matrix-synapse/homeserver.yaml. Put your own settings into separate files in /etc/matrix-synapse/conf.d/ instead: Synapse loads them after the main file, and package upgrades do not ask you to merge them. Stop Synapse first:
sudo systemctl stop matrix-synapseCreate three files. In the first one, replace change-me with the database password from Step 1 before you run the command; the second one generates a random registration secret:
sudo tee /etc/matrix-synapse/conf.d/database.yaml > /dev/null <<'EOF'
database:
name: psycopg2
args:
user: synapse_user
password: "change-me"
dbname: synapse
host: localhost
cp_min: 5
cp_max: 10
EOF
sudo tee /etc/matrix-synapse/conf.d/registration.yaml > /dev/null <<EOF
enable_registration: false
registration_shared_secret: "$(openssl rand -hex 32)"
EOF
echo 'public_baseurl: "https://matrix.example.com/"' | sudo tee /etc/matrix-synapse/conf.d/public_baseurl.yamldatabaseswitches Synapse to PostgreSQL.enable_registration: falsekeeps open sign-up off (it is also the default).registration_shared_secretletsregister_new_matrix_usercreate accounts. Anyone who knows it can register users, including administrators, even with registration disabled, so keep it private.public_baseurlis the address clients use. Without it, Synapse assumeshttps://example.com/, which is not where it runs.
Make the files readable only by root and the matrix-synapse group, then start Synapse:
sudo chown root:matrix-synapse /etc/matrix-synapse/conf.d/*.yaml
sudo chmod 640 /etc/matrix-synapse/conf.d/*.yaml
sudo systemctl start matrix-synapse
sudo systemctl status matrix-synapse --no-pager
curl -s http://localhost:8008/_matrix/client/versionsThe last command should print a JSON list of supported Matrix versions. Synapse listens only on localhost port 8008, and the packaged listener already trusts the X-Forwarded-For header from a local reverse proxy. If Synapse does not start, read sudo journalctl -u matrix-synapse -n 50 --no-pager.
Synapse can email password resets, address verification and (with enable_notifs: true) notifications about missed messages; to turn this on, add an email section to a file such as /etc/matrix-synapse/conf.d/email.yaml with smtp_host: smtp.example.com, smtp_port: 587, require_transport_security: true, smtp_user, smtp_pass and a notif_from address, as described under email in Synapse's configuration manual, then restart Synapse.
Step 4 — Configure Caddy for Synapse and delegation
Add two site blocks to /etc/caddy/Caddyfile. The first one proxies the Matrix client and federation APIs to Synapse; the second one serves the delegation files on your main domain:
matrix.example.com {
reverse_proxy /_matrix/* localhost:8008
reverse_proxy /_synapse/client/* localhost:8008
}
example.com {
header /.well-known/matrix/* Content-Type application/json
header /.well-known/matrix/* Access-Control-Allow-Origin *
respond /.well-known/matrix/server `{"m.server": "matrix.example.com:443"}`
respond /.well-known/matrix/client `{"m.homeserver": {"base_url": "https://matrix.example.com"}}`
}/.well-known/matrix/servertells other homeservers to send federation traffic forexample.comtomatrix.example.comon port 443, so port 8448 is not needed./.well-known/matrix/clientlets apps such as Element find the homeserver when a user types only@alice:example.com./_synapse/adminis deliberately not proxied. Synapse's documentation does not recommend exposing the admin API to the internet; reach it through an SSH tunnel tolocalhost:8008when you need it.
Reload Caddy and test all three addresses:
sudo systemctl reload caddy
curl https://example.com/.well-known/matrix/server
curl https://example.com/.well-known/matrix/client
curl https://matrix.example.com/_matrix/client/versionsStep 5 — Open the firewall and test federation
Synapse listens on localhost only, and delegation sends federation traffic to port 443, so the firewall needs SSH and web traffic only:
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verboseCheck federation with the official federation tester: enter example.com and every check should pass. The same test is available as a JSON API:
curl -s "https://matrix.org/federationtester/api/report?server_name=example.com" | grep -o '"FederationOK":[a-z]*'The output should be "FederationOK":true.
Step 6 — Create the first administrator
Create your own account as an administrator. Pass both the main configuration file (for the listener address) and the registration file (for the shared secret):
sudo register_new_matrix_user -c /etc/matrix-synapse/homeserver.yaml -c /etc/matrix-synapse/conf.d/registration.yamlThe tool asks for a user name, a password and whether to make the user an admin; answer yes for your own account. Repeat the command without admin rights for every other person. The new user ID is @username:example.com.
Step 7 — Install Element Web
Element publishes Element Web as a Debian package that works on Debian and Ubuntu. Add the repository and install it:
sudo wget -O /usr/share/keyrings/element-io-archive-keyring.gpg https://packages.element.io/debian/element-io-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/element-io-archive-keyring.gpg] https://packages.element.io/debian/ default main" | sudo tee /etc/apt/sources.list.d/element-io.list
sudo apt update
sudo apt install element-webPoint Element at your homeserver by replacing /etc/element-web/config.json with a minimal configuration:
sudo tee /etc/element-web/config.json > /dev/null <<'EOF'
{
"default_server_config": {
"m.homeserver": {
"base_url": "https://matrix.example.com",
"server_name": "example.com"
}
}
}
EOFThen add a site block for element.example.com to the Caddyfile. It serves the static files from /usr/share/element-web, sets the security headers Element recommends and turns off caching for the files that change on every release:
element.example.com {
root * /usr/share/element-web
file_server
header {
X-Frame-Options SAMEORIGIN
X-Content-Type-Options nosniff
X-XSS-Protection "1; mode=block"
Content-Security-Policy "frame-ancestors 'self'"
}
@nocache path / /index.html /version /config.json /i18n/*
header @nocache Cache-Control no-cache
}sudo systemctl reload caddy
curl -I https://element.example.comOpen https://element.example.com, sign in with the account from Step 6, create a room and invite a second user to check that messages arrive in both directions.
Back up and restore
Synapse's backup guide lists what holds state: the PostgreSQL database, the configuration in /etc/matrix-synapse (including the server's signing key homeserver.signing.key), and the local media in /var/lib/matrix-synapse/media. Remote media, URL previews and their thumbnails are caches and can be skipped. The guide also recommends leaving out the data of the e2e_one_time_keys_json table, because restoring old one-time keys can break encrypted sessions. pg_dump takes a consistent snapshot, so Synapse can keep running:
sudo mkdir -p /opt/backups
sudo chown $USER:$USER /opt/backups
chmod 700 /opt/backups
sudo -u postgres pg_dump -Fc --exclude-table-data e2e_one_time_keys_json synapse > /opt/backups/synapse-db-$(date +%F).dump
sudo tar czf /opt/backups/synapse-files-$(date +%F).tar.gz --exclude='remote_*' --exclude='url_cache*' /etc/matrix-synapse /var/lib/matrix-synapse/media /etc/element-web /etc/caddy/Caddyfile
ls -lh /opt/backupsTo restore on a new server, complete Steps 1 and 2 with the same server name and the same database password, but stop Synapse before it gets any data. Synapse's guide warns never to restore into a database that already contains tables; the database from Step 1 is still empty because Synapse used SQLite until now.
sudo systemctl stop matrix-synapse
sudo -u postgres pg_restore -d synapse < /opt/backups/synapse-db-2026-10-09.dump
sudo tar xzf /opt/backups/synapse-files-2026-10-09.tar.gz -C /
sudo chown -R matrix-synapse:matrix-synapse /var/lib/matrix-synapse
sudo systemctl start matrix-synapseThen install Element Web (Step 7) and reload Caddy.
Update Synapse
Synapse is updated through apt like any other package. Read the upgrade notes for every version between yours and the new one first, because some releases change configuration or raise the minimum Python or PostgreSQL version, and take a backup:
sudo apt update
sudo apt upgrade
curl -s http://localhost:8008/_matrix/federation/v1/versionThe last command prints the running Synapse version. You do not have to install every release you missed, but rollbacks are hard after database schema changes, so the backup is your way back. If apt asks whether to replace a configuration file, keep your version. The same apt upgrade updates Element Web.
Troubleshooting
register_new_matrix_user says no registration_shared_secret is defined
The tool only reads the files you pass with -c; it does not scan conf.d. Pass both homeserver.yaml and conf.d/registration.yaml as shown in Step 6.
Synapse does not start: Database has incorrect collation
The database was created with your system locale instead of C. Drop it with sudo -u postgres dropdb synapse (only on a new server without data), create it again with the createdb command from Step 1 and restart Synapse.
The federation tester reports a .well-known or certificate error
Run the three curl commands from Step 4. The server file must return JSON from https://example.com with a valid certificate, the m.server value must name matrix.example.com:443, and the request must not be redirected with HTTP 308. Check DNS for both names as well.
Element cannot reach the homeserver
Open https://matrix.example.com/_matrix/client/versions in the browser. If it fails, check the Caddy block and public_baseurl. If it works, check base_url in /etc/element-web/config.json and the Access-Control-Allow-Origin header on /.well-known/matrix/client.
Invited users from other servers cannot join your rooms
Federation needs connectivity in both directions, and Synapse's federation guide names a misconfigured reverse proxy as a common cause. Run the federation tester again, check sudo journalctl -u matrix-synapse for connection errors, and make sure the server can make outbound HTTPS connections.
Next steps
- Add video meetings with Jitsi Meet; Element can use a Jitsi server for group calls.
- Compare Matrix with Mattermost and Rocket.Chat.
- Learn more about the proxy used here in Caddy reverse proxy.
- Read the official Synapse documentation for workers, email, TURN for calls and the admin API.
- Compare servers for team chat on the team chat hosting page.
Frequently asked questions
Can I change my Matrix server name later?
No. Synapse's documentation states that the server_name cannot be changed later. It becomes part of every user ID and room alias, so decide on it, usually your main domain such as example.com, before you install.
Do I need to open port 8448?
Not with delegation. This guide publishes a .well-known/matrix/server file on your main domain that tells other servers to use matrix.example.com on port 443. Without delegation, other servers try port 8448 on the server_name host.
Why PostgreSQL instead of SQLite?
Synapse's documentation says SQLite should not be used in a production server and that almost all installations should use PostgreSQL. SQLite is only meant for testing.
How do other people get accounts?
Open registration stays off, which is Synapse's default. As the administrator you create accounts with register_new_matrix_user. If you later want self-service sign-up, read the registration options in Synapse's configuration manual, such as email verification or registration tokens, before you enable it.
Do voice and video calls need a TURN server?
Usually yes. Matrix calls use WebRTC, and users behind NAT or strict firewalls often cannot connect without a TURN relay. Synapse's TURN guide covers coturn and eturnal and the turn_uris and turn_shared_secret settings that hand TURN credentials to clients.
Sources
- element-hq.github.io/synapse/latest/setup/installation.html
- element-hq.github.io/synapse/latest/postgres.html
- element-hq.github.io/synapse/latest/reverse_proxy.html
- element-hq.github.io/synapse/latest/delegate.html
- element-hq.github.io/synapse/latest/federate.html
- element-hq.github.io/synapse/latest/usage/configuration/config_docu…
- element-hq.github.io/synapse/latest/usage/administration/backups.html
- element-hq.github.io/synapse/latest/upgrade.html
- packages.matrix.org/debian/dists
- packages.matrix.org/debian/dists/resolute/Release
- packages.matrix.org/debian/dists/trixie/Release
- raw.githubusercontent.com/element-hq/synapse/develop/debian/matrix-…