Reverse proxy and TLS
A reverse proxy is the web's concierge: it owns the public address, speaks HTTPS to the world, and walks each request to a plain service behind it. Putting one in front of tunnel-me is the recommended way to serve HTTPS — one wildcard certificate covers your base domain and every tunnel at once, and the proxy handles the details.
flowchart LR
v(["Visitor"]) -->|"HTTPS"| p["Reverse proxy<br/>wildcard certificate"]
p -->|"HTTP"| t["tunnel-me :8080"]
With this setup, leave --tls.enabled off (plain HTTP between proxy and tunnel-me on a private network is fine) and run the server on its default port.
The wildcard certificate
Every tunnel is a subdomain, so the certificate must be valid for *.tunnels.example.com and tunnels.example.com. Regular HTTP-based certificate renewal (Let's Encrypt "HTTP-01") cannot prove ownership of a wildcard name — use a DNS-based challenge instead (Let's Encrypt "DNS-01"): the certificate authority asks you to publish a temporary TXT record in your domain, the provider-specific Caddy/nginx plugins automate this. Most DNS providers are supported by the automation plugins of the popular proxies.
If you'd rather not automate, you can also buy any certificate covering *.tunnels.example.com + tunnels.example.com and drop the files in.
If you terminate TLS on tunnel-me itself instead (with
--tls.enabled), the same wildcard requirement applies to the files you pass to--tls.cert.
Telling tunnel-me about the proxy
When TLS ends at the proxy, add this to the server:
--auth.oidc.trust-proxy
It makes every sign-in that goes through the proxy honor the forwarded protocol and host headers — session cookies get their secure flag and redirects use your public address. Without it, sign-ins misbehave behind TLS-terminating proxies. It only affects OIDC sign-ins; basic/htpasswd panels work unchanged.
Caddy
Caddy can request and renew the wildcard certificate on its own (this uses its DNS plugin for your provider — install the build for your DNS provider):
tunnels.example.com {
reverse_proxy 127.0.0.1:8080
}
*.tunnels.example.com {
tls {
dns <provider> <provider-specific-options>
}
reverse_proxy 127.0.0.1:8080
}
Websockets — which the agents use — work without extra configuration.
nginx
server {
listen 443 ssl;
http2 on;
server_name tunnels.example.com *.tunnels.example.com;
ssl_certificate /etc/ssl/tunnels.example.com.crt; # must cover wildcard + base
ssl_certificate_key /etc/ssl/tunnels.example.com.key;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
# websocket support for /agent (agents' connection)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 3600s;
}
}
with the upgrade mapping once in the http block:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
proxy_read_timeout matters: an agent connection sits idle between visitor requests, and the nginx default of 60 seconds would keep dropping it (the agent reconnects, but pointlessly). An hour is a safe value.
Traefik (file provider)
http:
routers:
tunnel-me:
rule: "Host(`tunnels.example.com`) || HostRegexp(`{sub}.tunnels.example.com`)"
service: tunnel-me
tls:
certResolver: myresolver
services:
tunnel-me:
loadBalancer:
servers:
- url: "http://127.0.0.1:8080"
Websocket forwarding is automatic. For the wildcard certificate use a DNS-01 challenge in your certificate resolver configuration.
The X-User header and proxy-based login
Tunnels forward the signed-in visitor's username to your app in the X-User header (switchable per tunnel). If you also use the webauth panel login — where the proxy sets a header naming the signed-in admin — be aware both features read headers that a client could try to forge. Configure your proxy to overwrite (not append) those headers, and give the panel login its own header name (e.g. X-Panel-User) if your proxy sets X-User for other apps.