Hi,
I wanted to post this in General but there was no option to use markdown to strucure my post. My original idea was to upload a zip file which is not allowed so I used markdown in support category. Feel free to delete/move this post ![]()
Over the last few months, I used a lot of information from this community to get my Nextcloud installation working. This is why I would like to share with you my template for a Nextcloud docker installation with custom port (not 443), nginx as webserver and HPB for Talk. I also tried to get rid of as many warnings as possible in the Admin page. With Talk version 24.0.3 I get a warning for a missing changed-users capability of my HPB. Maybe that gets fixed with a new version of the strukturag/nextcloud-spreed-signaling image.
Anyway, this template is not designed for production nor should it be used if you have no issue opening port 443/80 on your firewall. Use the AIO image or traefik or Caddy instead.
Feel free to remove any IPv6 or http/3 related entries in the compose.yaml and nginx.conf files if you don’t need it. I had issues with http/3 and Firefox so I removed the exposure of the custom UDP port in the compose.yaml in my installation again.
The following assumes that you have a way of getting valid certificates (I used certboot with DNS challenge and a deployment hook to restart nginx when the certificates get renewed) and that you have a working docker installation with docker compose. Set up fail2ban after the Nextcloud installation for security reasons.
I tested this template with Ubuntu 24.04.4 LTS and I tested a talk session with 4 video clients (LAN, WIFI and mobile internet).
Because I don’t want to use port 443 for my installation, I was not able to use the AIO image for Talk. I used the images: strukturag/nextcloud-spreed-signaling, canyan/janus-gateway and nats instead. These images require various keys which is why I created a template to fill in the secrets, my nextcloud domain and the custom port in the various configuration files.
The template is structured like this:
Nextcloud_Template/
|-- Pre_Install.txt
#Make sure that the docker host has a static IP
#On docker host execute
sudo curl -L https://ssl-config.mozilla.org/ffdhe2048.txt -o /etc/dhparam
sudo sysctl -w vm.overcommit_memory=1
#change directory to Template folder
cd Nextcloud_Template
#Change nextcloud domain and port in file template.txt
nano template.txt
#Generate secrets and keys
cat >> template.txt <<EOF
DOCKER_HOST_IP=$(ip -4 route get 1.1.1.1 | awk '{print $7}')
MYSQL_ROOT_PASSWORD=$(openssl rand -base64 48 | tr -dc 'A-Za-z0-9' | head -c 32)
TURN_SECRET=$(openssl rand -hex 32)
SIGNALLING_SECRET=$(openssl rand -hex 32)
HASH_KEY=$(openssl rand -hex 32)
BLOCK_KEY=$(openssl rand -hex 16)
API_KEY=$(openssl rand -base64 16)
HARP_SECRET=$(openssl rand -base64 48 | tr -dc 'A-Za-z0-9' | head -c 32)
EOF
#Make .sh file executable
chmod +x fillTemplate.sh
#Fill in templates and copy files to Nextcloud installation folder
./fillTemplate.sh templates <NextcloudInstFolder>
|-- fillTemplate.sh
####################################################
# Usage:
# ./copy-configs.sh <source_directory> <target_directory>
####################################################
if [ $# -ne 2 ]; then
echo "Usage: $0 <source_directory> <target_directory>"
exit 1
fi
SOURCE="$(realpath "$1")"
TARGET="$(realpath "$2")"
CONFIG_FILE="./template.txt"
# Check input
if [ ! -d "$SOURCE" ]; then
echo "Source directory not found: $SOURCE"
exit 1
fi
if [ ! -f "$CONFIG_FILE" ]; then
echo "Configuration file not found: $CONFIG_FILE"
exit 1
fi
if ! command -v envsubst >/dev/null 2>&1; then
echo "envsubst not found."
echo "Install it with:"
echo " sudo apt install gettext"
exit 1
fi
# Load variables from configuration file
set -a
source "$CONFIG_FILE"
set +a
mkdir -p "$TARGET"
echo "Copying configuration files..."
find "$SOURCE" -type f \( \
-iname "*.yaml" -o \
-iname "*.conf" -o \
-iname "*.txt" -o \
-iname "*.jcfg" \
\) | while IFS= read -r file
do
# Determine relative path
rel_path="${file#$SOURCE/}"
rel_dir="$(dirname "$rel_path")"
filename="$(basename "$rel_path")"
# Create destination directory if necessary
mkdir -p "$TARGET/$rel_dir"
####################################################
# Process template files
####################################################
if [[ "$filename" == _template_* ]]; then
# Remove "_template_" prefix from filename
new_filename="${filename#_template_}"
target_file="$TARGET/$rel_dir/$new_filename"
echo "Processing template: $rel_path"
# Replace variables in template
envsubst "\$NEXTCLOUD_DOMAIN \$NEXTCLOUD_PORT \$DOCKER_HOST_IP \$MYSQL_ROOT_PASSWORD \$TURN_SECRET \$SIGNALLING_SECRET \$HASH_KEY \$BLOCK_KEY \$API_KEY \$HARP_SECRET" < "$file" > "$target_file"
####################################################
# Copy regular files
####################################################
else
target_file="$TARGET/$rel_dir/$filename"
echo "Copying: $rel_path"
cp -p "$file" "$target_file"
fi
done
echo
echo "======================================"
echo "All files have been successfully created."
echo "Target directory:"
echo " $TARGET"
echo "======================================"
|-- template.txt
NEXTCLOUD_DOMAIN=domain.tld
NEXTCLOUD_PORT=0000
`– templates/
|-- _template_Post_Install.txt
#Change directory to compose.yaml file of Nextcloud installation
cd <NextcloudInstFolder>
#Download and start containers
docker compose up -d
###############Please note: notify_push container will crash until notify is installed
####Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: error during container init: exec: "/var/www/html/custom_apps/notify_push/bin/x86_64/notify_push": stat /var/www/html/custom_apps/notify_push/bin/x86_64/notify_push: no such file or directory
#Unless you filled in user name and password in compose.yaml, complete the installation by opening the following URL in your browser
https://${NEXTCLOUD_DOMAIN}:${NEXTCLOUD_PORT}
######Install notify_push
docker compose exec app php occ app:install notify_push
#Restart notify_push container
docker compose up -d notify_push
#Uncomment /push location in nginx.conf and reload nginx
nano nginx.conf
docker compose exec web /bin/bash -c "nginx -s reload"
#Run setup of notify_push
docker compose exec app php occ notify_push:setup https://${NEXTCLOUD_DOMAIN}:${NEXTCLOUD_PORT}/push
#Get rid of warnings in admin page
#Set the region to your country code
docker compose exec app php occ config:system:set default_phone_region --value="XX"
docker compose exec app php occ config:system:set maintenance_window_start --type=integer --value=1
docker compose exec app php occ maintenance:repair --include-expensive
docker compose exec app php occ config:system:set serverid --type=integer --value=1
#This will throw an unhandled exeception in version 34.0.2, which can be ignored
docker compose exec app php occ background:Cron
#####Configure HaRP
https://${NEXTCLOUD_DOMAIN}:${NEXTCLOUD_PORT}/settings/admin/app_api
HaRP Proxy (Host)
docker-install
HaRP-Host: ${DOCKER_HOST_IP}:8780
Shared Secret: ${HARP_SECRET}
Nextcloud-URL: https://${NEXTCLOUD_DOMAIN}:${NEXTCLOUD_PORT}
FRP-Server Address: ${DOCKER_HOST_IP}:8782
#####Install Talk app and go to settings
https://${NEXTCLOUD_DOMAIN}:${NEXTCLOUD_PORT}/settings/admin/talk
#Enable HPB in Talk Settings:
High performace backend
URL: https://${NEXTCLOUD_DOMAIN}:${NEXTCLOUD_PORT}/standalone-signaling
Shared Secret: ${SIGNALLING_SECRET}
#SCROLL down to STUN and TURN
#STUN:
https://${NEXTCLOUD_DOMAIN}:${NEXTCLOUD_PORT}
#TURN:
${NEXTCLOUD_DOMAIN}:3478
Turn Secret: ${TURN_SECRET}
#####Setup Nextcloud Office
https://${NEXTCLOUD_DOMAIN}:${NEXTCLOUD_PORT}/settings/admin/richdocuments
Select Built-In CODE server
#In order to get rid of the WOIP warning, you can also inspect the docker network used in compose.yaml to get the exact networks
#docker network list , docker network inspect <NextcloudInstFolder>_nextcloud_net
#This will work for default docker networks
172.16.0.0/12,fc00::/7
#####Delete nextcloud.log because of the failed Notify installation
docker compose exec app php occ maintenance:mode --on
sudo rm data/nextcloud.log
docker compose exec app php occ maintenance:mode --off
###Finally open ports on your internet facing firewall and forward to ${DOCKER_HOST_IP}
${NEXTCLOUD_PORT} TCP
${NEXTCLOUD_PORT} UDP for http/3
3478 TCP for Talk
3478 UDP for Talk
|-- _template_compose.yaml
networks:
nextcloud_net:
enable_ipv6: true
ipam:
driver: default
volumes:
db_host:
services:
db:
#Check recommended version at https://docs.nextcloud.com/server/stable/admin_manual/installation/system_requirements.html
image: mariadb:11.8
container_name: nextcloud-hpb-mariadb
networks:
nextcloud_net:
restart: unless-stopped
command: --transaction-isolation=READ-COMMITTED --log-bin=binlog --binlog-format=ROW
volumes:
- db_host:/var/lib/mysql
- /etc/localtime:/etc/localtime:ro
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} #Usefull to export database for backup
- MARIADB_AUTO_UPGRADE=1
- MARIADB_DISABLE_UPGRADE_BACKUP=1
- MYSQL_PASSWORD=mysql
- MYSQL_DATABASE=nextcloud
- MYSQL_USER=nextcloud
cap_add:
- SYS_ADMIN
app:
image: nextcloud:fpm #alpine image does not include glibc needed for Built-In CODE server (Nextcloud Office)
container_name: nextcloud-hpb-app
networks:
nextcloud_net:
restart: unless-stopped
depends_on:
- db
- redis
volumes:
- ./html:/var/www/html:z
- ./config:/var/www/html/config:z
- ./data:/var/www/html/data:z
- /etc/localtime:/etc/localtime:ro
environment:
- REDIS_HOST=redis
- MYSQL_PASSWORD=mysql
- MYSQL_DATABASE=nextcloud
- MYSQL_USER=nextcloud
- MYSQL_HOST=db
# - NEXTCLOUD_ADMIN_USER=xxx
# - NEXTCLOUD_ADMIN_PASSWORD=*******
- TRUSTED_PROXIES=172.16.0.0/12 ##For fail2ban and notify_push, default for docker installation unless changed
- NEXTCLOUD_TRUSTED_DOMAINS=${NEXTCLOUD_DOMAIN}:${NEXTCLOUD_PORT}
- OVERWRITECLIURL=https://${NEXTCLOUD_DOMAIN}:${NEXTCLOUD_PORT}
# php
- PHP_MEMORY_LIMIT=1G
- PHP_UPLOAD_LIMIT=10G
cron:
image: nextcloud:fpm
container_name: nextcloud-hpb-cron
networks:
nextcloud_net:
restart: unless-stopped
depends_on:
- app
volumes_from:
- app
# NOTE: The `volumes` config of the `cron` and `app` containers must match
entrypoint: /cron.sh
web:
image: nginx
container_name: nextcloud-hpb-proxy
networks:
nextcloud_net:
restart: unless-stopped
ports:
- ${NEXTCLOUD_PORT}:443
- ${NEXTCLOUD_PORT}:443/udp #Needed for http/3, see nginx.conf, comment out if you have issues with your Browser
depends_on:
- app
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- /etc/letsencrypt/:/etc/nginx/certs/:ro
- /etc/dhparam:/etc/dhparam:ro
volumes_from:
- app
harp:
image: ghcr.io/nextcloud/nextcloud-appapi-harp:release
container_name: nextcloud-hpb-harp
network_mode: host
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./certs:/certs
environment:
- HP_SHARED_KEY=${HARP_SECRET}
- NC_INSTANCE_URL=https://${NEXTCLOUD_DOMAIN}:${NEXTCLOUD_PORT}
- HP_EXAPPS_ADDRESS=${DOCKER_HOST_IP}:8780
redis:
image: redis:alpine
container_name: nextcloud-hpb-memcache
networks:
nextcloud_net:
restart: unless-stopped
# command: redis-server --requirepass **** # notify_push does not work with redis password set
notify_push:
image: nextcloud:fpm
container_name: nextcloud-hpb-notify
networks:
nextcloud_net:
restart: unless-stopped
depends_on:
- app
environment:
- PORT=7867
- NEXTCLOUD_URL=https://${NEXTCLOUD_DOMAIN}:${NEXTCLOUD_PORT} #Needs to point to nginx
entrypoint: /var/www/html/custom_apps/notify_push/bin/x86_64/notify_push /var/www/html/config/config.php
volumes_from:
- app
nats:
image: nats:latest
container_name: nextcloud-hpb-nats-server
network_mode: host
restart: unless-stopped
command: ["-c", "/config/nats.conf"]
volumes:
- ./hpb/config/nats.conf:/config/nats.conf:ro
janus:
image: canyan/janus-gateway:latest
container_name: nextcloud-hpb-janus-gateway
network_mode: host
restart: unless-stopped
depends_on:
- coturn
volumes:
- ./hpb/config/janus/janus.jcfg:/usr/local/etc/janus/janus.jcfg
- ./hpb/config/janus/janus.eventhandler.wsevh.jcfg:/usr/local/etc/janus/janus.eventhandler.wsevh.jcfg
- ./hpb/config/janus/janus.transport.websockets.jcfg:/usr/local/etc/janus/janus.transport.websockets.jcfg
- ./hpb/config/janus/janus.plugin.videoroom.jcfg:/usr/local/etc/janus/janus.plugin.videoroom.jcfg
coturn:
image: coturn/coturn
container_name: nextcloud-hpb-turn
network_mode: host
restart: unless-stopped
volumes:
- ./hpb/config/turnserver.conf:/etc/turnserver.conf:ro
signaling:
image: strukturag/nextcloud-spreed-signaling:latest
container_name: nextcloud-hpb-spreed-signaling
network_mode: host
restart: unless-stopped
depends_on:
- nats
- janus
- coturn
volumes:
- ./hpb/config/server.conf:/config/server.conf:ro
environment:
- CONFIG_FILE=/config/server.conf
|-- _template_nginx.conf
# Nextcloud nginx configuration — root installation
# Version 2026-03-26
# PHP-FPM backend.
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
resolver 127.0.0.11;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
upstream php-handler {
# Use one of the options below, not both:
server app:9000;
#server unix:/run/php/php8.2-fpm.sock;
}
# Set the `immutable` cache control options only for assets with a cache busting `v` argument
map $arg_v $asset_immutable {
"" "";
default ", immutable";
}
server {
#listen 80;
#listen [::]:80; # comment to disable IPv6
#if ($scheme = "http") {
# return 301 https://$host$request_uri;
#}
#if ($http_x_forwarded_proto = "http") {
# return 301 https://$host$request_uri;
#}
#listen 443 ssl http2;
#listen [::]:443 ssl http2;
# With NGinx >= 1.25.1 you should use this instead:
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
listen 443 quic; # uncomment to enable HTTP/3 / QUIC - supported on nginx v1.25.0+ - please remove "reuseport" if there is already another quic listener on port 443 with enabled reuseport
listen [::]:443 quic; # uncomment to enable HTTP/3 / QUIC - supported on nginx v1.25.0+ - please remove "reuseport" if there is already another quic listener on port 443 with enabled reuseport - keep comment to disable IPv6
http3 on; # uncomment to enable HTTP/3 / QUIC - supported on nginx v1.25.0+
quic_gso on; #OPTIONAL for http3, uncomment to enable HTTP/3 / QUIC - supported on nginx v1.25.0+
quic_retry on; #OPTIONAL for http3, uncomment to enable HTTP/3 / QUIC - supported on nginx v1.25.0+
# quic_bpf on; # improves HTTP/3 / QUIC - supported on nginx v1.25.0+, if nginx runs as a docker container you need to give it privileged permission to use this option
add_header Alt-Svc 'h3=":${NEXTCLOUD_PORT}"; ma=86400'; # uncomment to enable HTTP/3 / QUIC - supported on nginx v1.25.0+
http3_stream_buffer_size 512k; # uncomment to enable HTTP/3 / QUIC - supported on nginx v1.25.0+
server_name ${NEXTCLOUD_DOMAIN};
# Path to the root of your installation
root /var/www/html;
# Use Mozilla's guidelines for SSL/TLS settings
# https://mozilla.github.io/server-side-tls/ssl-config-generator/
ssl_certificate /etc/nginx/certs/live/${NEXTCLOUD_DOMAIN}/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/live/${NEXTCLOUD_DOMAIN}/privkey.pem;
ssl_dhparam /etc/dhparam; # curl -L https://ssl-config.mozilla.org/ffdhe2048.txt -o /etc/dhparam
ssl_early_data on;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ecdh_curve x25519:x448:secp521r1:secp384r1:secp256r1;
ssl_prefer_server_ciphers on;
ssl_conf_command Options PrioritizeChaCha;
ssl_ciphers TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256;
# Prevent nginx HTTP Server Detection
server_tokens off;
# HSTS settings
# WARNING: Only add the preload option once you read about
# the consequences in https://hstspreload.org/. This option
# will add the domain to a hardcoded list that is shipped
# in all major browsers and getting removed from this list
# could take several months.
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# set max upload size and increase upload timeout:
client_max_body_size 0;
client_body_timeout 300s;
fastcgi_buffers 64 4K;
# Proxy and client response timeouts
# Uncomment an increase these if facing timeout errors during large file uploads
#keepalive_timeout 60s;
#proxy_connect_timeout 60s;
#proxy_send_timeout 60s;
#proxy_read_timeout 60s;
#send_timeout 60s;
# Enable gzip but do not remove ETag headers
gzip on;
gzip_vary on;
gzip_comp_level 4;
gzip_min_length 256;
gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
gzip_types application/atom+xml text/javascript application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/wasm application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;
# Pagespeed is not supported by Nextcloud, so if your server is built
# with the `ngx_pagespeed` module, uncomment this line to disable it.
#pagespeed off;
# The settings allows you to optimize the HTTP2 bandwidth.
# See https://blog.cloudflare.com/delivering-http-2-upload-speed-improvements/
# for tuning hints
client_body_buffer_size 512k;
# HTTP response headers borrowed from Nextcloud `.htaccess`
add_header Referrer-Policy "no-referrer" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Permitted-Cross-Domain-Policies "none" always;
add_header X-Robots-Tag "noindex, nofollow" always;
# Remove X-Powered-By, which is an information leak
fastcgi_hide_header X-Powered-By;
# Set .mjs and .wasm MIME types
# Either include it in the default mime.types list
# and include that list explicitly or add the file extension
# only for Nextcloud like below:
include mime.types;
types {
text/javascript mjs;
# application/wasm wasm;
}
# Specify how to handle directories -- specifying `/index.php$request_uri`
# here as the fallback means that Nginx always exhibits the desired behaviour
# when a client requests a path that corresponds to a directory that exists
# on the server. In particular, if that directory contains an index.php file,
# that file is correctly served; if it doesn't, then the request is passed to
# the front-end controller. This consistent behaviour means that we don't need
# to specify custom rules for certain paths (e.g. images and other assets,
# `/updater`, `/ocs-provider`), and thus
# `try_files $uri $uri/ /index.php$request_uri`
# always provides the desired behaviour.
index index.php index.html /index.php$request_uri;
# Rule borrowed from `.htaccess` to handle Microsoft DAV clients
location = / {
if ( $http_user_agent ~ ^DavClnt ) {
return 302 /remote.php/webdav/$is_args$args;
}
}
location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}
# Make a regex exception for `/.well-known` so that clients can still
# access it despite the existence of the regex rule
# `location ~ /(\.|autotest|...)` which would otherwise handle requests
# for `/.well-known`.
fastcgi_intercept_errors off;
location ^~ /.well-known {
# The rules in this block are an adaptation of the rules
# in the Nextcloud `.htaccess` that concern `/.well-known`.
# CalDAV & CardDAV
location = /.well-known/carddav { return 301 https://$host:${NEXTCLOUD_PORT}/remote.php/dav/; }
location = /.well-known/caldav { return 301 https://$host:${NEXTCLOUD_PORT}/remote.php/dav/; }
# Webfinger & Nodeinfo (Nextcloud 26+ / 28+ / 29)
location = /.well-known/webfinger { return 301 https://$host:${NEXTCLOUD_PORT}/index.php/.well-known/webfinger; }
location = /.well-known/nodeinfo { return 301 https://$host:${NEXTCLOUD_PORT}/index.php/.well-known/nodeinfo; }
# ACME & PKI
location /.well-known/acme-challenge { try_files $uri $uri/ =404; }
location /.well-known/pki-validation { try_files $uri $uri/ =404; }
# Fallback
return 301 https://$host:${NEXTCLOUD_PORT}/index.php$request_uri;
}
# Rules borrowed from `.htaccess` to hide certain paths from clients
location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/) { return 404; }
location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console) { return 404; }
# Pass PHP requests to PHP-FPM.
#
# Important: this block must appear above the static asset locations
# below. Those locations fall back to `/index.php$request_uri`; if
# they appear first, nginx can repeatedly rewrite to `/index.php`,
# causing an internal redirection loop.
location ~ \.php(?:$|/) {
# Rewrite most PHP requests to Nextcloud's front controller (`/index.php`).
#
# (Mirrors the rewrite exceptions in Nextcloud's Apache .htaccess.)
#
# Exceptions (not rewritten; must remain directly reachable):
# index.php, remote.php, public.php, cron.php, status.php
# ocs/v1.php, ocs/v2.php, ocs-provider/*
# core/ajax/update.php, updater/*
# */richdocumentscode(_arm64)?/proxy
#
# Other exceptions (e.g. /.well-known) are handled by dedicated
# location blocks elsewhere in this config.
#
# Caution: small edits to this regex can break routing or introduce
# rewrite loops.
rewrite ^/(?!index|remote|public|cron|status|ocs\/v[12]|ocs-provider\/.+|core\/ajax\/update|updater\/.+|.+\/richdocumentscode(_arm64)?\/proxy) /index.php$request_uri;
# Split `/file.php/path/info` into:
# - $fastcgi_script_name: `/file.php`
# - $fastcgi_path_info: `/path/info`
#
# This is required for entry-points such as `remote.php` and `public.php`,
# which route requests based on PATH_INFO.
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
set $path_info $fastcgi_path_info; # Save before try_files resets it
# Return 404 for nonexistent PHP scripts (avoids passing arbitrary
# paths to PHP-FPM, which is a known security risk).
try_files $fastcgi_script_name =404;
include fastcgi_params;
fastcgi_pass php-handler;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $path_info;
fastcgi_param HTTPS on; # Assumes TLS terminates here
fastcgi_param modHeadersAvailable true; # Avoid duplicate security headers
fastcgi_param front_controller_active true; # Enable pretty URLs
# Let nginx handle HTTP error responses from PHP-FPM (e.g. custom
# error pages). Disable for debugging if PHP errors are being hidden.
fastcgi_intercept_errors on;
# Required for uploads: PHP-FPM does not support chunked
# transfer encoding and needs a Content-Length header.
fastcgi_request_buffering on;
# Optional PHP-FPM timeout tuning (e.g. for 504 response timeouts).
# Increase these only if uploads or long-running PHP requests are
# timing out in your environment.
#fastcgi_read_timeout 60s;
#fastcgi_send_timeout 60s;
#fastcgi_connect_timeout 60s;
# Disable on-disk buffering of FastCGI responses (reduces disk I/O at
# the cost of holding responses in memory).
fastcgi_max_temp_file_size 0;
}
# Serve static files
location ~ \.(?:css|js|mjs|svg|gif|ico|jpg|png|webp|wasm|tflite|map|ogg|flac|mp4|webm)$ {
try_files $uri /index.php$request_uri;
# HTTP response headers borrowed from Nextcloud `.htaccess`
add_header Cache-Control "public, max-age=15778463$asset_immutable";
add_header Referrer-Policy "no-referrer" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Permitted-Cross-Domain-Policies "none" always;
add_header X-Robots-Tag "noindex, nofollow" always;
access_log off; # Optional: Don't log access to assets
}
location ~ \.(otf|woff2?)$ {
try_files $uri /index.php$request_uri;
expires 7d; # Cache-Control policy borrowed from `.htaccess`
access_log off; # Optional: Don't log access to assets
}
# Rule borrowed from `.htaccess`
location /remote {
return 301 /remote.php$request_uri;
}
location /exapps/ {
proxy_pass http://${DOCKER_HOST_IP}:8780;
proxy_set_header Host $host:${NEXTCLOUD_PORT};
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;
}
location / {
try_files $uri $uri/ /index.php$request_uri;
}
# location ^~ /push/ {
# proxy_pass http://notify_push:7867/;
# proxy_http_version 1.1;
# proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection "Upgrade";
# proxy_set_header Host $host:${NEXTCLOUD_PORT};
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# }
location /standalone-signaling/ {
proxy_pass http://${DOCKER_HOST_IP}:8081/;
proxy_http_version 1.1;
# WebSocket support
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Headers
proxy_set_header Host $host:${NEXTCLOUD_PORT};
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;
# Long-lived connections for WebSocket
proxy_connect_timeout 7d;
proxy_send_timeout 7d;
proxy_read_timeout 7d;
}
}
}
`– hpb/
`– config/
|-- _template_server.conf
[http]
listen = 0.0.0.0:8081
[app]
# Shared secret - must match what you configure in Nextcloud Talk settings
secret = ${SIGNALLING_SECRET}
[sessions]
# Hash key: 64 hex characters (32 bytes)
hashkey = ${HASH_KEY}
# Block key: MUST be exactly 32 hex characters (16 bytes), 48 (24 bytes), or 64 (32 bytes)
blockkey = ${BLOCK_KEY}
[nats]
url = nats://127.0.0.1:4222
[mcu]
type = janus
url = ws://127.0.0.1:8188
#Set bitrate to 5MB/s = 5 * 1024 * 1024
maxstreambitrate = 5242880
maxscreenbitrate = 5242880
[backend]
# List all your Nextcloud backends here
backends = backend1
# Backend 1
[backend1]
url = https://${NEXTCLOUD_DOMAIN}:${NEXTCLOUD_PORT}
# This secret must match the signaling secret above
secret = ${SIGNALLING_SECRET}
# TURN server configuration
[turn]
apikey = ${API_KEY}
secret = ${TURN_SECRET}
servers = turn:${NEXTCLOUD_DOMAIN}:3478?transport=udp,turn:${NEXTCLOUD_DOMAIN}:3478?transport=tcp
|-- _template_turnserver.conf
# Please find the complete file at https://github.com/coturn/coturn/blob/master/examples/etc/turnserver.conf
# TURN listener port for UDP and TCP (Default: 3478).
# Note: actually, TLS & DTLS sessions can connect to the
# "plain" TCP & UDP port(s), too - if allowed by configuration.
#
listening-port=3478
# Lower and upper bounds of the UDP relay endpoints:
# (default values are 49152 and 65535)
#
min-port=20000
max-port=40000
# Uncomment to use fingerprints in the TURN messages.
# By default the fingerprints are off.
#
fingerprint
# Use either lt-cred-mech or use-auth-secret in the conf
# to avoid any confusion.
#
use-auth-secret
# 'Static' authentication secret value (a string) for TURN REST API only.
# If not set, then the turn server
# will try to use the 'dynamic' value in the turn_secret table
# in the user database (if present). The database-stored value can be changed on-the-fly
# by a separate program, so this is why that mode is considered 'dynamic'.
#
static-auth-secret=${TURN_SECRET}
# Note: If the default realm is not specified, then realm falls back to the host domain name.
# If the domain name string is empty, or set to '(None)', then it is initialized as an empty string.
#
realm=${NEXTCLOUD_DOMAIN}
# It defaults to 600 secs (10 min) if no value is provided. After that delay,
# the client will get 438 error and will have to re-authenticate itself.
#
stale-nonce=600
`– janus/
|-- _template_janus.jcfg
general: {
configs_folder = "/usr/local/etc/janus"
plugins_folder = "/usr/local/lib/janus/plugins"
transports_folder = "/usr/local/lib/janus/transports"
events_folder = "/usr/local/lib/janus/events"
loggers_folder = "/usr/local/lib/janus/loggers"
debug_level = 4
log_to_stdout = true
}
nat: {
stun_server = "127.0.0.1"
stun_port = 3478
ice_lite = false
ice_tcp = false
full_trickle = true
turn_rest_api_key = "${API_KEY}"
}
media: {
ipv6 = false
rtp_port_range = "20000-40000"
}
plugins: {
}
transports: {
}
|-- janus.eventhandler.wsevh.jcfg
# This configures the WebSockets event handler. Since this plugin only
# forwards each event it receives via WebSockets, you simply need to
# configure (i) which events to subscribe to, and (ii) the address of
# the WebSockets server which will receive the requests.
general: {
enabled = true # By default the module is not enabled
events = "all" # Comma separated list of the events mask you're interested
# in. Valid values are none, sessions, handles, jsep, webrtc,
# media, plugins, transports, core, external and all. By
# default we subscribe to everything (all)
grouping = true # Whether events should be sent individually (one per
# HTTP POST, JSON object), or if it's ok to group them
# (one or more per HTTP POST, JSON array with objects)
# The default is 'yes' to limit the number of connections.
json = "compact" # Whether the JSON messages should be indented (pretty),
# plain (no indentation) or compact (no indentation and no spaces)
# Address the plugin will send all events to as WebSocket
# messages. In case authentication is required to contact
# the backend, set the credentials as well.
backend = "ws://127.0.0.1:8188"
# subprotocol = "your-subprotocol"
# If the WebSocket server isn't reachable or the client has
# to reconnect, the default behaviour of the handler plugin
# is to retry with an exponential back-off, all while buffering
# events that Janus may keep on pushing. Buffering has no
# limit, so if reconnecting takes a long time (or forever)
# memory usage will keep on growing; besides, it may cause
# a network spike when eventually reconnected, as all stored
# events would need to be sent to the backend before new
# ones can be relayed as well. You can prune queued events
# and put a cap on the amount of buffering to perform when
# reconnecting by setting the 'events_cap_on_reconnect'
# property accordingly: any number you set will be the
# maximum number of events stored in memory until we
# reconnect, which means older packets will be discarded
# if the cap is exceeded. Notice that setting a value
# of 0 will not mean "drop all packets", but will disable
# the cap (default behaviour), which means the minimum
# possible value is 1. Also notice that, when the cap is
# enabled, this means the event receiver may end up missing key
# events when a reconnection actually ends up taking place.
# events_cap_on_reconnect = 10
# In case you need to debug connection issues, you can configure
# the libwebsockets debugging level as a comma separated list of things
# to debug, supported values: err, warn, notice, info, debug, parser,
# header, ext, client, latency, user, count (plus 'none' and 'all')
#ws_logging = "err,warn"
}
|-- janus.plugin.videoroom.jcfg
general: {
# admin_key = ""
}
room-1234: {
description = "Demo Room"
publishers = 6
bitrate = 128000
fir_freq = 10
audiocodec = "opus"
videocodec = "vp8"
record = false
}
`– janus.transport.websockets.jcfg
general: {
json = "compact" # Whether the JSON messages should be indented (pretty),
# plain (no indentation) or compact (no indentation and no spaces)
ws = true # Whether to enable the WebSockets API
ws_port = 8188 # WebSockets server port
#ws_interface = "eth0" # Whether we should bind this server to a specific interface only
ws_ip = "127.0.0.1" # Whether we should bind this server to a specific IP address only
#ws_unix = "/run/ws.sock" # Use WebSocket server over UNIX socket instead of TCP
wss = false # Whether to enable secure WebSockets
}
admin: {
admin_ws = true
admin_ws_port = 7188
}
All files with _template_ need to be adjusted before starting the container up for the first time.
The images used for the HPB need to open UDP ports for the audio/video stream dynamically. When exposing a large port range with docker you might get performance issues (in my environment docker simply crashed every time) which is why I used the host network mode of docker. When using this mode you cannot use localhost, 127.0.0.1, the docker host name nor the name of the container when communicating with the containers running in bridge mode. What worked for me is using the IP of the docker host instead. Adjust your domain and the custom port in the file templates.txt and execute the following:
cat >> template.txt <<EOF
DOCKER_HOST_IP=$(ip -4 route get 1.1.1.1 | awk ‘{print $7}’)
MYSQL_ROOT_PASSWORD=$(openssl rand -base64 48 | tr -dc ‘A-Za-z0-9’ | head -c 32)
TURN_SECRET=$(openssl rand -hex 32)
SIGNALLING_SECRET=$(openssl rand -hex 32)
HASH_KEY=$(openssl rand -hex 32)
BLOCK_KEY=$(openssl rand -hex 16)
API_KEY=$(openssl rand -base64 16)
HARP_SECRET=$(openssl rand -base64 48 | tr -dc ‘A-Za-z0-9’ | head -c 32)
EOF
See also Pre_Install.txt for details. I used the envsubst command to fill in the variables in the files. Execute
fillTemplate.sh templates
and change directory to .
See Post_Install.txt for details.