{"id":8980,"date":"2026-09-29T07:56:24","date_gmt":"2026-09-29T05:56:24","guid":{"rendered":"https:\/\/speefak.spdns.de\/oss_lifestyle\/?p=8980"},"modified":"2026-09-29T07:58:58","modified_gmt":"2026-09-29T05:58:58","slug":"opnsense-zoneminder-mit-caddy-als-reverse-proxy","status":"publish","type":"post","link":"https:\/\/speefak.spdns.de\/oss_lifestyle\/opnsense-zoneminder-mit-caddy-als-reverse-proxy\/","title":{"rendered":"OPNsense: ZoneMinder mit Caddy als Reverse Proxy"},"content":{"rendered":"<p>ZoneMinder l\u00e4sst sich problemlos hinter einem Reverse Proxy betreiben, hat aber eine Eigenheit, die viele Setups zun\u00e4chst scheitern l\u00e4sst: Die Anwendung generiert ihre eigenen Links serverseitig aus <code dir=\"ltr\">SCRIPT_NAME<\/code> und geht dabei fest von ihrem lokalen <code dir=\"ltr\">\/zm<\/code>-Pfad aus. Ein naiver Reverse-Proxy-Handler mit festem Pfad-Pr\u00e4fix f\u00fchrt deshalb zu doppelten oder fehlenden Pfaden \u2013 die Login-Seite l\u00e4dt, aber Monitor-Kacheln und Live-Streams bleiben leer. Aus diesem Grund m\u00fcssen f\u00fcr den Betrieb von ZoneMinder \u00fcber einen Reverse Proxy mit Caddy zwei separate Handler eingerichtet werden:<\/p>\n<p><strong>Zwei Handler auf derselben Subdomain<\/strong> (Dienste \u2192 Caddy \u2192 Reverse Proxy \u2192 Handlers):<\/p>\n<p><strong>Handler A \u2013 <code>\/zm*<\/code> (eigentlicher Proxy):<\/strong><\/p>\n<pre><code>Pfad: \/zm*\r\nDirektive: reverse_proxy\r\nUpstream-Domain: &lt;ip&gt;\r\nUpstream-Port: 80\r\nUpstream-Pfad: (leer)\r\n<\/code><\/pre>\n<p><strong>Handler B \u2013 <code>\/<\/code> (Redirect):<\/strong><\/p>\n<pre><code>Pfad: \/\r\nDirektive: redir\r\nProtokoll: https:\/\/\r\nUpstream-Domain: zm.&lt;domain&gt;\r\nUpstream-Pfad: \/zm\/\r\n<\/code><\/pre>\n<p><strong>Ergebnis <\/strong>(cat \/usr\/local\/etc\/caddy\/Caddyfile | grep -B5 -A15 &#8220;zm.&lt;domain&gt;&#8221;):<\/p>\n<pre><code>zm.&lt;domain&gt; {\r\n\thandle \/zm* {\r\n\t\treverse_proxy &lt;ip&gt;:80\r\n\t}\r\n\thandle \/ {\r\n\t\tredir https:\/\/zm.&lt;domain&gt;\/zm\/\r\n\t}\r\n}\r\n<\/code><\/pre>\n<p>Keine manuellen <code>header_up<\/code>-Eintr\u00e4ge n\u00f6tig: Caddy setzt Host, X-Forwarded-For, X-Forwarded-Proto und X-Forwarded-Host bereits automatisch. Nur falls das Backend explizit <code>X-Real-IP<\/code> statt <code>X-Forwarded-For<\/code> auswertet, m\u00fcsste der zus\u00e4tzlich gesetzt werden.<\/p>\n<p><strong>Warum zwei Handler statt einem mit festem Pr\u00e4fix:<\/strong> Ein einzelner Handler mit <code>ToPath: \/zm<\/code> h\u00e4ngt den Pr\u00e4fix vor <strong>jeden<\/strong> Request \u2013 auch vor die, die ZM selbst schon mit <code>\/zm\/...<\/code> generiert. Ergebnis: doppelter Pr\u00e4fix, 404 bei AJAX-Requests, Monitore bleiben leer, obwohl Login l\u00e4dt. Die Trennung in &#8220;Root \u2192 Redirect auf <code>\/zm\/<\/code>&#8221; und &#8220;<code>\/zm*<\/code> \u2192 unver\u00e4ndert durchreichen&#8221; vermeidet das.<\/p>\n<p><strong>Bekannte Einschr\u00e4nkung:<\/strong> Nach dem Redirect zeigt die Adressleiste dauerhaft <code>zm.&lt;domain&gt;\/zm\/...<\/code>, weil ZM seine Links aus <code>SCRIPT_NAME<\/code> baut. Vermeidbar nur durch Eingriff auf dem ZM-Host selbst (Apache-Alias entfernen, ZM direkt unter <code>\/<\/code> ausliefern).<\/p>\n<p><strong>GUI-Stolperfalle:<\/strong> Bei <code>redir<\/code> ist Upstream-Domain ein Pflichtfeld. F\u00fcr einen Redirect innerhalb derselben Domain dort die eigene Domain (<code>zm.&lt;domain&gt;<\/code>), nicht die Backend-IP eintragen \u2013 sonst wird die interne Adresse im Browser sichtbar.<\/p>\n<hr \/>\n<p><img loading=\"lazy\" decoding=\"async\" class=\" wp-image-2821 alignleft\" src=\"https:\/\/speefak.spdns.de\/oss_lifestyle\/wp-content\/uploads\/2014\/08\/CC_BY_NC_SA.png\" alt=\"CC_BY_NC_SA\" width=\"65\" height=\"23\" \/>by Speefak<\/p>\n","protected":false},"excerpt":{"rendered":"<p>ZoneMinder l\u00e4sst sich problemlos hinter einem Reverse Proxy betreiben, hat aber eine Eigenheit, die viele Setups zun\u00e4chst scheitern l\u00e4sst: Die Anwendung generiert ihre eigenen Links serverseitig aus SCRIPT_NAME und geht dabei fest von ihrem lokalen \/zm-Pfad aus. Ein naiver Reverse-Proxy-Handler mit festem Pfad-Pr\u00e4fix f\u00fchrt deshalb zu doppelten oder fehlenden Pfaden \u2013 die Login-Seite l\u00e4dt, aber [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[32,113,43,112],"tags":[],"class_list":["post-8980","post","type-post","status-publish","format-standard","hentry","category-netzwerke","category-opnsense","category-sicherheit","category-zoneminder"],"rttpg_featured_image_url":null,"rttpg_author":{"display_name":"speefak","author_link":"https:\/\/speefak.spdns.de\/oss_lifestyle\/author\/speefak_oss\/"},"rttpg_comment":0,"rttpg_category":"<a href=\"https:\/\/speefak.spdns.de\/oss_lifestyle\/category\/netzwerke\/\" rel=\"category tag\">Netzwerke<\/a> <a href=\"https:\/\/speefak.spdns.de\/oss_lifestyle\/category\/opnsense\/\" rel=\"category tag\">OPNSense<\/a> <a href=\"https:\/\/speefak.spdns.de\/oss_lifestyle\/category\/sicherheit\/\" rel=\"category tag\">Sicherheit<\/a> <a href=\"https:\/\/speefak.spdns.de\/oss_lifestyle\/category\/zoneminder\/\" rel=\"category tag\">Zoneminder<\/a>","rttpg_excerpt":"ZoneMinder l\u00e4sst sich problemlos hinter einem Reverse Proxy betreiben, hat aber eine Eigenheit, die viele Setups zun\u00e4chst scheitern l\u00e4sst: Die Anwendung generiert ihre eigenen Links serverseitig aus SCRIPT_NAME und geht dabei fest von ihrem lokalen \/zm-Pfad aus. Ein naiver Reverse-Proxy-Handler mit festem Pfad-Pr\u00e4fix f\u00fchrt deshalb zu doppelten oder fehlenden Pfaden \u2013 die Login-Seite l\u00e4dt, aber&hellip;","_links":{"self":[{"href":"https:\/\/speefak.spdns.de\/oss_lifestyle\/wp-json\/wp\/v2\/posts\/8980","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/speefak.spdns.de\/oss_lifestyle\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/speefak.spdns.de\/oss_lifestyle\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/speefak.spdns.de\/oss_lifestyle\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/speefak.spdns.de\/oss_lifestyle\/wp-json\/wp\/v2\/comments?post=8980"}],"version-history":[{"count":3,"href":"https:\/\/speefak.spdns.de\/oss_lifestyle\/wp-json\/wp\/v2\/posts\/8980\/revisions"}],"predecessor-version":[{"id":8983,"href":"https:\/\/speefak.spdns.de\/oss_lifestyle\/wp-json\/wp\/v2\/posts\/8980\/revisions\/8983"}],"wp:attachment":[{"href":"https:\/\/speefak.spdns.de\/oss_lifestyle\/wp-json\/wp\/v2\/media?parent=8980"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/speefak.spdns.de\/oss_lifestyle\/wp-json\/wp\/v2\/categories?post=8980"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/speefak.spdns.de\/oss_lifestyle\/wp-json\/wp\/v2\/tags?post=8980"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}