Why a reverse proxy
- One open port. Port 443 on your router goes to the proxy. Home Assistant, Jellyfin and Nextcloud stay on their own internal ports and are never exposed directly.
- Names instead of port numbers.
ha.yourname.uk.app,films.yourname.uk.appandcloud.yourname.uk.appcan all use port 443; the proxy tells them apart by the name the browser asks for. - Certificates in one place. The proxy gets and renews them. The apps behind it don’t need to know about HTTPS at all.
- Browsers insist on it.
.appis on the HSTS preload list, so browsers refuse plain HTTP for any.appname and won’t let anyone click past a certificate error.
Before you start: DNS and ports
- Point
home.yourname.uk.appat your public IP with a dynamic DNS updater (see the start guide). - For each app, add a CNAME record in your uk.app DNS:
ha→home.yourname.uk.app, and so on. Or add one wildcard: host*, type CNAME, valuehome.yourname.uk.app. - Give the proxy machine a fixed LAN address and forward TCP 443 to it on your router. Forward TCP 80 too if you use the automatic (HTTP‑01) certificate method below. If you are not sure how, see the guide for your router.
Option A: Caddy, with automatic HTTPS
Caddy gets and renews certificates by itself as soon as a site block names a public host. It is the easiest choice if you are comfortable with a text file. Install it from the official instructions for your system (Debian and Ubuntu have an apt repository), then edit /etc/caddy/Caddyfile:
ha.yourname.uk.app {
reverse_proxy 192.168.1.20:8123
}
films.yourname.uk.app {
reverse_proxy 192.168.1.30:8096
}
cloud.yourname.uk.app {
reverse_proxy 192.168.1.40:11000
}sudo systemctl reload caddy
journalctl -u caddy -f # watch the certificates being issuedCaddy passes the visitor’s address to the app in the X-Forwarded-For header and handles WebSockets without extra settings, which is what Home Assistant and Jellyfin need. Each app still has to be told to trust the proxy – that step is in every app guide on this site.
Option B: Nginx Proxy Manager
Nginx Proxy Manager (NPM) is a web interface on top of Nginx, usually run in Docker. It is a good fit if you prefer forms to config files. It is also available as a Home Assistant app.
- Open NPM’s admin page (port 81 by default) and change the default login.
- Hosts → Proxy Hosts → Add Proxy Host. Enter the domain name (
ha.yourname.uk.app), schemehttp, the app’s LAN address and port. Tick Websockets Support and Block Common Exploits. - On the SSL tab, choose Request a new Certificate, tick Force SSL and HTTP/2 Support, and save. NPM asks Let’s Encrypt using the HTTP‑01 method, so port 80 must be forwarded to NPM too.
Watch out
Don’t forward port 81 (the NPM admin page) from your router. Reach it on your LAN or over a VPN.
Option C: one wildcard certificate via DNS‑01
Instead of a certificate per name, you can get one certificate for yourname.uk.app and *.yourname.uk.app by proving control of the DNS rather than of a web server. This has three advantages: port 80 can stay closed, new app names work immediately, and it uses fewer certificates – which matters, because certificate authorities apply rate limits per registered domain, and uk.app names share that allowance until uk.app is listed on the Public Suffix List.
uk.app provides a small hook for acme.sh (and an adapter for lego). It needs an API token with the read and write:records scopes from my.uk.app/tokens – a DDNS token won’t do.
# as root on the proxy machine (sudo -i)
apt install unzip python3 curl
curl https://get.acme.sh | sh -s email=you@example.com
curl -fsSLO https://uk.app/downloads/ukapp-tools.zip && unzip ukapp-tools.zip
install -m 755 ukapp.py /usr/local/bin/ukapp
cp dns_ukapp.sh ~/.acme.sh/dnsapi/
install -m 600 /dev/null /root/.ukapp-api-token
nano /root/.ukapp-api-token # paste the API token, saveexport UKAPP_TOKEN="$(cat /root/.ukapp-api-token)"
~/.acme.sh/acme.sh --issue --server letsencrypt --dns dns_ukapp --dnssleep 300 \
-d yourname.uk.app -d '*.yourname.uk.app'The wait (--dnssleep 300) gives resolvers time to see the temporary TXT record. Try --server letsencrypt_test first if you are experimenting, so failed attempts don’t count against the real limits.
mkdir -p /etc/caddy/certs
~/.acme.sh/acme.sh --install-cert -d yourname.uk.app --ecc \
--fullchain-file /etc/caddy/certs/fullchain.pem \
--key-file /etc/caddy/certs/key.pem \
--reloadcmd "chown -R caddy:caddy /etc/caddy/certs && systemctl reload caddy"In the Caddyfile, tell each site to use those files instead of fetching its own:
(wildcard) {
tls /etc/caddy/certs/fullchain.pem /etc/caddy/certs/key.pem
}
ha.yourname.uk.app {
import wildcard
reverse_proxy 192.168.1.20:8123
}In Nginx Proxy Manager, upload the two files under SSL Certificates → Add SSL Certificate → Custom instead. For plain Nginx, use them as ssl_certificate and ssl_certificate_key.
Make renewals work
acme.sh adds a daily cron job, but that job won’t have UKAPP_TOKEN in its environment. Replace it with a small wrapper:
cat > /usr/local/sbin/acme-renew <<'EOF'
#!/bin/sh
UKAPP_TOKEN="$(cat /root/.ukapp-api-token)"
export UKAPP_TOKEN
exec /root/.acme.sh/acme.sh --cron --home /root/.acme.sh
EOF
chmod 700 /usr/local/sbin/acme-renew
~/.acme.sh/acme.sh --uninstall-cronjob
echo '17 3 * * * root /usr/local/sbin/acme-renew >/var/log/acme-renew.log 2>&1' > /etc/cron.d/acme-renewNote
The hook keeps a note of the TXT records it created under ~/.cache/ukapp-acme so it can remove exactly those afterwards. Leave that folder in place. Full details are in uk.app’s ACME DNS‑01 guide.
Which option should you pick?
| Caddy (automatic) | Nginx Proxy Manager | Wildcard via DNS‑01 | |
|---|---|---|---|
| Ports forwarded | 80 and 443 | 80 and 443 | 443 only |
| Set‑up | One text file | Web forms, Docker | Command line, API token |
| Certificates | One per name | One per name (or custom) | One for everything |
| Works for names only reachable on a VPN | No | No | Yes |
The last row matters for the Proxmox and Pi‑hole guides: a service you only reach over WireGuard can still have a trusted certificate if you use DNS‑01.
Tell each app it is behind a proxy
Most apps need to know the proxy’s LAN address before they trust the forwarded headers. Without this you get odd errors: Home Assistant answers 400 Bad Request, Nextcloud shows “untrusted domain”, Jellyfin thinks every visitor is on your LAN.
- Home Assistant: Settings → System → Network → HTTP server, turn on Trust X‑Forwarded‑For and add the proxy to Trusted proxies. Details.
- Jellyfin: Dashboard → Networking → Known proxies. Details.
- Nextcloud:
trusted_proxies,trusted_domainsandoverwriteprotocolinconfig.php. Details.
Check it
- Does the name point home? Look up
ha.yourname.uk.appwith the DNS lookup and compare the A record with the address on ip.uk.app. After a change, the propagation checker shows which resolvers still have the old one. - Is the port open from the internet? Run the open port checker against port 443, port 80. “Open” means your router forwards it and something answers. “Closed” or “timed out” means the forward, the device’s own firewall or CGNAT is in the way.
- Is the certificate right? The SSL checker shows whether the certificate covers the exact name, who issued it and when it expires.
- Test from outside for real. Turn Wi‑Fi off on your phone and open the address over mobile data. Testing from inside your own network can give misleading results.
Tip
Run the SSL checker on two different names. With a wildcard, both should show the same certificate covering *.yourname.uk.app.