self-hosting

Giving Every Service a Proper Domain Name and HTTPS

How I replaced a wall of port numbers with clean domain names and automatic HTTPS using Traefik as a reverse proxy on my home lab.

For a long time, accessing anything on my home lab meant remembering a port number. Home Assistant was on 8123. Portainer was on 9443. Jellyfin was on 8096. It worked, but it felt unfinished and it did not scale. Adding a new service meant adding another number to remember and another exception in the firewall. None of it used HTTPS, which browsers increasingly make difficult to ignore.

The solution was a reverse proxy. One service sits in front of everything else, reads the domain name in each incoming request, and forwards traffic to the right container. From outside, every service looks like a normal website.

What I was trying to achieve

I wanted each service accessible at a clean internal domain. Home Assistant at home.example.com. Portainer at portainer.example.com. Everything using HTTPS with a valid certificate that browsers accept without complaint. And I wanted adding a new service to be a matter of a few lines, not a separate configuration exercise.

I also set up local DNS rewrites in AdGuard Home first, so those domain names resolve to my Pi's local IP on my home network. That part is covered in the local DNS project. The reverse proxy then handles routing once the traffic reaches the Pi.

Why I chose Traefik

Traefik integrates directly with Docker. You add labels to a container definition describing what domain it should be reachable at, and Traefik picks them up automatically. No central routing file to update every time you add a service. The configuration lives alongside the container it describes.

Traefik also has a built-in ACME client. Point it at Let's Encrypt, give it a domain, and it requests and renews SSL/TLS certificates automatically. HTTPS for every service became something that simply happened, not something I had to configure per application.

What made it harder than expected

I will be honest: Traefik took me longer to get right than anything else in my home lab. The documentation is thorough but assumes familiarity with networking concepts that I had to learn alongside it. The label-based configuration is elegant once you understand the model, but the first time through it was not obvious how the pieces connected.

The most confusing part was the relationship between entrypoints, routers, and services, which are three distinct concepts in Traefik's model that all need to be correctly wired together. I rebuilt my configuration at least twice before it stuck.

A wildcard certificate for a local domain also requires DNS challenge verification rather than the standard HTTP challenge, because the domains are not publicly reachable. This adds a step involving your DNS provider's API. It is documented clearly at doc.traefik.io but it is not the obvious starting point.

What I would do differently

I would read the Traefik documentation end to end before writing a single label. I tried to get something working quickly by copying examples, and the gaps in my understanding of the model cost me hours. The concepts behind a reverse proxy are not complicated, but Traefik has a specific vocabulary and it is worth understanding it fully before starting.

I would also test with a single service before adding anything else. Getting one container routing correctly with HTTPS confirmed before expanding to the rest of the stack would have saved the confusion of multiple things failing at once.

The result

Every service is now at a proper domain name, all using HTTPS, all with certificates that renew automatically. Adding a new container to the proxy takes five minutes. The port number era is over.

For the full configuration reference, the Traefik documentation at doc.traefik.io is the place to go. What I have documented here is the experience of getting there, not the steps to repeat it.