Security

How to Set Up HTTPS for Your Homelab: SSL/TLS Without Breaking Local Access

Learn the simplest way to add SSL/TLS to your homelab with Caddy, internal DNS overrides, and one hostname that works locally and remotely.

AU

Author

David Okonkwo

> FTC disclosure: This article contains affiliate links. If you purchase through these links, we may earn a commission at no additional cost to you.

Key Takeaways

  • You do not need a complicated enterprise PKI just to stop browser warnings in your homelab.
  • The simplest beginner-friendly pattern is: one reverse proxy, one public hostname per service, and one internal DNS override so local devices hit your LAN IP instead of hairpinning out to the internet.
  • Caddy is the easiest command-first way to get valid HTTPS certificates with very little configuration.
  • Start by exposing one low-risk service. Keep Proxmox, NAS admin panels, routers, and backup consoles private.
  • If your goal is one clean URL that works both at home and away from home, internal DNS matters just as much as TLS.

A Few Low-Power Picks That Fit This Setup

If you are still piecing together the hardware side, these are sensible fits for a lightweight DNS + reverse proxy stack:

If you have ever opened a self-hosted app in your browser and seen a certificate warning, an IP address with a port you can never remember, or a service that works remotely but feels weirdly slower when you are sitting five feet away from it, you are in the exact situation this guide is meant to fix.

A lot of homelab HTTPS guides jump straight into reverse proxies, wildcard certificates, and DNS challenge plugins. Those tools matter, but that is not usually where the confusion starts. The real frustration is simpler: you want photos.yourdomain.com to work everywhere, you want it to be encrypted, and you do not want your traffic leaving your house just to come back in through the same router.

This guide gives you the cleanest first setup I know for that problem. We are going to put Caddy in front of one service, get a valid certificate, and make your local DNS resolver return the proxy's LAN IP while public DNS still returns your WAN path. Think of it like giving your homelab one front door and then teaching local visitors to use the driveway instead of walking around the block.

I will use Caddy for the hands-on path because it is the easiest command-driven option for beginners. If you prefer a GUI, HomelabAddiction already has a full Nginx Proxy Manager setup guide, and I will point out where that alternative makes sense.

I also recommend keeping the official docs open for the pieces we touch:

Why HTTPS matters in a homelab

Before the commands, here is the plain-English version.

HTTPS does three jobs for you:

1. It encrypts the connection so credentials and session cookies are not sent around in the clear.

2. It proves your browser reached the service you intended to reach.

3. It lets modern apps behave normally, because many self-hosted tools assume secure origins for logins, cookies, OAuth redirects, and WebSockets.

Why this matters in a homelab:

  • Browser warnings train you to click through bad security habits.
  • Some apps behave strangely or refuse features when they are behind plain HTTP.
  • If family members use your services, certificate warnings destroy trust immediately.
  • Clean hostnames like jellyfin.example.com are much easier to remember than 192.168.1.50:8096.

If you already read our broader Homelab DNS guide, this is the next layer on top of that foundation. DNS gives things names. TLS makes those names trustworthy.

What you are building

For this beginner path, the architecture looks like this:

  • Public DNS for app.example.com points at your home internet edge
  • Your router forwards ports 80 and 443 to one reverse proxy host
  • Caddy terminates TLS and forwards traffic to the internal app
  • Your internal DNS server overrides app.example.com to the proxy's LAN IP so local devices stay local

That last bullet is the piece many first-timers miss.

Without the internal DNS override, your laptop on Wi-Fi asks public DNS for app.example.com, gets the public answer, exits through your router, and may hairpin back in through the WAN path. That can work, but it is clumsy. With an override, local devices go straight to the proxy on your LAN while still using the exact same hostname.

Before you start

Use these assumptions for the rest of the guide:

  • You own a domain, such as example.com
  • You have one service to publish, such as Jellyfin on 192.168.1.50:8096
  • You have a Linux Docker host for the reverse proxy
  • You run Pi-hole or another local DNS resolver for the home network
  • You are willing to forward only ports 80 and 443 to the reverse proxy host

If you do not already have a local DNS service, set that up first. Pi-hole and AdGuard Home are both fine choices. If you are still deciding between them, our Pi-hole vs AdGuard Home comparison will help you choose the one that fits your network.

> What could go wrong: Do not start with Proxmox, your NAS admin UI, your router, or anything else that controls the rest of the lab. Pick a low-risk service first. Media apps and dashboards are much safer first targets than management planes.

Step 1: Pick one safe service and one hostname

Why this matters:

Beginners get in trouble when they try to front five services at once. If you start with one app, you will know whether failures come from DNS, certificates, routing, or the application itself.

For this example, we will publish Jellyfin like this:

  • Service: Jellyfin
  • Internal address: 192.168.1.50:8096
  • Public hostname: jellyfin.example.com
  • Reverse proxy host LAN IP: 192.168.1.20

If your app already runs in Docker on the same host as Caddy, the backend target can be a container name instead of a raw IP.

Step 2: Run Caddy in Docker

Why this matters:

Caddy is the shortest path I know from "I have an app" to "I have working HTTPS." It handles certificate requests and renewals automatically, so you do not need a separate Certbot schedule or hand-built Nginx config just to get your first service online.

Create a working directory:

mkdir -p ~/homelab/caddy\ncd ~/homelab/caddy

Create docker-compose.yml:

services:\n  caddy:\n    image: caddy:2\n    container_name: caddy\n    restart: unless-stopped\n    ports:\n      - "80:80"\n      - "443:443"\n      - "443:443/udp"\n    volumes:\n      - ./Caddyfile:/etc/caddy/Caddyfile\n      - caddy_data:/data\n      - caddy_config:/config\n\nvolumes:\n  caddy_data:\n  caddy_config:

Create Caddyfile:

jellyfin.example.com {\n    reverse_proxy 192.168.1.50:8096\n}

Start Caddy:

docker compose up -d

Check the container:

docker compose ps\ndocker logs caddy --tail 50

Expected behavior:

  • The container should stay up
  • Caddy will wait for traffic and attempt certificate management once the hostname resolves correctly and ports 80/443 reach the proxy

If your backend app is on the same Docker network, you can use a service name instead of an IP:

grafana.example.com {\n    reverse_proxy grafana:3000\n}

That is often cleaner long term because the hostname stays stable even if the container IP changes.

Step 3: Open only the ports you actually need

Why this matters:

This is where HTTPS can either improve your security or quietly make things worse. The reverse proxy should be the front door. Your backend apps should not each get their own public port just because the proxy makes them easier to reach.

If you use UFW on the proxy host, allow only web traffic:

sudo ufw allow 80/tcp\nsudo ufw allow 443/tcp\nsudo ufw status

Then configure your router to forward:

  • TCP 80 -> 192.168.1.20
  • TCP 443 -> 192.168.1.20

That is it.

Do not forward:

  • 8096 for Jellyfin
  • 8006 for Proxmox
  • 5000/5001 for NAS admin
  • random app ports "just in case"

If you want to understand when a public port is justified and when a tunnel or VPN is safer, read Cloudflare Tunnel vs VPN vs Port Forwarding.

Step 4: Create the public DNS record

Why this matters:

TLS certificates depend on names resolving correctly. Before your browser can trust jellyfin.example.com, the ACME process needs a working DNS answer and a reachable path back to the reverse proxy.

In your DNS provider's panel, create an A record:

  • Name: jellyfin
  • Value: your home WAN IP or your dynamic DNS target pattern

Then verify from the shell:

dig +short jellyfin.example.com

You should see the public IP you expect.

If your ISP changes your WAN IP often, use dynamic DNS or a DNS provider API-backed workflow later. For the first pass, a stable manual record is enough to prove the pattern.

> What could go wrong: If dig still shows an old value, do not keep troubleshooting Caddy. Fix DNS first. HTTPS setup always feels mysterious when the real problem is stale DNS.

Step 5: Let Caddy obtain the certificate

Why this matters:

At this point, you have all the ingredients for a valid certificate: a hostname, a reachable proxy, and ports 80/443 mapped correctly. Caddy handles the actual ACME flow for you.

Once DNS and port forwarding are in place, request the site:

curl -I http://jellyfin.example.com\ncurl -I https://jellyfin.example.com

Then watch the logs:

docker logs caddy --tail 100 -f

What you want to see:

  • Caddy provisioning a certificate
  • no endless retry loop for ACME validation
  • successful HTTPS response headers from the app

If you hit certificate failures, check these in order:

1. Public DNS resolves correctly

2. Your router forwards 80/443 to the proxy host

3. Your ISP is not blocking inbound traffic on those ports

4. The hostname in your Caddyfile exactly matches the public DNS record

For background on why HTTP-01 and DNS-01 work differently, the official Let's Encrypt challenge types documentation is worth bookmarking.

Step 6: Add the internal DNS override so local traffic stays local

Why this matters:

This is the step that turns "I got HTTPS working" into "this actually feels good to use every day."

Without internal DNS, local clients may still resolve jellyfin.example.com to your public path. That means extra hops, possible hairpin NAT weirdness, and inconsistent behavior.

If you use Pi-hole, create a local DNS override on the Pi-hole host.

For a dnsmasq-based Pi-hole setup, create a custom file:

echo 'address=/jellyfin.example.com/192.168.1.20' | sudo tee /etc/dnsmasq.d/99-homelab-override.conf\nsudo pihole restartdns

Now test resolution from a client that uses Pi-hole:

nslookup jellyfin.example.com

You want the answer to be 192.168.1.20, which is the proxy's LAN IP.

If you use AdGuard Home instead, create an equivalent DNS rewrite in the UI:

  • Domain: jellyfin.example.com
  • Answer: 192.168.1.20

This is a good point to mention one naming rule that saves headaches: use real public names you control, not .local names. .local often collides with mDNS behavior, which is a polite way of saying it can waste your Saturday.

If your homelab uses multiple internal-only subdomains, you may later want a broader split-horizon pattern or a home.arpa plan. For a first tutorial target, one explicit override is simpler and easier to debug.

Step 7: Test both the local path and the remote path

Why this matters:

A lot of people only test on Wi-Fi, assume everything is done, and then discover remote access is broken later. Others test only remotely and never notice their local clients are still hairpinning out through the WAN path.

Test from inside the LAN:

curl -I https://jellyfin.example.com

Test the DNS answer locally:

nslookup jellyfin.example.com

If you want to force a local test from the shell without waiting on client DNS, use:

curl -I --resolve jellyfin.example.com:443:192.168.1.20 https://jellyfin.example.com

Then test from outside your network:

  • turn off Wi-Fi on your phone
  • browse to https://jellyfin.example.com
  • confirm the certificate is valid and the page loads normally

That local-versus-remote pair is the whole goal. Same hostname. Same certificate. Different network path depending on where you are.

Step 8: Harden the setup before you add more apps

Why this matters:

HTTPS is not the same thing as safe exposure. A reverse proxy is like the lobby in an apartment building. It can make entry cleaner and more controlled, but it does not mean every room should have a door directly onto the street.

Before you add the next service:

1. Keep admin apps private.

- Proxmox, NAS admin, router UI, and backup dashboards should stay LAN-only or VPN-only.

2. Harden host access.

- If you have not locked down SSH yet, follow our SSH hardening guide.

3. Add identity protection for public services.

- For family-facing apps that need internet access, an auth layer matters. Our Authelia SSO guide is a good next step.

4. Keep backups boring and regular.

- Your proxy config, DNS overrides, and app data should all be backed up. If the reverse proxy host dies, recovery should be routine, not archaeology.

5. Add more services one at a time.

- Reuse the same pattern: new hostname, new reverse proxy entry, new internal DNS override if needed.

When Nginx Proxy Manager makes more sense

Caddy is my favorite command-first choice for beginners because the config is so short. That said, some people learn faster with a GUI. If that is you, Nginx Proxy Manager is a perfectly reasonable alternative.

Use Nginx Proxy Manager when:

  • you prefer forms over config files
  • you want certificate management from the browser
  • you are helping family members or teammates who will maintain it later

Use Caddy when:

  • you want fewer moving parts
  • you like versioning configs in files
  • you want the easiest path to repeatable setups

Neither choice is morally superior. Pick the one you will actually maintain.

Common Mistakes

1. Exposing the app port and the proxy

This defeats the point of centralizing traffic. Publish the proxy, not the backend.

2. Testing only on your home Wi-Fi

You need one local test and one cellular or remote test.

3. Forgetting the internal DNS override

This is the classic "HTTPS works, but local performance still feels weird" mistake.

4. Using .local hostnames for everything

That often collides with mDNS and creates confusing name-resolution behavior.

5. Putting Caddy on a different Docker network from the backend

If Caddy cannot reach the app, cert issuance may succeed while the service still fails at runtime.

6. Starting with Proxmox or your NAS admin panel

Start with a low-risk service. Public exposure is a privilege, not a default setting.

7. Treating HTTPS as the whole security plan

TLS protects the connection. It does not replace updates, auth, backups, segmentation, or sane exposure decisions.

What to learn next

Once this first service works cleanly, the next good topics to learn are:

  • split-horizon DNS patterns for multiple services
  • wildcard certificates with DNS challenge for internal-only apps
  • SSO in front of public services
  • network segmentation so your proxy can reach only what it needs
  • better service inventory and backup discipline

If you want a smooth progression, I would go in this order:

1. DNS fundamentals - Homelab DNS: 9 Rules

2. reverse proxy GUI alternative - Nginx Proxy Manager on Docker

3. edge security - SSH Hardening Guide

4. identity layer - Authelia SSO for your homelab

Frequently Asked Questions

Can I use HTTPS for a homelab service that stays on my local network?

Yes. The simplest beginner path is to use a real hostname you control, terminate TLS at a reverse proxy like Caddy, and point local DNS at the proxy's LAN IP. That gives you a valid certificate and keeps local traffic on the LAN.

Do I need a reverse proxy for homelab SSL/TLS?

Usually, yes. A reverse proxy centralizes certificates, gives you clean hostnames, and keeps individual app ports off the public internet. You can run certificates per app, but it gets messy quickly.

What is split-horizon DNS in a homelab?

Split-horizon DNS means local clients get an internal answer for a hostname while external clients get the public answer. It lets the same URL work both at home and away from home without changing bookmarks.

Should I use Caddy or Nginx Proxy Manager for my first homelab HTTPS setup?

Use Caddy if you want the simplest file-based setup. Use Nginx Proxy Manager if you prefer a web UI. Both are good choices. The right answer is the one you will keep updated and understand six months from now.

If you have been avoiding HTTPS in your homelab because every guide seemed to assume you were ready to become your own certificate authority, I hope this one felt more grounded. Start with one service, get the pattern working cleanly, and then expand. Once the first hostname works both locally and remotely, the rest of the setup stops feeling like magic and starts feeling like a system you actually understand.