I'm using zonemindere v1.38.1 with go2rtc version: 1.9.14
GO2RTC_PATH
http://127.0.0.1:1984/api
/etc/go2rtc.yml
api:
listen: "127.0.0.1:1984"
rtsp:
listen: "127.0.0.1:8554"
streams:
3_ZoneMinderPrimary:
- rtsp://127.0.0.1:2000/monitor3
3_CameraDirectPrimary:
- rtsp://user:pass@172.20.224.66:554/Streaming/Channels/101
3_CameraDirectSecondary:
- rtsp://user:pass@172.20.224.66:554/Streaming/Channels/102
"3":
- rtsp://127.0.0.1:2000/monitor3
i made proxy with nginx to go2rtc
map $cookie_hls_session $bypass_secure_link {
"authorized" "1";
default $secure_link;
}
location /hls/ {
secure_link $arg_md5,$arg_expires;
secure_link_md5 "someSALThash$arg_expires";
if ($bypass_secure_link = "") { return 403; }
if ($bypass_secure_link = "0") { return 410; }
add_header Set-Cookie "hls_session=authorized; Path=/hls/; Max-Age=7200; HttpOnly; SameSite=Strict";
proxy_pass http://127.0.0.1:1984/;
proxy_http_version 1.1;
proxy_buffering off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
i use .py script to generate temp link hls stream for player
#!/usr/bin/env python3
import time
import hashlib
import base64
import sys
# ==================== SETTINGS ====================
SECRET = "someSALThash"
# External IP or domain of your ZoneMinder server
SERVER_IP = "******"
# The protected path configured in Nginx
URI = "/hls/api/stream.m3u8"
# URL expiration time in seconds (3600 = 1 hour)
TTL_SECONDS = 3600
# ===================================================
def generate_secure_url(monitor_id):
# 1. Calculate the link expiration timestamp (current time + TTL)
expires = int(time.time()) + TTL_SECONDS
# 2. Construct the string for hashing exactly as Nginx secure_link_md5 expects:
# secure_link_md5 "Secret$arg_expires";
protected_string = f"{SECRET}{expires}".encode('utf-8')
# 3. Calculate the binary MD5 hash
md5_binary = hashlib.md5(protected_string).digest()
# 4. Encode to URL-safe Base64 format (replacing + with -, / with _, and removing '=')
md5_base64 = base64.urlsafe_b64encode(md5_binary).decode('utf-8').replace('=', '')
# 5. Assemble the final URL, appending the camera identifier to the 'src' parameter
final_url = f"http://{SERVER_IP}{URI}?src={monitor_id}&md5={md5_base64}&expires={expires}"
return final_url, expires
if __name__ == "__main__":
# If the monitor ID is passed as a CLI argument, use it; otherwise default to Monitor 3
monitor = sys.argv[1] if len(sys.argv) > 1 else "3"
url, exp_time = generate_secure_url(monitor)
print("\n" + "="*60)
print(f" SECURE URL FOR WEB PLAYER OR SMARTPHONE (ZM Monitor: {monitor})")
print("="*60)
print(url)
print("="*60)
print(f"Link is valid until: {time.ctime(exp_time)} (1 hour)\n")
on web server log is good
**** - - [16/Jun/2026:13:41:22 +0300] "GET /hls/api/stream.m3u8?src=3&md5=7fIds9YjOof2TLpleDbYKg&expires=1781610035 HTTP/1.1" 200 0 "http://****/hls/api/stream.m3u8?src=3&md5=7fIds9YjOof2TLpleDbYKg&expires=1781610035" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36" "-"
but at go2rtc:
undefined error=streams: user/pass not provided caller=github.com/AlexxIT/go2rtc/internal/hls/hls.go:76

I'm using zonemindere v1.38.1 with go2rtc version: 1.9.14
GO2RTC_PATH
http://127.0.0.1:1984/api/etc/go2rtc.yml
i made proxy with nginx to go2rtc
i use .py script to generate temp link hls stream for player
on web server log is good
**** - - [16/Jun/2026:13:41:22 +0300] "GET /hls/api/stream.m3u8?src=3&md5=7fIds9YjOof2TLpleDbYKg&expires=1781610035 HTTP/1.1" 200 0 "http://****/hls/api/stream.m3u8?src=3&md5=7fIds9YjOof2TLpleDbYKg&expires=1781610035" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36" "-"
but at go2rtc:
undefined error=streams: user/pass not provided caller=github.com/AlexxIT/go2rtc/internal/hls/hls.go:76