Skip to main content

TLS Certificates

Miren automatically provisions TLS certificates for your applications using Let's Encrypt. By default, no configuration is needed — when you set a route for your app, Miren obtains a certificate automatically.

Minimum working example​

There's nothing to configure — set a route and HTTPS follows:

miren route set myapp.example.com myapp

The first request to the hostname triggers certificate provisioning from Let's Encrypt; renewal is automatic. If your server isn't reachable from the public internet, switch to a DNS-01 challenge:

/etc/miren/server.toml
[tls]
acme_dns_provider = "cloudflare"
acme_email = "you@example.com"

The DNS provider also needs its API credential in the Miren server's environment (for Cloudflare, CF_DNS_API_TOKEN) — the DNS-01 section shows how to wire it up.

How It Works​

When a request arrives for a hostname with a configured route, Miren provisions a TLS certificate from Let's Encrypt using the ACME protocol. Certificates are cached on disk and renewed automatically before they expire.

Unrouted hostnames

Hostnames without a configured route are served with a self-signed fallback certificate (browsers will show a warning).

A name one level under a route, such as foo.myapp.example.com under a wildcard route *.myapp.example.com or pr-123.myapp.example.com under myapp.example.com, gets a certificate on its first request only if something vouches for it: a live ephemeral deploy with that label, or the route's app answering its TLS check. Otherwise it gets the fallback certificate. Wildcard DNS sends every name to your cluster, so without this check anyone could make Miren request a certificate for any name they liked. See Certificates for wildcard subdomains.

Challenge Types​

Let's Encrypt needs to verify that you control the domain before issuing a certificate. Miren supports two verification methods.

HTTP-01 Challenge (Default)​

The default method. Let's Encrypt makes an HTTP request to your server on port 80 to verify domain ownership.

Requirements:

  • Your server must be reachable from the public internet on ports 80 and 443
  • DNS for your domain must point to the server's public IP

This is the zero-configuration option — it works out of the box with miren server install.

DNS-01 Challenge​

If your server isn't publicly reachable (e.g., it's behind a VPN like Tailscale, on a private network, or doesn't have ports 80/443 open to the internet), you can use DNS-01 challenges instead. This method proves domain ownership by creating a DNS TXT record, so it works regardless of whether your server is reachable from the internet.

Miren uses lego for DNS challenges, which supports 90+ DNS providers.

Configuration​

Add the DNS provider and ACME email to your server config file:

/etc/miren/server.toml
[tls]
acme_dns_provider = "dnsimple"
acme_email = "you@example.com"

Each DNS provider requires credentials passed via environment variables. Create an environment file for the Miren service:

/var/lib/miren/server/env
DNSIMPLE_OAUTH_TOKEN=your-token-here

Then update the systemd service to load the environment file and config:

sudo systemctl edit miren --force

Add the environment file:

[Service]
EnvironmentFile=/var/lib/miren/server/env

Restart to pick up the changes:

sudo systemctl restart miren

Provider Examples​

Each provider needs different environment variables. Here are a few common ones:

DNSimple:

acme_dns_provider = "dnsimple"
DNSIMPLE_OAUTH_TOKEN=your-token

Cloudflare:

acme_dns_provider = "cloudflare"
CF_DNS_API_TOKEN=your-token

Route 53 (AWS):

acme_dns_provider = "route53"
AWS_ACCESS_KEY_ID=your-key
AWS_SECRET_ACCESS_KEY=your-secret
AWS_REGION=us-east-1

See the lego DNS provider documentation for the full list of supported providers and their required environment variables.

Ingress Modes and TLS​

Whether Miren terminates TLS at all (and on which ports) is set by ingress.mode. The default tls-autoprovision mode is what this page has been describing: TLS on :443, plus :80 for the HTTPS redirect and HTTP-01 ACME challenges.

Two other modes are available for deployments where Miren sits behind a TLS-terminating proxy (nginx, Caddy, Cloudflare Tunnel, tailscale serve, ALB). This is also the answer when something else already owns :443 on the host: rather than fighting over the port, let that proxy keep it and run Miren behind it with behind-proxy-http.

ModeWhat Miren doesCert source
tls-autoprovision (default)Binds :443 for TLS and :80 for redirect / HTTP-01 ACME[tls] (ACME or self-signed)
behind-proxy-httpPlain HTTP at the configured address (default 127.0.0.1:80); TLS lives at the proxyn/a (proxy terminates TLS)
behind-proxy-httpsTLS terminated at the configured address (default 127.0.0.1:443); no :80 listener, so no HTTP-01 ACME[tls] self-signed or DNS-01 ACME only

Under behind-proxy-http, Miren learns the visitor's original scheme from the proxy's X-Forwarded-Proto (or RFC 7239 Forwarded) header, and uses it to decide whether auth cookies are marked Secure and what X-Forwarded-Proto your app receives. It selects the visitor address for access logs from X-Forwarded-For, counting from the right past ingress.trusted_proxy_hops trusted proxies (one by default). In the other two modes Miren terminates TLS itself, so it ignores those headers on inbound requests and goes by the connection. Traffic arriving through a Miren Anywhere POP is always https, since the POP terminates TLS for visitors.

behind-proxy-http trusts the proxy, so lock it down

The proxy must set X-Forwarded-Proto and overwrite any value the client sent. If the listener is bound anywhere other than loopback, a firewall must limit it to the proxy's addresses; see [ingress] for why.

See Server Configuration Reference → [ingress] for the full schema. The HTTP-01 ACME flow described above only applies under tls-autoprovision; under behind-proxy-https, certs must come from DNS-01 ACME or be self-signed because Miren doesn't bind :80 in that mode (and the public DNS for the hostname points at the proxy anyway, not at Miren).

TLS Settings Reference​

All TLS settings live under the [tls] section of the server config file (typically /etc/miren/server.toml — see Server Configuration for the search order). The three ingress-cert settings below are consulted only under TLS-terminating ingress modes:

SettingCLI FlagDescription
acme_email--acme-emailEmail for Let's Encrypt account registration and expiry notifications
acme_dns_provider--acme-dns-providerDNS provider name for DNS-01 challenges (e.g., cloudflare, route53, dnsimple)
self_signed--self-signed-tlsUse a self-signed cert instead of ACME (development, or behind a non-verifying TLS proxy)

Two additional [tls] settings apply to the API server and etcd certs rather than ingress, so they're valid under any ingress mode:

SettingCLI FlagDescription
additional_names--dns-namesExtra DNS names appended to the API server and etcd cert SANs. Under tls-autoprovision, these can also get an ingress cert (see below)
additional_ips--ipsExtra IPs appended to the API server and etcd cert SANs, and forced into the advertised address list
additional_names and the ingress cert

Under tls-autoprovision with HTTP-01 challenges, Miren treats each additional_names entry as a name the server answers to as itself. It gets an ACME certificate without needing a route, as soon as its DNS points at the cluster. That's what lets a self-hosted cluster serve its workload identity issuer over valid TLS. IPs, single-label names, wildcards, and reserved suffixes like .internal and .local are skipped.

With acme_dns_provider set, only the first entry gets an ingress cert, and only when it's the workload identity issuer. DNS-01 can issue for a name even when it doesn't point at the cluster, so the rest stay on the API and etcd certs rather than being sent to Let's Encrypt.

additional_ips also controls advertisement

Addresses in additional_ips bypass the filtering applied to auto-discovered addresses and are always advertised to Miren Cloud, which makes this the lever for a cluster behind a static NAT or on a private network. See Running Miren on a Tailnet.

Troubleshooting​

Certificate Not Provisioning​

Check the server logs for ACME errors:

sudo journalctl -u miren | grep -i acme

HTTP-01 challenges: Ensure ports 80 and 443 are reachable from the public internet. See Firewall Configuration for cloud provider-specific guidance.

DNS-01 challenges: Verify your DNS provider credentials are correct and the environment file is loaded:

sudo systemctl show miren | grep EnvironmentFile

Wrong Certificate / Self-Signed Warning​

If you're seeing a self-signed certificate warning for a domain that should have a real certificate, check that a route is configured for that hostname:

miren route

Miren provisions ACME certificates for hostnames with configured routes, names that something vouches for under those routes, and the server's own names (its Miren Cloud hostname and its additional_names, as described above). All other hostnames get the self-signed fallback.