Skip to content

Instantly share code, notes, and snippets.

@ma5ter
Created March 6, 2026 10:40
Show Gist options
  • Select an option

  • Save ma5ter/c82bb575771e0d4a91584c381eee7a41 to your computer and use it in GitHub Desktop.

Select an option

Save ma5ter/c82bb575771e0d4a91584c381eee7a41 to your computer and use it in GitHub Desktop.

Custom Matrix Node without Docker and other bloats

This guide is focused on modern approach entirely skipping outdated TURN services for media, so only modern clients like Element X are supported.

The official guide is here https://github.com/element-hq/element-call/blob/livekit/docs/self-hosting.md

Prerequisites

  • rust
  • go
  • clang
  • nginx
  • mage (yet another kind of go build system)

When on Gentoo simply emerge:

emerge -av dev-lang/rust dev-lang/go sys-devel/clang www-servers/nginx

And install mage

cd /usr/src
git clone --depth 1 https://github.com/magefile/mage
cd mage
go run bootstrap.go
mv ~/go/bin/mage /usr/local/bin/

Installation

You need three services:

  • Matrix API engine (chats and other except live media)
  • SFU
  • RTC auth service

Tuwunel (Matrix API engine)

Build and install

cd /usr/src
git clone --depth 1 https://github.com/matrix-construct/tuwunel.git
cd tuwunel
LIBCLANG_PATH=/usr/lib/llvm/21/lib64/ cargo build --release --jobs 2
install -Dm755 target/release/tuwunel /usr/local/bin/tuwunel

Configure

Inherit default config

cp tuwunel-example.toml /etc/tuwunel.toml 

Change these values

< #server_name =
> server_name = "matrix.your.domain"

< #unix_socket_path =
> unix_socket_path = "/run/tuwunel/tuwunel.sock"

< #[global.well_known]
> [global.well_known]

< #client =
> client = "https://matrix.your.domain"

< #server =
> server = "matrix.your.domain:443"

< #rtc_transports = []
> rtc_transports = [{ type = "livekit", livekit_service_url = "https://matrix.your.domain/livekit/jwt" }]

Additionally, allow registration for the first (admin) user, disable later

< #allow_registration = false
> allow_registration = true

< #registration_token =
> registration_token = "some_secret_token_to_register_users"

Create runscript or whatever you use on your bubuntu

cat << 'EOF' > /etc/init.d/tuwunel
#!/sbin/openrc-run

name="Tuwunel Matrix homeserver"
command="/usr/local/bin/tuwunel"
command_args="-c /etc/tuwunel.toml"
command_background=true
pidfile="/run/${RC_SVCNAME}.pid"
command_user="tuwunel:nginx"
directory="/var/lib/tuwunel"

export TOKIO_WORKER_THREADS=4

logfile="/var/log/${RC_SVCNAME}.log"
output_log="${logfile}"
error_log="${logfile}"

start_pre() {
	checkpath --file --owner $command_user --mode 0644 "${logfile}"
	checkpath --directory --owner $command_user -m 0750 /run/${RC_SVCNAME}
}
EOF

chmod +x /etc/init.d/tuwunel

Create user and dirs

useradd -r -u 501 -s/sbin/nologin -d/var/lib/tuwunel tuwunel
mkdir -p /var/lib/tuwunel
chown tuwunel:tuwunel /var/lib/tuwunel
chmod 0750 /var/lib/tuwunel

Start service

rc-update add tuwunel default
rc-service tuwunel start

Check logs

cat /var/log/tuwunel.log

Livekit (SFU)

Build and install

cd /usr/src
git clone --depth 1 https://github.com/livekit/livekit-server.git
cd livekit-server
go mod download
./install-livekit.sh

Configure

cat << 'EOF' > /etc/livekit.yaml
port: 7880

bind_addresses:
 - "127.0.0.1"

rtc:
  tcp_port: 7881
  port_range_start: 50000
  port_range_end: 50100
  # use_external_ip: true # when behind NAT
  node_ip: 1.2.3.4 # direct

keys:
  element: <super_secret_key>

room:
  auto_create: false
EOF

chown chown livekit:livekit /etc/livekit.yaml
chmod 0600 /etc/livekit.yaml

More options here: https://github.com/livekit/livekit/blob/master/config-sample.yaml

Create runscript

cat << 'EOF' > /etc/init.d/livekit
#!/sbin/openrc-run

name="LiveKit RTC server"
command="/usr/local/bin/livekit-server"
command_args="--config /etc/livekit.yaml --dev"
command_background=true
pidfile="/run/${RC_SVCNAME}.pid"
command_user="livekit:livekit"
directory="/var/lib/livekit"

logfile="/var/log/${RC_SVCNAME}.log"
output_log="${logfile}"
error_log="${logfile}"

start_pre() {
	checkpath --file --owner $command_user --mode 0644 "${logfile}"
}
EOF

chmod +x /etc/init.d/livekit

Create user and dirs

useradd -r -u 502 -s/sbin/nologin -d/var/lib/livekit livekit
mkdir -p /var/lib/livekit
chown livekit:livekit /var/lib/livekit
chmod 0750 /var/lib/livekit

Start service

rc-update add livekit default
rc-service livekit start

Check logs

cat /var/log/livekit.log

Livekit-JVT (RTC auth service)

Build and install

git clone https://github.com/element-hq/lk-jwt-service.git
cd lk-jwt-service
go build -o lk-jwt-service
install -Dm755 lk-jwt-service /usr/local/bin/lk-jwt-service

Create runscript and config

Don't be lazy and separate configuration variables to /etc/conf.d/lk-jwt

cat << 'EOF' > /etc/init.d/lk-jwt
#!/sbin/openrc-run

name="LiveKit JWT Auth service"
command="/usr/local/bin/lk-jwt-service"
command_background=true
pidfile="/run/${RC_SVCNAME}.pid"
command_user="livekit:livekit"
directory="/var/lib/livekit"

export LIVEKIT_URL="wss://matrix.your.domain/livekit/sfu"
export LIVEKIT_KEY="element"
export LIVEKIT_SECRET="<super_secret_key>"
export LIVEKIT_FULL_ACCESS_HOMESERVERS="matrix.your.domain"
export LIVEKIT_JWT_BIND="127.0.0.1:7882"

logfile="/var/log/${RC_SVCNAME}.log"
output_log="${logfile}"
error_log="${logfile}"

start_pre() {
	checkpath --file --owner $command_user --mode 0644 "${logfile}"
}
EOF

chmod 0750 /etc/init.d/lk-jwt # prevent access to the LIVEKIT_SECRET

Start service

rc-update add lk-jwt default
rc-service lk-jwt start

Check logs

cat /var/log/lk-jwt.log

Configure host and proxy

Add this server config to nginx

# Matrix homeserver reverse proxy
server {
    listen 0.0.0.0:443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name matrix.your.domain;

    ssl_certificate /etc/letsencrypt/live/your.domain/fullchain.pem; 
    ssl_certificate_key /etc/letsencrypt/live/your.domain/privkey.pem; 

    access_log /var/log/nginx/matrix.your.domain.ssl_access_log main;
    error_log /var/log/nginx/matrix.your.domain.ssl_error_log info;

    # LiveKit JWT auth endpoint -- proxy to lk-jwt http port
    location ^~ /livekit/jwt/ {
        proxy_pass http://127.0.0.1:7882/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # LiveKit SFU websocket connection running at port 7880
    location ^~ /livekit/sfu/ {
        proxy_pass http://127.0.0.1:7880/;

        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_send_timeout 120;
        proxy_read_timeout 120;
        proxy_buffering off;

        proxy_set_header Accept-Encoding gzip;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

    }

    # Matrix API -- proxy to tuwunel unix socket
    location / {
        proxy_pass http://unix:/run/tuwunel/tuwunel.sock;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
    }
}

Reload nginx config after adding new endpoints.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment