{"id":6905,"date":"2026-02-24T11:31:23","date_gmt":"2026-02-24T06:01:23","guid":{"rendered":"https:\/\/toolswift.com\/blog\/?p=6905"},"modified":"2026-02-24T11:31:23","modified_gmt":"2026-02-24T06:01:23","slug":"how-to-build-a-self-hosted-reverse-proxy-with-valid-ssl-for-local-only-access-on-synology-nas","status":"publish","type":"post","link":"https:\/\/toolswift.com\/blog\/how-to-build-a-self-hosted-reverse-proxy-with-valid-ssl-for-local-only-access-on-synology-nas\/","title":{"rendered":"How to Build a Self-Hosted Reverse Proxy with Valid SSL for Local-Only Access on Synology NAS"},"content":{"rendered":"<p>Running multiple self-hosted apps on a Synology NAS is easy\u2014until you want them to feel \u201cproduction clean\u201d: one HTTPS URL per app (no ports), trusted certificates (no browser warnings), and LAN-only access (nothing exposed to the public internet).<\/p>\n<p>This guide shows two proven ways to do it:<\/p>\n<p><strong>Option A (Best for \u201cpublicly trusted\u201d SSL):<\/strong> Use a real domain + Let\u2019s Encrypt DNS-01 challenge so certificates are valid even if your NAS is never reachable from the internet. (Recommended for most people.)<\/p>\n<p><strong>Option B (Best for purely internal names):<\/strong> Use a private CA (step-ca or Caddy\u2019s internal CA) for internal-only domains like <strong>*.lan<\/strong> or <strong>*.home<\/strong>.<\/p>\n<p>You\u2019ll also lock access down so the reverse proxy only works inside your home network.<\/p>\n<h2>Target Outcome<\/h2>\n<p>You\u2019ll end up with URLs like:<\/p>\n<ul>\n<li><strong>https:\/\/vaultwarden.home.example.com<\/strong><\/li>\n<li><strong>https:\/\/photos.home.example.com<\/strong><\/li>\n<li><strong>https:\/\/ha.home.example.com<\/strong><\/li>\n<\/ul>\n<p>All of them:<\/p>\n<ul>\n<li>terminate TLS at the NAS reverse proxy<\/li>\n<li>show a valid certificate<\/li>\n<li>route to internal services (Docker containers, NAS packages, other LAN servers)<\/li>\n<li>are not reachable from the internet<\/li>\n<\/ul>\n<h2>What You Need<\/h2>\n<h3>Hardware \/ software<\/h3>\n<ul>\n<li>Synology NAS on DSM 7.x<\/li>\n<li>Your apps running on the NAS (Docker\/Container Manager or Synology packages) or on other LAN hosts<\/li>\n<\/ul>\n<h3>Network &amp; DNS<\/h3>\n<p>A way to resolve your chosen hostnames inside your LAN:<\/p>\n<ul>\n<li>your router\u2019s DNS, or<\/li>\n<li>Pi-hole \/ Technitium \/ AdGuard Home, or<\/li>\n<li>Synology DNS Server package<\/li>\n<\/ul>\n<h3>For Option A (publicly trusted SSL)<\/h3>\n<ul>\n<li>A real domain you control (e.g., example.com)<\/li>\n<li>DNS provider that supports API access (Cloudflare, Route53, etc.) for automated TXT records<\/li>\n<\/ul>\n<h2>Core Design: Reverse Proxy + Split DNS + Firewall<\/h2>\n<h3>1) Reverse proxy on Synology<\/h3>\n<p>DSM includes a reverse proxy UI where you map:<\/p>\n<p>Source (public-facing in LAN): <strong>https:\/\/app.example.com:443<\/strong><br \/>\n\u2192 Destination (your real service): <strong>http:\/\/127.0.0.1:8080<\/strong> (or another LAN IP\/port)<\/p>\n<p>Synology documents this under <strong>Control Panel \u2192 Application Portal \u2192 Reverse Proxy<\/strong> (DSM 7).<\/p>\n<h3>2) Local DNS<\/h3>\n<p>Your LAN clients must resolve <strong>app.example.com<\/strong> to your NAS LAN IP (e.g., <strong>192.168.1.10<\/strong>). That\u2019s the key to \u201clocal-only\u201d without exposing anything publicly.<\/p>\n<h3>3) Local-only enforcement<\/h3>\n<p>You ensure no WAN access by:<\/p>\n<ul>\n<li>not forwarding ports 80\/443 on your router<\/li>\n<li>adding Synology firewall rules to only allow LAN subnets to connect (details below)<\/li>\n<\/ul>\n<h2>Option A (Recommended): Valid Public SSL with Let\u2019s Encrypt DNS-01 (No Public Exposure)<\/h2>\n<h3>Why DNS-01 is the trick<\/h3>\n<p>Let\u2019s Encrypt normally validates using HTTP (requires your server to be reachable). But DNS-01 proves ownership via a TXT record in DNS, so the server does not need to be internet-accessible.<\/p>\n<p>Synology\u2019s built-in certificate flow has limitations around DNS challenge support (varies by setup\/provider), so a common reliable approach is using <strong>acme.sh<\/strong> and then deploying the cert into DSM.<\/p>\n<h3>Step A.1 \u2014 Pick a naming scheme<\/h3>\n<p>A clean pattern is:<\/p>\n<p><strong>*.home.example.com<\/strong> for all internal apps<\/p>\n<p>Example:<\/p>\n<ul>\n<li>vaultwarden.home.example.com<\/li>\n<li>ha.home.example.com<\/li>\n<li>nas.home.example.com<\/li>\n<\/ul>\n<p>This makes wildcard certs very convenient.<\/p>\n<h3>Step A.2 \u2014 Configure DNS (public + local)<\/h3>\n<h4>Public DNS (at your domain registrar\/DNS provider)<\/h4>\n<p>You only need DNS access for the TXT records (ACME challenge). You can choose one of these:<\/p>\n<p><strong>Approach 1 (Simple):<\/strong><br \/>\nSet A records for <strong>*.home.example.com<\/strong> to your NAS private IP (e.g., <strong>192.168.1.10<\/strong>). This works for most home setups, but you are publishing a private IP in public DNS (not dangerous by itself, just messy).<\/p>\n<p><strong>Approach 2 (Cleaner): Split DNS<\/strong><\/p>\n<p>Public DNS: <strong>*.home.example.com<\/strong> can point anywhere (or not exist at all; DNS-01 can still work with wildcard issuance depending on provider\/tooling).<\/p>\n<p>Local DNS: create A records so your LAN resolves <strong>*.home.example.com \u2192 192.168.1.10<\/strong>.<\/p>\n<p>Most people prefer split DNS so internal services don\u2019t \u201cdepend\u201d on public resolution.<\/p>\n<h3>Step A.3 \u2014 Issue a wildcard certificate with acme.sh (DNS API)<\/h3>\n<h4>Where to run acme.sh<\/h4>\n<ul>\n<li>In a Docker container on the NAS, or<\/li>\n<li>via Synology SSH + Task Scheduler (Docker is usually cleaner)<\/li>\n<\/ul>\n<p>acme.sh provides a Synology DSM deploy hook (<strong>synology_dsm.sh<\/strong>) specifically to install certs into DSM automatically.<\/p>\n<h4>Example (conceptual) issuance flow<\/h4>\n<p>You\u2019ll use your DNS provider\u2019s acme.sh DNS plugin (example: Cloudflare plugin name is <strong>dns_cf<\/strong>, other providers differ).<\/p>\n<p>Typical commands look like:<\/p>\n<p>Issue:<\/p>\n<pre><code>acme.sh --issue --dns &lt;dns_plugin&gt; -d \"*.home.example.com\" -d \"home.example.com\"<\/code><\/pre>\n<p>Deploy into DSM:<\/p>\n<pre><code>acme.sh --deploy --deploy-hook synology_dsm -d \"*.home.example.com\"<\/code><\/pre>\n<p>The DSM deploy hook is a standard approach used in real Synology DNS-challenge setups.<\/p>\n<p><strong>Important:<\/strong> the deploy hook needs DSM credentials\/host details (set as environment variables per the hook documentation\/script).<\/p>\n<h3>Step A.4 \u2014 Make renewal automatic<\/h3>\n<p>Let\u2019s Encrypt certs are short-lived (90 days). Your goal is zero manual renewals.<\/p>\n<p>Common patterns:<\/p>\n<ul>\n<li>Run acme.sh container with a cron-like schedule<\/li>\n<li>Or use Synology Task Scheduler to run the renew\/deploy command daily<\/li>\n<\/ul>\n<p>acme.sh is designed to renew automatically and redeploy when needed.<\/p>\n<h3>Step A.5 \u2014 Assign the certificate in DSM<\/h3>\n<p>Once the cert is installed in DSM:<\/p>\n<ul>\n<li>Go to <strong>Control Panel \u2192 Security \u2192 Certificate<\/strong><\/li>\n<li>Ensure the wildcard cert is set as default, and\/or assigned to the services (Reverse Proxy \/ DSM \/ apps)<\/li>\n<\/ul>\n<p>(Some deployments apply it automatically; if not, you assign it in DSM.)<\/p>\n<h2>Configure DSM Reverse Proxy Rules (Works for both Option A and B)<\/h2>\n<h3>Step RP.1 \u2014 Create a reverse proxy entry<\/h3>\n<p>DSM 7 path (varies slightly by DSM build, but typically):<\/p>\n<p><strong>Control Panel \u2192 Application Portal \u2192 Reverse Proxy \u2192 Create<\/strong><\/p>\n<p>You\u2019ll set:<\/p>\n<p><strong>Source<\/strong><\/p>\n<ul>\n<li>Protocol: HTTPS<\/li>\n<li>Hostname: vaultwarden.home.example.com<\/li>\n<li>Port: 443<\/li>\n<\/ul>\n<p><strong>Destination<\/strong><\/p>\n<ul>\n<li>Protocol: HTTP (or HTTPS if your backend uses it)<\/li>\n<li>Hostname: 127.0.0.1 (or container host \/ LAN IP)<\/li>\n<li>Port: 8080 (example)<\/li>\n<\/ul>\n<p>Repeat per app.<\/p>\n<p><strong>Tip:<\/strong> Keep backends on HTTP internally unless you truly need end-to-end TLS. The reverse proxy is your TLS boundary for LAN.<\/p>\n<h3>Step RP.2 \u2014 (Optional but recommended) Add security headers<\/h3>\n<p>In Synology reverse proxy \u201cCustom Header\u201d settings (if available), common additions:<\/p>\n<ul>\n<li>HSTS (only if you\u2019re sure you want HTTPS always)<\/li>\n<li>X-Forwarded-Proto, X-Forwarded-For (often automatic)<\/li>\n<\/ul>\n<p>Keep it minimal unless you know the app requirements.<\/p>\n<h2>Lock It to Local-Only Access<\/h2>\n<h3>Step L.1 \u2014 Do NOT port-forward 80\/443 from your router<\/h3>\n<p>This is the biggest lever. If WAN traffic can\u2019t reach your NAS, your proxy stays local.<\/p>\n<p>If you previously had port forwards, remove them.<\/p>\n<h3>Step L.2 \u2014 Use Synology Firewall to allow LAN subnets only<\/h3>\n<p>Enable DSM firewall and create rules like:<\/p>\n<ul>\n<li>Allow your LAN subnet (example: <strong>192.168.1.0\/24<\/strong>) to ports 443 (and 80 only if you need HTTP\u2192HTTPS redirects)<\/li>\n<li>Deny everything else (catch-all)<\/li>\n<\/ul>\n<p>Rule ordering matters in DSM firewall.<\/p>\n<p><strong>Note:<\/strong> If you find reverse proxy behaves unexpectedly with firewall rules, you\u2019re not alone\u2014there are community reports around reverse proxy and firewall interactions, so test from both LAN and a non-LAN client (like phone on cellular).<\/p>\n<h3>Step L.3 \u2014 Ensure LAN DNS is authoritative for your internal hostnames<\/h3>\n<p>Make sure your devices actually use your LAN DNS (router DNS, Pi-hole, etc.). Otherwise, they may query public DNS and not reach your NAS correctly.<\/p>\n<h2>Option B: Valid HTTPS for Internal-Only Domains Using a Private CA (step-ca or Caddy Internal CA)<\/h2>\n<p>If you want names like:<\/p>\n<ul>\n<li><strong>https:\/\/vaultwarden.lan<\/strong><\/li>\n<li><strong>https:\/\/ha.home.arpa<\/strong><\/li>\n<\/ul>\n<p>or you simply don\u2019t want to use a public domain at all, you need a private CA.<\/p>\n<h3>Two solid approaches<\/h3>\n<h4>B.1) step-ca (Private ACME CA)<\/h4>\n<ul>\n<li>step-ca runs as your internal Certificate Authority<\/li>\n<li>ACME clients can request\/renew certs automatically (similar workflow to Let\u2019s Encrypt, but private)<\/li>\n<\/ul>\n<p><strong>Pros:<\/strong><\/p>\n<ul>\n<li>Fully internal<\/li>\n<li>Automated issuance\/renewal using ACME<\/li>\n<\/ul>\n<p><strong>Cons:<\/strong><\/p>\n<ul>\n<li>You must distribute and trust your root CA on your devices (once per device\/OS)<\/li>\n<\/ul>\n<h4>B.2) Caddy\u2019s internal CA mode<\/h4>\n<p>Caddy can issue and manage internal certificates (not publicly trusted) and can do internal PKI flows.<\/p>\n<p><strong>Pros:<\/strong><\/p>\n<ul>\n<li>Very simple if you\u2019re already using Caddy<\/li>\n<li>Automatic renewals<\/li>\n<\/ul>\n<p><strong>Cons:<\/strong><\/p>\n<ul>\n<li>Same trust distribution requirement (install CA root on clients)<\/li>\n<\/ul>\n<p><strong>Practical note:<\/strong> If your goal is \u201cno warnings on every device without installing anything,\u201d Option A (public domain + DNS-01) is usually the winner.<\/p>\n<h2>Troubleshooting Checklist<\/h2>\n<h3>Certificate issuance fails<\/h3>\n<ul>\n<li>DNS API credentials wrong or missing<\/li>\n<li>TXT record not propagating quickly enough<\/li>\n<li>Using an internal-only TLD (public CAs won\u2019t issue for .lan, .internal, etc.) \u2014 you\u2019ll see errors like \u201cdoes not qualify for a public certificate\u201d in some setups<\/li>\n<\/ul>\n<h3>Reverse proxy works by IP:port but not by hostname<\/h3>\n<ul>\n<li>LAN DNS doesn\u2019t resolve app.home.example.com to the NAS LAN IP<\/li>\n<li>Client is using a different DNS server than you think<\/li>\n<\/ul>\n<h3>It\u2019s accessible from the internet (you don\u2019t want that)<\/h3>\n<ul>\n<li>Router port forward is still enabled<\/li>\n<li>DSM firewall rule order is wrong (allow rules must be above deny-all)<\/li>\n<\/ul>\n<h2>Recommended \u201cBest Practice\u201d Setup Summary (Most Homes)<\/h2>\n<ul>\n<li>Use <strong>*.home.example.com<\/strong><\/li>\n<li>Local DNS resolves those names to NAS LAN IP<\/li>\n<li>Issue wildcard Let\u2019s Encrypt cert via DNS-01 using <strong>acme.sh<\/strong><\/li>\n<li>Deploy cert into DSM with <strong>synology_dsm<\/strong> deploy hook<\/li>\n<li>DSM Reverse Proxy maps each hostname \u2192 internal service<\/li>\n<li>No router port forwards<\/li>\n<li>DSM firewall: allow LAN \u2192 443, deny everything else<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Running multiple self-hosted apps on a Synology NAS is easy\u2014until you want them to feel \u201cproduction clean\u201d: one HTTPS URL per app (no ports),&hellip;<\/p>\n","protected":false},"author":1,"featured_media":6906,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[],"class_list":["post-6905","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-how-to"],"_links":{"self":[{"href":"https:\/\/toolswift.com\/blog\/wp-json\/wp\/v2\/posts\/6905","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/toolswift.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/toolswift.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/toolswift.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/toolswift.com\/blog\/wp-json\/wp\/v2\/comments?post=6905"}],"version-history":[{"count":3,"href":"https:\/\/toolswift.com\/blog\/wp-json\/wp\/v2\/posts\/6905\/revisions"}],"predecessor-version":[{"id":6909,"href":"https:\/\/toolswift.com\/blog\/wp-json\/wp\/v2\/posts\/6905\/revisions\/6909"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/toolswift.com\/blog\/wp-json\/wp\/v2\/media\/6906"}],"wp:attachment":[{"href":"https:\/\/toolswift.com\/blog\/wp-json\/wp\/v2\/media?parent=6905"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/toolswift.com\/blog\/wp-json\/wp\/v2\/categories?post=6905"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/toolswift.com\/blog\/wp-json\/wp\/v2\/tags?post=6905"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}