These tutorials are written by Freedom Lab members in their free time. If you find them helpful, please consider supporting our work.

TutorialsSelf-Hosted Matrix Server

Self-Hosted Matrix Server

Self-hosting Privacy Linux

Run your own Matrix homeserver with Synapse for private, federated communication.

This guide covers installing Synapse on a Debian/Ubuntu VPS with PostgreSQL, Nginx, and Element Web.

Prerequisites

Secure SSH Access

After logging in, update your system:

sudo apt update && sudo apt upgrade -y

Edit the SSH configuration:

sudo vim /etc/ssh/sshd_config

Change the following settings (uncomment if needed):

Port 22222
PasswordAuthentication no
UsePAM no

Restart SSH:

sudo systemctl restart sshd

Setup Firewall

sudo apt install ufw -y
sudo ufw allow 22222/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 8448/tcp
sudo ufw enable

Install Dependencies

sudo apt install -y build-essential python3-dev libffi-dev \
    python3-pip python3-setuptools sqlite3 \
    libssl-dev virtualenv libjpeg-dev libxslt1-dev

Install Synapse

Add the Matrix repository and install Synapse:

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 -y matrix-synapse-py3

When prompted, enter your server name (e.g., matrix.example.com).

Setup PostgreSQL

Install PostgreSQL:

sudo apt install -y libpq5 postgresql postgresql-contrib

Create the database and user:

sudo -u postgres createuser --pwprompt synapse_user
sudo -u postgres createdb --encoding=UTF8 --locale=C --template=template0 --owner=synapse_user synapse

Edit the Synapse configuration:

sudo vim /etc/matrix-synapse/homeserver.yaml

Find the database section and replace it with:

database:
  name: psycopg2
  args:
    user: synapse_user
    password: YOUR_DATABASE_PASSWORD
    dbname: synapse
    host: 127.0.0.1
    cp_min: 5
    cp_max: 10

Also uncomment and set the server_name:

server_name: "matrix.example.com"

Restart Synapse:

sudo systemctl restart matrix-synapse

Verify PostgreSQL is working:

psql -h localhost -U synapse_user -d synapse -W -c "\dt"

You should see Matrix tables listed.

Setup Nginx and TLS

Install Certbot and Nginx:

sudo apt install -y certbot python3-certbot-nginx nginx

Obtain a TLS certificate:

sudo certbot certonly --nginx -d matrix.example.com

Test certificate renewal:

sudo certbot renew --dry-run

Create the Nginx configuration:

sudo vim /etc/nginx/sites-available/matrix

Add the following (replace matrix.example.com with your domain):

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    # For the federation port
    listen 8448 ssl default_server;
    listen [::]:8448 ssl default_server;

    server_name matrix.example.com;
    ssl_certificate /etc/letsencrypt/live/matrix.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/matrix.example.com/privkey.pem;

    location ~ ^(/_matrix|/_synapse/client) {
        # note: do not add a path (even a single /) after the port in `proxy_pass`,
        # otherwise nginx will canonicalise the URI and cause signature verification
        # errors.
        proxy_pass http://localhost:8008;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $host:$server_port;

        # Nginx by default only allows file uploads up to 1M in size
        # Increase client_max_body_size to match max_upload_size defined in homeserver.yaml
        client_max_body_size 50M;

        # Synapse responses may be chunked, which is an HTTP/1.1 feature.
        proxy_http_version 1.1;
    }

    # Element Web interface
    location /element-web {
        alias /var/www/element;
        try_files $uri $uri/ /element-web/index.html;

        # Password protection (optional, see below)
        # auth_basic "Private Element";
        # auth_basic_user_file /etc/nginx/.htpasswd;
    }
}

Enable the site:

sudo ln -s /etc/nginx/sites-available/matrix /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Test the endpoints:

curl https://matrix.example.com/_matrix/client/versions -s | jq .
curl https://matrix.example.com:8448/_matrix/key/v2/server -s | jq .

Create Admin User

Add a registration shared secret to the config:

sudo vim /etc/matrix-synapse/homeserver.yaml

Add at the bottom (generate a secure random string):

registration_shared_secret: "YOUR_SECURE_RANDOM_STRING"

Restart Synapse:

sudo systemctl restart matrix-synapse

Create a new user:

register_new_matrix_user -c /etc/matrix-synapse/homeserver.yaml

Follow the prompts to set username, password, and admin status.

Your Matrix ID will be @username:matrix.example.com.

Complete homeserver.yaml Reference

Here's a complete example of /etc/matrix-synapse/homeserver.yaml with all the settings from this guide:

# Configuration file for Synapse.
# See https://element-hq.github.io/synapse/latest/usage/configuration/config_documentation.html

server_name: "matrix.example.com"
pid_file: "/var/run/matrix-synapse.pid"

listeners:
  - port: 8008
    tls: false
    type: http
    x_forwarded: true
    bind_addresses: ['::1', '127.0.0.1']
    resources:
      - names: [client, federation]
        compress: false

database:
  name: psycopg2
  args:
    user: synapse_user
    password: "YOUR_DATABASE_PASSWORD"
    dbname: synapse
    host: 127.0.0.1
    cp_min: 5
    cp_max: 10

log_config: "/etc/matrix-synapse/log.yaml"
media_store_path: /var/lib/matrix-synapse/media
signing_key_path: "/etc/matrix-synapse/homeserver.signing.key"

trusted_key_servers:
  - server_name: "matrix.org"

registration_shared_secret: "YOUR_SECURE_RANDOM_STRING"

Install Element Web

Download and extract Element Web:

cd /var/www
sudo wget https://github.com/element-hq/element-web/releases/download/v1.12.1/element-v1.12.1.tar.gz
sudo tar -xzf element-v1.12.1.tar.gz
sudo mv element-v1.12.1 element
cd element
sudo cp config.sample.json config.json

Edit the configuration:

sudo vim config.json

Update the homeserver settings:

{
    "default_server_config": {
        "m.homeserver": {
            "base_url": "https://matrix.example.com",
            "server_name": "matrix.example.com"
        }
    },
    "disable_custom_urls": true,
    "disable_guests": true
}

Reload Nginx:

sudo systemctl reload nginx

Access Element Web at https://matrix.example.com/element-web

Optional: Password Protect Element Web

Add HTTP basic authentication:

sudo apt install -y apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd yourusername

Update the Nginx configuration:

sudo vim /etc/nginx/sites-available/matrix

Modify the Element Web location block:

location /element-web {
    alias /var/www/element;
    try_files $uri $uri/ /element-web/index.html;

    auth_basic "Private Element";
    auth_basic_user_file /etc/nginx/.htpasswd;
}

Reload Nginx:

sudo systemctl reload nginx

Summary

Your Matrix server is now running with:

Users connect with their Matrix ID: @username:matrix.example.com