Here we are going to install a Synapse server using docker + nginx + cloudflare argo tunnel + oauth.

In this part we'll deploy a working Synapse server, so you don't need to wait for other parts, if it's enough to you. Keep in mind that in this way you will expose your real server's IP.


  • a linux server with docker
  • another linux server with nginx as reverse proxy (not mandatory, but good practices say that's a good choice)
  • a cloudflare account (free tier)
  • a google account (free tier), mastodon, github, or whatever to provide oauth backend
  • a domain pointing to your nginx proxy (for example mine is If you want to use argo tunnels, domain's DNS must be managed by cloudflare.

Install Synapse

Let's ssh on our linux server with docker and let's deploy the synapse's docker-compose.yaml

# mkdir /opt/vector
# cd /opt/vetor
# docker network create matrix
# vim docker-compose.yaml

Here is the docker-compose.yaml content:

version: "3.3"
        image: ""
          - default
          - matrix
        restart: always
        container_name: "matrix"
            - "./data:/data"
            VIRTUAL_HOST: ""
            VIRTUAL_PORT: 8008
            SYNAPSE_SERVER_NAME: ""
            SYNAPSE_REPORT_STATS: "yes"
            - "9008:8008/tcp"  # <-- Change these if your ports are already busy
            - "8448:8448/tcp" # <-- Change these if you ports are already busy
        - postgresql
        image: postgres:15.5-bullseye
        restart: always
          - default
            POSTGRES_USER: synapse
            POSTGRES_DB: synapse
            POSTGRES_INITDB_ARGS: "--encoding='UTF8' --lc-collate='C' --lc-ctype='C'"
            - "./data/postgresdata:/var/lib/postgresql/data"
    external: true

Now we have to generate the very first config file:

# docker run -it --rm -v ./data:/data -e -e SYNAPSE_REPORT_STATS=yes generate

Let's get into data and let's modify homeserver.yaml

# cd data
# vim homeserver.yaml

Here is its content:

# Configuration file for Synapse.
# This is a YAML file: see [1] for a quick introduction. Note in particular
# that *indentation is important*: all the elements of a list or dictionary
# should have the same indentation.
# [1]
# For more information on how to configure Synapse, including a complete accounting of
# each option, go to docs/usage/configuration/ or
server_name: ""
public_baseurl: ""
pid_file: /data/
  - port: 8008
    tls: false
    type: http
    x_forwarded: true
      - names: [client, federation]
        compress: false
    name: psycopg2
        user: synapse
        password: __CHANGEME__ # <-- this has to be the same as the one in docker-compose.yaml
        host: postgresql
        database: synapse
        cp_min: 5
        cp_max: 10

allow_public_rooms_over_federation: true 
#federation_domain_whitelist:  # <-- remove the comment if you want to whitelist only some domains. If the list is empty, no federation is allowed
enable_registration: false # <-- registration with mail is inhibited by now.

log_config: "/data/"
media_store_path: /data/media_store
registration_shared_secret: "__REDACTED__"
report_stats: true
macaroon_secret_key: "__REDACTED__"
form_secret: "__REDACTED__"
signing_key_path: "/data/"
  - server_name: ""
email: #< -- This block is not mandatory: it's just to send mail in case of registration, lost password and such
  smtp_host: ""
  smtp_port: 587
  smtp_user: "__REDACTED__"
  smtp_pass: "__REDACTED__"
  require_transport_security: true
  notif_from: "[email protected]"
  app_name: cyberveins
supress_key_server_warning: true

Let's get back into docker-compose.yaml directory and let's fire up the instance

# cd ../
# docker compose up -d; docker compose logs -f 

Check that everything's ok and press CTRL+C.

Now let's ssh on the nginx's server and let's create a site config file:

# cd /etc/nginx/sites-available
# vim

Here is the content:

server {
  listen 80;
  return 301 https://$server_name$request_uri;

server {
   listen 443 ssl http2;
   #listen 8448 ssl http2;

   http2_push_preload on; # Enable HTTP/2 Server Push

   ssl_certificate /etc/letsencrypt/live/;
   ssl_certificate_key /etc/letsencrypt/live/;
   ssl_session_timeout 1d;
   access_log /var/log/nginx/;
   error_log /var/log/nginx/;
   ssl_protocols TLSv1.2 TLSv1.3;
   ssl_early_data on;

   ssl_prefer_server_ciphers on;
   ssl_session_cache shared:SSL:50m;
   add_header Strict-Transport-Security max-age=15768000;

   ssl_stapling on;
   ssl_stapling_verify on;
  location ^~ /.well-known {
          root /var/www/html/matrix;
 location ~ ^(/_matrix|/_synapse/client|/_synapse/admin) {
        proxy_pass; # <-- This is the server that is running synapse. If it's on the same host, write;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $host;
        client_max_body_size 50M;
    proxy_http_version 1.1;

We don't want to expose port 8008 nor 8448 for federation, we want to expose only port 443, thus:

# mkdir -p /var/www/html/matrix/.well-known/matrix
# cd /var/www/html/matrix/.well-known/matrix
# vim server

It's content is:

    "m.server": ""

This will inform other servers that our federation port is 443.

Let's request a letsencrypt certificate (YES even if you're behind argo tunnel, if you want to provide oauth):

# certbot certonly --nginx --preferred-challenges http -d
# ln -sf /etc/nginx/sites-available/ /etc/nginx/sites-enabled
# nginx -t

If it's all ok:

# systemctl restart nginx 

Now let's get back in the synapse server and create our admin user:

# docker exec -it matrix register_new_matrix_user -c /data/homeserver.yaml -u admin -p _PASSWORD_ 

At this point you can use your brand new synapse server with the client you prefer. On next arcticles we'll configure the rest (that is argo tunnel followed by oauth providers).

Previous Post