Self-Hosted Matrix Server
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
- A VPS running Ubuntu 24.04 (recommended) or Debian
- A domain name pointing to your VPS (e.g.,
matrix.example.com) - SSH access to the server
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:
- Synapse on port 8008 (internal)
- Nginx on ports 443 (clients) and 8448 (federation)
- PostgreSQL for the database
- Element Web at
/element-web
Users connect with their Matrix ID: @username:matrix.example.com