# Deploying Jitsi Meet Behind Traefik with Docker Compose (Custom SSL PEM/KEY + Host Authentication)

This guide walks through how we deployed **Jitsi Meet** using **Docker Compose**, placed it **behind Traefik**, terminated **TLS using our own certificate** (`.pem` + `.key`) via Traefik’s **File Provider**, and enabled **Host Authentication** using **internal auth** (Prosody users).

---

## 1) Target Setup and Architecture

Goals:

* Traefik is already listening on **443** (used by other services).
    
* Jitsi must run **behind Traefik** and not take over port 443 directly.
    
* TLS must be served using a **custom certificate** (not Let’s Encrypt).
    
* Enable **Host Authentication** (internal auth), so only authenticated users can host/start meetings (and optionally allow guests to join).
    

Traffic flow:

```yaml
Internet (https://meet.shonizcloud.ir)
          |
          v
     Traefik :443    (TLS termination using custom cert)
          |
          v
    Jitsi Web :80     (inside Docker network)
          |
          +--> Prosody / Jicofo / JVB ...
```

---

## 2) Get the Official Jitsi Docker Compose Files (Release ZIP)

We keep all Docker-based apps under a single parent folder so backup is easy (zip the parent folder).

Create the folder structure:

```bash
mkdir -p docker/jitsi
cd docker/jitsi
```

Download the latest official `docker-jitsi-meet` release ZIP:

```bash
wget $(curl -s https://api.github.com/repos/jitsi/docker-jitsi-meet/releases/latest | grep 'zip' | cut -d\" -f4)
```

Install `unzip`:

### Ubuntu / Debian

```bash
sudo apt install unzip
```

### RedHat / CentOS / Fedora

```bash
sudo dnf install unzip
```

Unzip the downloaded release (replace `<version>` with the actual filename you downloaded):

```bash
unzip ./stable-<version>
```

Rename the extracted folder to something clean:

```bash
mv jitsi-docker-jitsi-meet-<hash-or-version> jitsi-meet
cd jitsi-meet
```

---

## 3) Copy Environment and Compose Files

Create your working `.env` from the example:

```bash
cp env.example .env
```

Copy the compose file to the newer naming convention (`compose.yaml`) and back up the original:

```bash
cp docker-compose.yml compose.yaml
mv docker-compose.yml docker-compose.yml.bak
```

---

## 4) Edit `.env` (Config Path, Domain, IPs, and Internal Authentication)

Open `.env`:

```bash
nano .env
```

### 4.1 Put the config volume inside the project directory

This keeps all persistent configuration under the project folder (easy backups):

```yaml
CONFIG=./.jitsi-meet-cfg
```

### 4.2 Ports

If the default ports (`8000`, `8443`) are free, you can keep them. If other apps use them, change them here and remember the values for your reverse proxy design.

### 4.3 Timezone

Set the correct timezone for your server, for example:

```yaml
TZ=Europe/Berlin
```

### 4.4 PUBLIC\_URL

This is very important. It must match the **external URL** users will browse to:

```yaml
PUBLIC_URL=https://meet.shonizcloud.ir
```

### 4.5 JVB\_ADVERTISE\_IPS

For NAT or multi-path access scenarios, you can set:

```yaml
JVB_ADVERTISE_IPS=192.168.21.14,209.41.5.158
```

**Very important note:**  
The **public IP** you put here must be **the exact public IP address that external users see and connect to** (i.e., the Internet/WAN-facing IP).  
If your server is behind NAT/firewall, this should **not** be your internal/private IP—use the **public (NATed) IP** that inbound traffic actually reaches. Optionally, you can include your private LAN IP first, then the public IP.

### 4.6 Enable Internal Authentication (Host Authentication)

In the Authentication section, enable:

```yaml
ENABLE_AUTH=1
ENABLE_GUESTS=1
AUTH_TYPE=internal
```

* With `ENABLE_GUESTS=1`, unauthenticated users can join once a host starts the meeting.
    
* If you don’t want guests at all, do not enable `ENABLE_GUESTS`.
    

### 4.7 Generate Strong Internal Passwords

Exit the editor and run:

```bash
./gen-passwords.sh
```

Re-open `.env` and verify the Security section got populated with strong random passwords.

### 4.8 Enable Restart Policy

Set:

```yaml
RESTART_POLICY=unless-stopped
```

---

## 5) Pull Images and Start Jitsi

Pull all images:

```bash
docker compose pull
```

Start the stack and follow logs:

```bash
docker compose up -d && docker compose logs -f
```

---

## 6) Create Prosody Users for Authentication (Recommended: Use Default `meet.jitsi`)

If you try to create a Prosody user and see:

```
The given hostname does not exist in the config
```

it means the domain you used is not a configured `VirtualHost` in Prosody (or you are pointing `prosodyctl` at the wrong config).

To see the configured VirtualHosts:

```bash
docker compose exec prosody sh -lc "grep -RIn 'VirtualHost' /config | head -n 50"
```

In `docker-jitsi-meet`, VirtualHosts are commonly defined here:

* `/config/conf.d/jitsi-meet.cfg.lua`
    

> **Recommendation:** To avoid unnecessary complexity with XMPP domain changes, create users using the default internal XMPP domain `meet.jitsi`, even if your external web domain is [`meet.shonizcloud.ir`](http://meet.shonizcloud.ir).

Create a user like this:

```bash
docker compose exec prosody prosodyctl --config /config/conf.d/jitsi-meet.cfg.lua \
  register mahdi meet.jitsi mahdi123
```

---

## 7) Put Jitsi Behind Traefik

In this design, Traefik handles HTTPS and forwards traffic internally to Jitsi over HTTP (port 80 inside the Docker network).

---

## 8) Full Traefik Configuration for Custom SSL (PEM/KEY) Using File Provider

We disabled Let’s Encrypt for this site and used our own certificate.

### 8.1 Traefik Docker Compose (full config used)

```yaml
services:
  traefik:
    image: "docker-mirror.kubarcloud.com/traefik"
    restart: always
    command:
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--providers.file.filename=/dynamic/tls.yml"
      - "--providers.file.watch=true"
      - "--entrypoints.web.address=:80"
     # - "--entrypoints.web.http.redirections.entryPoint.to=websecure"
     # - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
      - "--entrypoints.websecure.address=:443"
    #  - "--certificatesresolvers.mytlschallenge.acme.tlschallenge=true"
    #  - "--certificatesresolvers.mytlschallenge.acme.email=mahdishadi99@gmail.com"
    #  - "--certificatesresolvers.mytlschallenge.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"
    networks:
      - radar
    volumes:
      - traefik_data:/letsencrypt
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /root/Dashboard-Internet/dynamic:/dynamic:ro
      - /root/Jitsi-meet/keys:/etc/traefik/certs:ro
```

Notes:

* `--providers.file.filename=/dynamic/tls.yml` tells Traefik to load TLS certs from this file.
    
* We mount the cert directory so Traefik can read the PEM/KEY inside the container:
    
    * `/root/Jitsi-meet/keys` → `/etc/traefik/certs`
        

### 8.2 TLS config file (`tls.yml`)

Path on the host:

`/root/Dashboard-Internet/dynamic/tls.yml`

Content:

```yaml
tls:
  certificates:
    - certFile: /etc/traefik/certs/meet.shonizcloud.ir.pem
      keyFile: /etc/traefik/certs/meet.shonizcloud.ir.key
```

This means the following files must exist on the host:

* `/root/Jitsi-meet/keys/`[`meet.shonizcloud.ir`](http://meet.shonizcloud.ir)`.pem`
    
* `/root/Jitsi-meet/keys/`[`meet.shonizcloud.ir`](http://meet.shonizcloud.ir)`.key`
    

---

## 9) Jitsi Labels for Traefik (HTTPS + Redirect + Security Headers)

Apply these labels to the **Jitsi web** service to route [`meet.shonizcloud.ir`](http://meet.shonizcloud.ir) through Traefik:

```yaml
labels:
  - traefik.enable=true
  - traefik.http.routers.meet.rule=Host(`meet.shonizcloud.ir`)
  - traefik.http.routers.meet.entrypoints=websecure
  - traefik.http.routers.meet.tls=true
  - traefik.http.routers.meet.middlewares=meet-headers@docker
  - traefik.http.services.meet-svc.loadbalancer.server.port=80

  - traefik.http.middlewares.meet-headers.headers.STSSeconds=315360000
  - traefik.http.middlewares.meet-headers.headers.forceSTSHeader=true
  - traefik.http.middlewares.meet-headers.headers.STSIncludeSubdomains=true
  - traefik.http.middlewares.meet-headers.headers.STSPreload=true
  - traefik.http.middlewares.meet-headers.headers.browserXSSFilter=true
  - traefik.http.middlewares.meet-headers.headers.contentTypeNosniff=true

  - traefik.http.routers.meet-http.rule=Host(`meet.shonizcloud.ir`)
  - traefik.http.routers.meet-http.entrypoints=web
  - traefik.http.routers.meet-http.middlewares=meet-redirect@docker
  - traefik.http.middlewares.meet-redirect.redirectscheme.scheme=https
```

Key points:

* `websecure` router serves HTTPS.
    
* Separate `web` router redirects HTTP → HTTPS.
    
* `loadbalancer.server.port=80` tells Traefik to forward to the Jitsi web container on port 80.
    

---

## 10) Verify the Certificate Served by Traefik

To confirm Traefik is presenting your custom certificate:

```bash
echo | openssl s_client -connect meet.shonizcloud.ir:443 -servername meet.shonizcloud.ir 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates
```

---

## 11) Summary

What we achieved:

* Downloaded and deployed `docker-jitsi-meet` from the latest official release ZIP
    
* Organized the project for easy backup (`docker/jitsi/jitsi-meet`)
    
* Configured `.env` (including internal auth + generated strong passwords)
    
* Started the Jitsi stack using Docker Compose
    
* Recommended creating Prosody users on the default XMPP domain `meet.jitsi`
    
* Configured Traefik to terminate TLS using a custom PEM/KEY certificate via File Provider
    
* Added complete Traefik labels to route Jitsi over HTTPS and redirect HTTP to HTTPS
