
Using a Public Domain to Access Private Services
In the previous article, I explained how to configure a Mac mini homelab and use Tailscale to connect to it remotely. Although Tailscale provides MagicDNS, you still have to access services using the MagicDNS:port format. This article walks through, step by step, how to use a public domain with HTTPS to access services running inside your private network.
The limitation of Tailscale
Imagine the following setup:
-
Two Docker services:
-
Service A running on port
`3000` -
Service B running on port
`3001`
-
-
A domain
`abc.com`managed by Cloudflare -
A public VPS with an IPv4 address
In a traditional setup, you would expose these services like this:
-
`a.abc.com`→ Service A -
`b.abc.com`→ Service B
You’d add DNS records in Cloudflare and use Nginx to reverse-proxy traffic to the corresponding services.

What Changes with Tailscale?
Tailscale provides MagicDNS, for example:
homelab.xxxx.ts.net
However, MagicDNS does not support wildcard subdomains, which means:
You cannot access services via:
-
`a.homelab.xxxx.ts.net` -
`b.homelab.xxxx.ts.net`
Instead, Tailscale offers the `serve` feature, which forces you to use:
-
`homelab.xxxx.ts.net:3000` -
`homelab.xxxx.ts.net:3001`
Technically it works—but let’s be honest: It’s ugly.

Can We Use a Public Domain?
Yes—but with a catch.
You can configure a wildcard subdomain in Cloudflare, such as:
*.homelab.abc.com
and point it to your homelab’s private IP address (reachable only through Tailscale).
With this setup, once the Tailscale tunnel is active, you can access:
-
`a.homelab.abc.com` -
`b.homelab.abc.com`
via a reverse proxy (Nginx or Caddy).
Sounds perfect, right?

The HTTPS Problem
There’s one critical issue.
Modern browsers strongly discourage (or outright block) visiting websites without HTTPS.
You might think:
“No problem, I’ll just get a Let’s Encrypt certificate.”
Unfortunately, that won’t work.
Let’s Encrypt will not issue certificates for:
-
Private IP addresses
-
Non-publicly routable endpoints (like Tailscale IPs)
So how do we get HTTPS?
Using a Cloudflare API Token
The solution is DNS-01 validation using Cloudflare’s API. Go to: https://dash.cloudflare.com/profile/api-tokens and click Create Token.


Permissions
-
Zone → Zone → Read
-
Zone → DNS → Edit
Zone Resources
- Include → All zones
Finish the setup and save the generated API token. Important: This token is shown only once—store it securely.

Reverse Proxy Your Services (with Caddy)
This time, we’ll use Caddy v2 instead of Nginx, because Caddy handles HTTPS automation extremely well.
Create the following structure:
. ├── caddy_config/ ├── caddy_data/ ├── Caddyfile └── docker-compose.yml

docker-compose.yml
Paste your Cloudflare API token into the environment section and start Caddy with: `docker-compose up -d`
services: caddy: image: caddy:latest container_name: caddy restart: unless-stopped ports: - "80:80" - "443:443" environment: - CLOUDFLARE_API_TOKEN=<YOUR_CLOUDFLARE_API_TOKEN> volumes: - ./Caddyfile:/etc/caddy/Caddyfile - caddy_data:/data - caddy_config:/config volumes: caddy_data: caddy_config:
Install the Cloudflare DNS Module
The official Caddy image does not include the Cloudflare DNS module by default. Run the following commands:
docker exec -it caddy sh caddy add-package github.com/caddy-dns/cloudflare
Tailscale DNS Configuration (Important)
Before configuring Caddy, go to Tailscale Admin Console and add public DNS resolvers:
-
Cloudflare DNS:
`1.1.1.1` -
Google DNS:
`8.8.8.8`
Path:
DNS → Global nameservers
This ensures DNS-01 validation works correctly inside the Tailscale network.

Configure the Caddyfile
Below is a minimal example.
Visiting `a.homelab.abc.com` will return a static response:
{ acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN} } a.homelab.abc.com { respond / "Hello, world! This is a static response from Caddy." 200 }
Once this is working, you can replace `respond` with `reverse_proxy` to point to your actual services.

PREVIOUS POST
Using Tailscale to Manage Your Homelab