Homelab Update: Tailscale Edge + Peer Relay Progress
“The network is the product when the product is the network.”
Today was a networking-focused homelab sprint: I pushed forward on the Tailscale edge migration, kept the webhook ingress safe, and started the OCI peer relay build. This post is a walkthrough of what changed and the real-world gotchas that surfaced.
Note: This post does not expose internal IPs, private hostnames, or Tailscale DNS names. I use placeholders like <TAILNET_DOMAIN> and <HOMELAB_DOMAIN> on purpose.
🤔 Why this change
- I want one consistent HTTPS entry point for every internal service, instead of per-service Tailscale Serve config.
- A free custom domain plus a wildcard SSL cert keeps internal URLs clean and production-like without paying for certs per service.
- Centralizing routing reduces port conflicts and makes reboots less fragile.
- Webhooks still need public ingress, so Funnel stays, but only for a webhook-only proxy.
- A peer relay improves throughput when direct peer-to-peer connections are not possible.
✅ What I Progressed Today
1) Edge Caddy tailnet migration (walkthrough)
Goal: centralize HTTPS on an edge node and route everything through a single Caddy instance.
Walkthrough:
- Confirmed the edge node plan and the custom domain pattern for internal services (
https://<service>.<HOMELAB_DOMAIN>). - Locked in a free custom domain for internal services and planned a wildcard DNS record.
- Validated the migration path: keep the current Tailscale Serve config on the Docker VM until edge cutover is complete.
- Documented the steps for wildcard TLS via DNS-01 so Caddy can terminate
*.<HOMELAB_DOMAIN>cleanly. - Updated internal docs to reflect the in-progress state (so future me doesn’t forget what’s live and what’s planned).
2) Webhook ingress stays on Funnel (for now)
I kept the webhook-only proxy behind Tailscale Funnel on <TAILNET_DOMAIN>. This lets public webhooks flow without exposing the main app UI.
3) OCI peer relay build (in progress)
I created the OCI Free Tier VM and opened the relay port, but SSH connections are hanging during banner exchange. The instance also got stuck in a “Stopping” state in the console. That blocks the relay setup until the VM stabilizes or gets rebuilt.
🔧 Setup details (high level)
1) Free domain + DNS
- Registered a free domain for internal services.
- Pointed a wildcard record (
*.<HOMELAB_DOMAIN>) at the tailnet edge address.
2) Edge node
- Joined the edge host to the tailnet.
- Installed Caddy and bound it to the tailnet interface (not
0.0.0.0).
3) Wildcard SSL cert
- Issued a wildcard certificate using ACME DNS-01.
- Stored the cert/key on the edge host and referenced them in the Caddy config.
4) Caddy routing
- Added reverse-proxy routes for each internal service:
https://<service>.<HOMELAB_DOMAIN> -> <LAN_SERVICE_URL>.
5) Keep Serve during migration
- Left existing Tailscale Serve entries in place until edge cutover is complete.
6) Webhook-only ingress
- Funnel stays on
<TAILNET_DOMAIN>and targets a proxy that only allows webhook paths.
🔌 n8n + webhooks (detail)
The goal is simple: keep the n8n UI private, but still accept public webhooks.
- Internal UI:
https://n8n.<HOMELAB_DOMAIN>(tailnet-only) - Public webhooks:
https://<TAILNET_DOMAIN>/webhook/*
I use a webhook-only reverse proxy so Funnel never exposes the full n8n UI:
:<WEBHOOK_PROXY_PORT> {
@webhooks path /webhook/* /webhook-test/*
reverse_proxy @webhooks <N8N_LOCAL_URL>
respond 404
}
Then Funnel only points at that proxy:
tailscale funnel --https=443 --bg --yes http://localhost:<WEBHOOK_PROXY_PORT>
Finally, n8n gets explicit base URLs so links and callbacks are correct:
N8N_EDITOR_BASE_URL=https://n8n.<HOMELAB_DOMAIN>
WEBHOOK_URL=https://<TAILNET_DOMAIN>
🧭 Current Architecture (Simplified)
Internet (webhooks)
|
v
Tailscale Funnel
|
webhook-only proxy
|
v
n8n
Tailnet clients
|
v
Edge Caddy (in progress)
|
v
Internal services on LAN
🧨 Gotchas (And What Actually Helped)
1) Funnel hostnames are locked to *.ts.net
Gotcha: Funnel doesn’t support custom domains. You can’t funnel traffic through <HOMELAB_DOMAIN>.
What helped: Accept Funnel on <TAILNET_DOMAIN> and route only webhooks through it, while everything else lives behind edge Caddy.
2) Tailscale Serve can steal host ports
Gotcha: Serve can bind ports before Docker does, which makes containers look “up” but unreachable.
What helped: Keep Serve active only where it’s needed and leave edge cutover for later. I also keep a boot workflow that temporarily disables Serve during container startup.
3) Wildcard TLS needs DNS-01
Gotcha: If you want *. <HOMELAB_DOMAIN> certificates for a tailnet-only host, HTTP-01 won’t work.
What helped: Use DNS-01 with an API-capable DNS provider and let Caddy load the cert files directly.
4) Caddy should bind to the tailnet interface
Gotcha: Binding to 0.0.0.0 exposes the edge proxy on the LAN, which I don’t want.
What helped: Bind Caddy explicitly to the tailnet interface IP and keep the service tailnet-only.
5) OCI VM stuck in “Stopping” state
Gotcha: The VM won’t complete stop/start cycles and SSH hangs during banner exchange.
What helped: Documenting the state and preparing a fallback plan to rebuild the VM if it doesn’t recover. Sometimes the fastest fix is a clean rebuild.
✅ Results So Far
- The edge migration path is clear and documented.
- Webhooks remain safe and isolated behind Funnel.
- The peer relay plan is in place, with a known blocker tracked for recovery.
🔜 Next Up
- Finish edge cutover and remove per-service Tailscale Serve on the Docker VM.
- Re-attempt OCI peer relay once the VM is stable (or rebuild if needed).
- Validate that all tailnet HTTPS routes resolve correctly post-migration.
📚 References
🧠 Meta Description
Today’s homelab update focuses on the Tailscale edge migration and an OCI peer relay attempt. This walkthrough captures the steps taken, the gotchas encountered, and what’s next.