# 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.

Difficulty: Advanced\
Tested on: Ubuntu 24.04 LTS, Ubuntu 26.04 LTS, Debian 12, Debian 13

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 `sudo` rights: see [Secure a new Linux server](/guides/secure-a-new-linux-server) and [Set up SSH keys](/guides/ssh-keys).
- DNS A records (and AAAA records if you use IPv6) for `matrix.example.com` and `element.example.com` pointing to this server.
- The main domain `example.com` must 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](/guides/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:

```bash
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 synapse
```

Enter the generated password twice when `createuser` asks for it, and keep it for Step 3. Check the new database:

```bash
sudo -u postgres psql -l | grep synapse
```

The 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.

```bash
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-py3
```

During installation the package asks two questions:

- **Name of the server**: enter your server name, `example.com`, not `matrix.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:

```bash
cat /etc/matrix-synapse/conf.d/server_name.yaml
sudo systemctl status matrix-synapse --no-pager
```

The 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:

```bash
sudo systemctl stop matrix-synapse
```

Create 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:

```bash
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.yaml
```

- `database` switches Synapse to PostgreSQL.
- `enable_registration: false` keeps open sign-up off (it is also the default).
- `registration_shared_secret` lets `register_new_matrix_user` create accounts. Anyone who knows it can register users, including administrators, even with registration disabled, so keep it private.
- `public_baseurl` is the address clients use. Without it, Synapse assumes `https://example.com/`, which is not where it runs.

Make the files readable only by root and the `matrix-synapse` group, then start Synapse:

```bash
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/versions
```

The 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](https://element-hq.github.io/synapse/latest/usage/configuration/config_documentation.html), then restart Synapse.

> **Note**
>
> Outbound port 25 is closed by default on HyperDC VPS. For services bought for a term of 3 months or longer, it is opened on request: [open a support ticket](/guides/support-tickets). Until then, send mail through an SMTP relay on port 587.

## 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:

```caddyfile
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/server` tells other homeservers to send federation traffic for `example.com` to `matrix.example.com` on port 443, so port 8448 is not needed.
- `/.well-known/matrix/client` lets apps such as Element find the homeserver when a user types only `@alice:example.com`.
- `/_synapse/admin` is deliberately not proxied. Synapse's documentation does not recommend exposing the admin API to the internet; reach it through an SSH tunnel to `localhost:8008` when you need it.

Reload Caddy and test all three addresses:

```bash
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/versions
```

> **Note**
>
> If `example.com` is your website on another server, leave out the `example.com` block and publish the same two files on that website, with `Content-Type: application/json` and the `Access-Control-Allow-Origin` header for the client file. Avoid 308 redirects in front of them: Synapse does not follow HTTP 308.

## Step 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:

```bash
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose
```

Check federation with the official [federation tester](https://matrix.org/federationtester): enter `example.com` and every check should pass. The same test is available as a JSON API:

```bash
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):

```bash
sudo register_new_matrix_user -c /etc/matrix-synapse/homeserver.yaml -c /etc/matrix-synapse/conf.d/registration.yaml
```

The 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:

```bash
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-web
```

Point Element at your homeserver by replacing `/etc/element-web/config.json` with a minimal configuration:

```bash
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"
    }
  }
}
EOF
```

Then 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:

```caddyfile
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
}
```

```bash
sudo systemctl reload caddy
curl -I https://element.example.com
```

Open `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:

```bash
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/backups
```

> **Warning**
>
> The signing key identifies your server to the whole federation, and local media may be the only copy anywhere. Keep the archive private and store a copy off the server.

To 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.

```bash
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-synapse
```

Then install Element Web (Step 7) and reload Caddy.

## Update Synapse

Synapse is updated through apt like any other package. Read the [upgrade notes](https://element-hq.github.io/synapse/latest/upgrade.html) 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:

```bash
sudo apt update
sudo apt upgrade
curl -s http://localhost:8008/_matrix/federation/v1/version
```

The 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](/guides/install-jitsi-meet); Element can use a Jitsi server for group calls.
- Compare Matrix with [Mattermost](/guides/install-mattermost) and [Rocket.Chat](/guides/install-rocket-chat).
- Learn more about the proxy used here in [Caddy reverse proxy](/guides/caddy-reverse-proxy).
- Read the official [Synapse documentation](https://element-hq.github.io/synapse/latest/) for workers, email, TURN for calls and the admin API.
- Compare servers for team chat on the [team chat hosting](/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.

---

Source: <https://hyperdc.com/guides/tutorials/install-matrix-synapse>\
Updated: 2026-10-09
