2026-01-05 13:03:55 +08:00
# Matrix ESS (Community) — Single‑ Node Install on Debian 13 (K3s + Traefik + cert‑ manager)
_Last updated: 2025-09-25 08:45 UTC_
This guide installs **Element Server Suite (ESS) Community** (Synapse + MAS + Element Web + Matrix RTC) on a **single Debian 13** node using **K3s** , **Traefik** (default in K3s), and **cert‑ manager** with **Let’ s Encrypt** . It is tailored to your domain choices:
- **serverName**: `chans.xyz`
- **Hosts**: `synapse.chans.xyz` , `account.chans.xyz` , `chat.chans.xyz` , `mrtc.chans.xyz`
> Tip: if you already have K3s and cert‑ manager installed and working, you can jump to **5. Values files** and **6. Install ESS**.
---
## 0) Requirements & Ports
- Debian 13 (root/sudo), public IPv4 (and optional IPv6).
- DNS control for `chans.xyz` .
- Open/forward these ports to this node:
- **80/tcp**, **443/tcp** (ACME + HTTPS + federation)
- **30881/tcp**, **30882/udp** (Matrix RTC SFU)
- Time in sync (`systemd-timesyncd` or equivalent).
---
## 1) DNS Setup
Create A/AAAA records that point to your node’ s public IP(s):
```
chans.xyz A / AAAA -> <YOUR_IP>
synapse.chans.xyz A / AAAA -> <YOUR_IP>
account.chans.xyz A / AAAA -> <YOUR_IP>
chat.chans.xyz A / AAAA -> <YOUR_IP>
mrtc.chans.xyz A / AAAA -> <YOUR_IP>
```
Notes:
- **Do not** use a `CNAME` at the **apex** (`chans.xyz` )—use `A/AAAA` . Subdomains can be `CNAME` s if you prefer.
- Federation relies on `https://chans.xyz/.well-known/matrix/server` which the chart serves for you.
---
## 2) (Optional) Cloud‑ Init (without firewalld)
If you build the node via cloud‑ init, this minimal config installs K3s & Helm and disables swap:
```yaml
#cloud-config
package_update : true
package_upgrade : true
packages : [ curl, ca-certificates, gnupg, lsb-release]
runcmd :
- swapoff -a
- sed -ri 's/^[^#].*\sswap\s/## &/g' /etc/fstab
- curl -sfL https://get.k3s.io | sh -s - server
- mkdir -p /home/windy/.kube
- cp /etc/rancher/k3s/k3s.yaml /home/windy/.kube/config
- chown windy:windy /home/windy/.kube/config && chmod 600 /home/windy/.kube/config
- bash -lc 'echo export KUBECONFIG=$HOME/.kube/config >> /home/windy/.bashrc'
- su - windy -c "curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash"
```
You can manage ports at the cloud firewall or your router (no `firewalld` required).
---
## 3) Manual K3s + Helm (if not using cloud‑ init)
```bash
# Install latest K3s
curl -sfL https://get.k3s.io | sh -s - server
# kubeconfig for your user (replace 'windy' if needed)
2026-08-08 17:27:24 +08:00
mkdir -p ~/.kube
2026-01-05 13:03:55 +08:00
sudo cp /etc/rancher/k3s/k3s.yaml ~windy/.kube/config
2026-08-08 17:27:24 +08:00
sudo chown windy:windy ~/.kube/config
chmod 600 ~/.kube/config
2026-01-05 13:03:55 +08:00
echo 'export KUBECONFIG=$HOME/.kube/config' | sudo tee -a ~windy/.bashrc
# Helm
sudo -iu windy bash -lc 'curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash'
```
Verify:
```bash
kubectl get nodes -o wide
kubectl get pods -A
```
You should see the node `Ready` and `traefik` running in `kube-system` .
---
## 4) cert‑ manager + Let’ s Encrypt (ClusterIssuer)
If you haven’ t installed cert‑ manager yet:
```bash
helm repo add jetstack https://charts.jetstack.io --force-update
kubectl create namespace cert-manager 2>/dev/null || true
helm install cert-manager jetstack/cert-manager -n cert-manager --set crds.enabled= true
```
Create a production ClusterIssuer (`letsencrypt-prod` ):
```yaml
# clusterissuer.yaml
apiVersion : cert-manager.io/v1
kind : ClusterIssuer
metadata :
name : letsencrypt-prod
spec :
acme :
server : https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef :
name : letsencrypt-prod-private-key
solvers :
- http01 :
ingress :
class : traefik
```
Apply:
```bash
kubectl apply -f clusterissuer.yaml
kubectl get clusterissuer
```
You should see `letsencrypt-prod READY=True` .
---
## 5) Values files (hosts + TLS)
Create the directory and values files:
```bash
mkdir -p ~/ess-config-values
```
** `~/ess-config-values/hostnames.yaml` **
```yaml
serverName : chans.xyz
elementWeb :
ingress :
host : chat.chans.xyz
synapse :
ingress :
host : synapse.chans.xyz
matrixAuthenticationService :
ingress :
host : account.chans.xyz
matrixRTC :
ingress :
host : mrtc.chans.xyz
```
** `~/ess-config-values/tls.yaml` **
```yaml
global :
ingress :
className : traefik
annotations :
cert-manager.io/cluster-issuer : letsencrypt-prod
tls :
enabled : true
issuer : letsencrypt-prod
```
> The TLS values ensure your Ingresses are annotated for cert‑ manager and include TLS host entries so Certificates are created automatically.
---
## 6) Install ESS (matrix‑ stack chart)
```bash
kubectl create namespace ess 2>/dev/null || true
helm upgrade --install ess oci://ghcr.io/element-hq/ess-helm/matrix-stack -n ess -f ~/ess-config-values/hostnames.yaml -f ~/ess-config-values/tls.yaml --wait
```
Check status:
```bash
kubectl get pods -n ess
kubectl get ingress -n ess
```
You should see ingresses for `synapse` , `account` , `chat` , `mrtc` , and `well-known` with `CLASS=traefik` .
---
## 7) Certificates issuance
Confirm the ingresses have TLS + issuer:
```bash
kubectl -n ess get ingress -o jsonpath = '{range .items[*]}{.metadata.name}{" issuer="}{.metadata.annotations.cert-manager\.io/cluster-issuer}{" tlsHosts="}{range .spec.tls[*].hosts}{.}{" "}{end}{"\n"}{end}'
```
Then watch certs:
```bash
kubectl get certificate -n ess
kubectl get order,challenge -n ess
```
When ready, confirm live certs:
```bash
for h in synapse.chans.xyz account.chans.xyz chat.chans.xyz mrtc.chans.xyz chans.xyz; do
echo "=== $h ==="
openssl s_client -connect " $h :443" -servername " $h " </dev/null 2>/dev/null | openssl x509 -noout -issuer -subject -dates
done
```
---
## 8) Well‑ Known verification (federation & clients)
```bash
curl -s https://chans.xyz/.well-known/matrix/server | jq .
curl -s https://chans.xyz/.well-known/matrix/client | jq .
```
Expected:
- `server` → `{ "m.server": "synapse.chans.xyz:443" }`
- `client` → `{ "m.homeserver": { "base_url": "https://synapse.chans.xyz" }, ... }`
Optional federation tester: <https://federationtester.matrix.org/#chans .xyz>
---
## 9) Create the first admin account
Interactive:
```bash
kubectl exec -n ess -it deploy/ess-matrix-authentication-service -- mas-cli manage register-user --admin
```
Non‑ interactive example:
```bash
kubectl exec -n ess deploy/ess-matrix-authentication-service -- mas-cli manage register-user --yes --admin --username admin --password 'CHANGE_ME_strong_password'
```
Login at **https://chat.chans.xyz** .
---
## 10) Enable self‑ registration (optional)
```yaml
# ~/ess-config-values/mas-registration.yaml
matrixAuthenticationService :
additional :
registration.yaml :
config : |
account:
password_registration_enabled: true
password_recovery_enabled: true
login_with_email_allowed: true
```
Apply (include this file):
```bash
helm upgrade --install ess oci://ghcr.io/element-hq/ess-helm/matrix-stack -n ess -f ~/ess-config-values/hostnames.yaml -f ~/ess-config-values/tls.yaml -f ~/ess-config-values/mas-registration.yaml --wait
```
---
## 11) Outbound email (MAS required, Synapse optional)
### 11.1 MAS SMTP (required for signup/reset)
**Option A — inline values (simple):**
```yaml
# ~/ess-config-values/mas-email.yaml
matrixAuthenticationService :
additional :
user-config.yaml :
config : |
email:
from: '"Matrix @ chans.xyz" <noreply@chans.xyz>'
reply_to: '"Support" <support@chans.xyz>'
transport: smtp
mode: starttls
hostname: smtp.windy.me
port: 587
username: noreply@chans.xyz # authenticate as the sender
password: "MAILBOX_PASSWORD"
account:
password_registration_enabled: true
password_recovery_enabled: true
login_with_email_allowed: true
```
**Option B — secret ref (keeps password out of Git):**
```bash
cat > /tmp/mas-user-config.yaml <<'YAML'
email:
from: '"Matrix @ chans.xyz" <noreply@chans.xyz>'
reply_to: '"Support" <support@chans.xyz>'
transport: smtp
mode: starttls
hostname: smtp.windy.me
port: 587
username: noreply@chans.xyz
password: "MAILBOX_PASSWORD"
account:
password_registration_enabled: true
password_recovery_enabled: true
login_with_email_allowed: true
YAML
kubectl -n ess create secret generic mas-extra-config --from-file= user-config.yaml= /tmp/mas-user-config.yaml
```
Then reference it:
```yaml
# ~/ess-config-values/mas-email-secretref.yaml
matrixAuthenticationService :
additional :
user-config.yaml :
configSecret : mas-extra-config
configSecretKey : user-config.yaml
```
Apply (include one of the two files above):
```bash
helm upgrade --install ess oci://ghcr.io/element-hq/ess-helm/matrix-stack -n ess -f ~/ess-config-values/hostnames.yaml -f ~/ess-config-values/tls.yaml -f ~/ess-config-values/mas-email.yaml --wait
# or replace mas-email.yaml with mas-email-secretref.yaml if you used a Secret
```
> **Mailcow 553 fix**: If authenticating as `zhiqiang@windy.me` and sending as `noreply@chans.xyz`, Mailcow rejects with `553 5.7.1 Sender address rejected`. Either (a) **authenticate as** `noreply@chans.xyz` by creating that mailbox in Mailcow and publishing SPF/DKIM/DMARC for `chans.xyz`; or (b) allow “send as” in Mailcow’ s **Sender ACL** for `zhiqiang@windy.me`. Hosting the `chans.xyz` mailbox gives best deliverability (DKIM/DMARC alignment).
Monitor while testing:
```bash
kubectl -n ess logs deploy/ess-matrix-authentication-service -f | grep -iE 'smtp|email|send'
```
### 11.2 Synapse email notifications (optional)
```yaml
# ~/ess-config-values/synapse-email.yaml
synapse :
additional :
email.yaml :
config : |
email:
smtp_host: "smtp.windy.me"
smtp_port: 587
smtp_user: "noreply@chans.xyz"
smtp_pass: "MAILBOX_PASSWORD"
require_transport_security: true
notif_from: "Matrix on chans.xyz <noreply@chans.xyz>"
enable_notifs: true
```
Include this file in your next Helm upgrade.
---
## 12) Health checks & troubleshooting
**Basic:**
```bash
kubectl get pods,svc,ingress,certificate -n ess -o wide
```
**Certs flow:**
```bash
kubectl get certificate,order,challenge -n ess
kubectl describe challenge -n ess <name>
kubectl logs -n kube-system deploy/traefik --tail= 200
```
**Well‑ known + federation:**
```bash
curl -s https://chans.xyz/.well-known/matrix/server | jq .
curl -s https://chans.xyz/.well-known/matrix/client | jq .
```
**Common pitfalls:**
- Ingresses lack TLS + `cert-manager.io/cluster-issuer` → fix `tls.yaml` .
- `553 Sender address rejected` from Mailcow → align SMTP auth user with sender or allow “send as”, and set SPF/DKIM/DMARC for `chans.xyz` .
- Port 80 blocked → Let’ s Encrypt HTTP‑ 01 fails (check challenges).
- Apex `chans.xyz` not pointing at the node → `.well-known` fails → federation fails.
---
## 13) Upgrades / Uninstall
Upgrade to latest chart:
```bash
helm repo update # if using repos
helm upgrade --install ess oci://ghcr.io/element-hq/ess-helm/matrix-stack -n ess -f ~/ess-config-values/hostnames.yaml -f ~/ess-config-values/tls.yaml --wait
```
Uninstall ESS (keeps PVCs unless you delete them):
```bash
helm uninstall ess -n ess
kubectl delete namespace ess
```
Reset K3s (if ever needed):
```bash
sudo /usr/local/bin/k3s-uninstall.sh
```
---
## 14) Quick copy‑ paste checklist
1. DNS A/AAAA for: `chans.xyz` , `synapse.` , `account.` , `chat.` , `mrtc.` → your IP.
2. K3s running with Traefik; cert‑ manager installed; `ClusterIssuer letsencrypt-prod` **Ready** .
3. `hostnames.yaml` with `*.ingress.host` set to your subdomains.
4. `tls.yaml` with `global.ingress.annotations.cert-manager.io/cluster-issuer=letsencrypt-prod` and TLS enabled.
5. `helm upgrade --install ess …` with both files.
6. `kubectl get certificate -n ess` → `READY=True` .
7. `/.well-known` returns correct JSON; federation tester OK.
8. Create admin via MAS CLI; log in at `https://chat.chans.xyz` .
9. Configure SMTP for MAS (and optionally Synapse), fix Mailcow sender policy if needed.
2026-08-08 17:27:24 +08:00
matrix hermes token:
```
mpt_RHPPoHqhXYbhkZyDaBT5H6Bd0plq4V_p3woK4
```