For a while my homelab was a small museum of certificate handling. One service had a self-signed cert I clicked through. One had a real Let's Encrypt cert it renewed itself via HTTP-01, which meant punching port 80 through to it. One just ran on plain http and I told myself I would "sort it later". Later arrived this week, between the turkey and the new year.
The fix was the obvious one I had been avoiding: put everything behind a single reverse proxy and let that one box own TLS. Nothing internal listens on the internet now. The proxy gets a wildcard cert for *.home.example over DNS-01, so there is no port 80 to expose at all, the challenge happens against my DNS provider's API.
traefik:
certResolver: cloudflare # DNS-01, wildcard
entryPoints: websecure (443)
The nice part is that adding a service is now boring. It gets a label, it gets a hostname, it gets HTTPS for free, and I never think about its certificate again. The boring is the point. Renewals are one cron-shaped thing in one place rather than three half-remembered ones scattered across hosts.
If you have been meaning to do this and keep finding reasons not to, the DNS-01 route is what makes it painless. No inbound ports, wildcard covers everything, and the only secret you manage is the DNS API token. Worth the afternoon.