Wildcard routes and DNS-01 certificates
A wildcard route serves every name one level below a domain, such as
alice.tenants.example.com and bob.tenants.example.com. One route then
gives each of your users or tenants their own subdomain, without a manifest
change for each.
This page also explains DNS-01 certificates, which piqueld gets through your DNS provider for wildcard and private routes.
Before you start
- The gateway is enabled; see Serving an application over HTTPS.
- A DNS provider covers the domain: Cloudflare or OVH. piqueld needs it to get the wildcard’s certificate. Through a Cloudflare Tunnel, a public wildcard route needs no DNS provider.
- Run
piquelctlon the host, or on any machine where it is signed in.
Add a wildcard route
-
Add a route whose hostname starts with
*.:-
In the dashboard, add it on the application’s Routes tab like any other route, with
*.tenants.example.comas its Hostname and Visibility set to Public. -
With
piquelctl, quote the hostname so that your shell doesn’t expand the*:piquelctl app route add notes '*.tenants.example.com' web 3000 --visibility public -
In the manifest:
[[spec.routes]] hostname = "*.tenants.example.com" service = "web" port = 3000 visibility = "public"
-
-
Create the DNS records for
*.tenants.example.com: the same records an exact route of its visibility needs, such as an A record to your server for a public route. If piqueld manages your DNS records, it creates them. -
Deploy the application.
The route matches alice.tenants.example.com, but neither
tenants.example.com nor a.b.tenants.example.com. Your service receives the
name the client asked for in the Host and X-Forwarded-Host headers, and
tells tenants apart with it.
Check that it works
In the dashboard, the application’s Routes tab lists the route in
Deployed routes. With piquelctl:
piquelctl app route list notes
Once the route’s state is ready, piqueld has reached a name below it over
trusted HTTPS: the first one-character name that no exact route has, such as
a.tenants.example.com. Its DNS records and certificate work, like an exact
route’s.
Exact routes win
- An exact hostname always wins over a wildcard covering it. Add
admin.tenants.example.comas its own route, and the wildcard no longer serves it. This holds whichever application owns it, and whether it is public or private, so a wildcard never captures a hostname that exists already. The webhook and dashboard hostnames win too. - Names below a wildcard belong to its application. Only that application can add an exact route below it. An exact name that another application routed before the wildcard existed stays that application’s.
- A wildcard can’t lie inside a hostname piqueld uses itself. With
hooks.example.comas the webhook hostname,*.hooks.example.comis refused.*.example.comis accepted, and doesn’t servehooks.example.com.
Wildcards in previews
A preview’s hostnames can start with *. too, for example
*.${{ env.slug }}.dev.example.com. Each preview then has a subdomain per
tenant, with a certificate of its own. See
previews.
DNS-01 certificates
The gateway gets most certificates the usual way: the certificate authority connects to it on port 80 or 443 to check that it serves the hostname. That can’t work for:
- private routes and the dashboard hostname, which the certificate authority can’t reach;
- public wildcard routes, since only DNS can prove control of every name below a domain.
For those, piqueld gets the certificate itself, from Let’s Encrypt unless you choose another authority, through your DNS provider:
- It creates an
_acme-challengeTXT record in the zone. - It waits until every nameserver of the zone serves the record, for up to 10 minutes.
- The certificate authority checks the record and issues the certificate.
- piqueld deletes the record, even when issuing fails.
Your DNS credentials never leave the daemon: the gateway gets the certificates, not the credentials. With a Cloudflare Tunnel, Cloudflare serves public wildcards’ HTTPS, so they don’t need DNS-01.
Which certificate covers a hostname
A private hostname, or the dashboard’s, shares the wildcard certificate of its
parent domain, when that parent is the provider’s zone or inside it. Otherwise
it gets a certificate of its own. A wildcard route uses its own wildcard
certificate. An exact public hostname gets its own certificate from the
gateway. With the zone piquel.fr:
| Private hostname or wildcard route | Certificate |
|---|---|
admin.piquel.fr, staging.piquel.fr | *.piquel.fr |
admin.staging.piquel.fr, *.staging.piquel.fr | *.staging.piquel.fr |
piquel.fr | piquel.fr, since its parent fr is outside the zone |
All the private previews below dev.piquel.fr therefore share one
certificate, *.dev.piquel.fr. Exact public routes below a public wildcard
keep their own certificates from the gateway. Exact private routes below it
share the wildcard’s.
A hostname belongs to the provider with the longest zone containing it. A zone that two providers both list is a conflict, and neither uses it.
Choose the certificate authority
piqueld uses Let’s Encrypt by default. It registers an account the first time it needs a certificate, and accepts the authority’s terms of service.
email is the
account’s contact address. piqueld sends it only when it first
registers, so set it before your first DNS-01 certificate:
services.piqueld.settings.ingress.acme.email = "[email protected]";
Or, in config.toml:
[ingress.acme]
email = "[email protected]"
directory
sets another ACME certificate authority, by the HTTPS URL of its
directory. The default is
https://acme-v02.api.letsencrypt.org/directory. piqueld registers without
external account binding, so the authority must not require it: ZeroSSL, for
example, can’t be the directory.
Renewal and failures
piqueld checks its certificates every minute. It renews one when less than a third of its lifetime remains: about 30 days before a 90-day certificate expires.
- A failed attempt is retried after 5 minutes, then twice as long after each new failure, up to every 6 hours.
- A certificate that no route needs any more isn’t renewed, and is deleted once it expires.
- piqueld renews these certificates only while the daemon runs. Its
certificates live in the data directory, under
ingress/, so back it up whole.
On the host, piquelctl status lists each certificate, the hostnames using it,
and its expiry, in milliseconds since 1970:
Certificate: *.piquel.fr for admin.piquel.fr, staging.piquel.fr (expires at Unix ms 1767225600000)
A failing one adds a Certificate problem: line with the error. In the
dashboard, System status counts them on one certificates row; opening
it lists each, marked issued, pending, failed, or renewal
failing when the latest attempt to renew a stored certificate failed, with
its routes, expiry and last error.
Each issue, renewal and deletion appears in the daemon’s
events. A destination
that receives daemon_failures notifications
is notified when a certificate that keeps failing to renew is less than 14
days from expiry. If it also receives recovery notifications, it is notified
again once the certificate recovers.
Troubleshooting
- A route is
failedwithNo DNS-01 certificate: no configured DNS provider zone contains <hostname>. No provider lists the hostname’s zone. Check the provider’s zones withpiquelctl dns refresh, or Check DNS providers in the dashboard’s System status; see Cloudflare or OVH. - A route or certificate reports
zone <zone> is claimed by more than one DNS provider.piquelctl dns refreshprints the same problem asDNS problem: Zones also claimed by another provider are not used: <zones>. Two configured providers can see the same zone. Make sure only one does: narrow one token’s zone resources, or remove the duplicate provider from piqueld’s configuration, then restart the daemon. Don’t delete the zone from your DNS host: that can break the domain’s DNS. - A certificate fails with
was not visible on every authoritative nameserver. The TXT record didn’t reach every nameserver within 10 minutes. piqueld tries again later. - A private route below a public wildcard answers 404 on the internet. That’s expected: the public listener completes the connection with the wildcard’s certificate, but the wildcard doesn’t serve names that have their own route.
- Your hostname is refused with
hostname_conflict. Another application has a wildcard above it, or piqueld uses the hostname itself.