One Port to Route Them All

I have a Raspberry Pi 4 on my network. I call it the nasbox. It started as somewhere to run a log ingestion server for my ESP32 devices — they post diagnostic logs over HTTPS and the Pi files them away. That was the plan. The plan immediately raised a question: how do I get HTTPS onto a Pi without it being a mess?
The obvious answer is self-signed certificates. The obvious answer is also wrong, because self-signed certificates mean either disabling TLS verification on every device that talks to the Pi (which defeats the point), or distributing a custom CA root to everything on your network (which is a different kind of mess). Let's Encrypt exists. Use Let's Encrypt.
The slightly less obvious answer is: get a wildcard certificate, set up nginx to route by subdomain, and make adding a new service a five-line config block. That's what I did.
The certificate
For a single domain — logs.nasbox.nakomis.com — you'd use the HTTP-01 challenge. Let's Encrypt connects to your server on port 80 and checks a file you put there. Simple enough.
Wildcard certificates (*.nasbox.nakomis.com) don't work that way. You can't serve an HTTP challenge for a wildcard — the challenge is domain-specific. Instead you need DNS-01: prove you control the domain by adding a TXT record.
I'm already using Route53 for the domain. certbot-dns-route53 handles the whole thing automatically:
pip install certbot certbot-dns-route53
certbot certonly \
--dns-route53 \
--agree-tos \
--non-interactive \
--email martin@nakom.is \
-d "nasbox.nakomis.com" \
-d "*.nasbox.nakomis.com"
Certbot creates a _acme-challenge TXT record in Route53, waits for it to propagate, then removes it. The certificate lands in /etc/letsencrypt/live/nasbox.nakomis.com/ and is valid for every subdomain I'll ever create. Renewal is a cron job:
0 3 * * * certbot renew --quiet && nginx -s reload
That's certificate management done. Permanently.
The nginx trick
Here's where it gets slightly more interesting, because not everything on the Pi terminates TLS the same way.
The nasbox services (the log server, and whatever else I add) are happy for nginx to decrypt HTTPS and proxy plain HTTP to them internally. But I also have Home Assistant running on a different machine on the LAN — it has its own certificate and handles TLS itself. Both need to be reachable via port 443. Both are going to different places.
The naive approach is to run HA on a non-standard port, or use a path prefix, or do something else that makes the URLs ugly. The actual approach is nginx's stream module and ssl_preread.
ssl_preread is a stream-level directive that reads the SNI hostname from a TLS ClientHello before the connection is decrypted. nginx sees which hostname the client is asking for and routes accordingly — without ever terminating TLS itself. The client gets handed off to wherever that hostname should go, and TLS is handled at the destination.
stream {
map $ssl_preread_server_name $backend_443 {
homeassistant.nakomis.com 172.29.0.20:8123; # HA handles its own TLS
default 127.0.0.1:8443; # nginx handles TLS for everything else
}
server {
listen 443;
proxy_pass $backend_443;
ssl_preread on;
}
}
Home Assistant traffic goes straight to the HA machine. Everything else goes to localhost:8443, where a separate nginx http block does the TLS termination using the wildcard cert and proxies the decrypted traffic to whatever service is running locally.
The result: port 443 handles everything, each service gets its own subdomain, no certificate management per service. Adding a new service means adding one block to the nginx config:
server {
listen 8443 ssl;
server_name logs.nasbox.nakomis.com;
location / {
proxy_pass http://127.0.0.1:8765;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
The wildcard cert already covers it. The DNS record already covers it (a *.nasbox.nakomis.com A record in Route53 pointing at the external IP). Nothing else to touch.

The log server
The first service sitting behind this is a small FastAPI app that accepts log uploads from ESP32 devices. Each device posts to /logs/{device_name} with a Bearer token; logs land in /var/log/logserver/{device_name}/ on the Pi.
@app.post("/logs/{device_name}")
async def upload_log(
device_name: str,
request: Request,
credentials: HTTPAuthorizationCredentials = Depends(bearer),
) -> dict:
_check_token(credentials)
body = await request.body()
# write to per-device log directory
return {"ok": True}
Running under uvicorn via systemd, bound to 127.0.0.1 only. nginx is the only thing that talks to it.
The ESP32 side is straightforward — https://logs.nasbox.nakomis.com/logs/my-device with a Bearer header. A proper certificate means no certificate verification exceptions, no custom CA roots distributed to firmware. It just works like any HTTPS endpoint.
A check worth doing first
Before building any of this, I did one quick sanity check: NAT hairpinning.
The external DNS record points *.nasbox.nakomis.com at my external IP. The router forwards port 443 to the Pi. The question is whether devices inside my LAN can reach the Pi via its external IP — traffic that goes out and comes back in through the router. Some consumer routers don't support this (called NAT loopback or hairpinning), in which case internal devices just time out when they try to reach an external IP that's actually on the same network.
If hairpinning doesn't work, you need split-horizon DNS: a local DNS server (typically dnsmasq) that answers *.nasbox.nakomis.com with the Pi's LAN IP for internal clients, while Route53 handles the same names externally. More moving parts.
curl -s https://logs.nasbox.nakomis.com/health
If that responds from inside the LAN, hairpinning is fine and Route53 alone is sufficient. Mine works. The dnsmasq setup exists as a fallback if it ever stops working.
What I actually learned
The ssl_preread trick is the thing I'd use again immediately. The common approach for "multiple services on one IP" is a reverse proxy that terminates TLS and routes by path or Host header — which works, but requires the proxy to hold the certificate for every domain. The stream-level SNI approach means the proxy doesn't need to know anything about TLS for pass-through traffic, and you can mix terminating and non-terminating services on the same port without any awkwardness.
DNS-01 for the wildcard cert means renewal never requires port 80 to be accessible and never involves putting challenge files on the server. The certbot-dns-route53 plugin handles it entirely within AWS.
And the wildcard cert + subdomain pattern is simply correct for a multi-service host. The cost of getting a second service up is five lines of nginx config. Everything else is already done.
Martin Harris connects real hardware to cloud services. If you've got an ESP32 that needs somewhere to post its grievances, there's now an HTTPS endpoint for that.
If you find this blog useful, please consider a donation.
