When you add a custom domain in linq, you want HTTPS working immediately — not after editing a reverse-proxy config, restarting a service, and waiting for certificate issuance. linq’s Caddy integration automates that entire flow. Set two environment variables pointing linq at Caddy’s admin API, and every domain create, archive, reactivation, and purge is pushed to Caddy as its own route. Caddy’s automatic HTTPS issues and renews the certificate. You never touch a Caddy config by hand.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/org-quicko/linq/llms.txt
Use this file to discover all available pages before exploring further.
How it works
Configure the two environment variables
Add these to your repo-root
.env:LINQ_CADDY_ADMIN_URL tells linq where to find Caddy’s admin API. LINQ_CADDY_UPSTREAM tells Caddy where to forward matched traffic — always linq:3000 inside the Compose network.Setting LINQ_CADDY_ADMIN_URL is what activates the integration. Without it, every domain operation completes successfully but nothing is pushed to Caddy. Unset it to disable Caddy sync entirely.Start a Caddy-enabled Compose stack
Pick one of the Compose files that includes Caddy (stacks 03, 04, 07, or 08):Caddy boots from an empty-skeleton config at
docker/examples/dockerfiles/caddy/caddy.json. linq reconciles all active domain routes against Caddy during its own startup.Route synchronisation details
linq manages each domain as an individually addressable route in Caddy, identified by a stable ID (domain:<uuid>). The sync protocol is:
- Create or reactivate:
DELETE /id/domain:<id>(tolerating a 404 — that is the idempotent case) followed by aPOSTof the new route tagged with the same@id. - Archive, delete, or purge: a single
DELETE /id/domain:<id>.
error level but never fails the domain API request. The next successful mutation on that domain — or the next linq boot — repairs the missed sync. This is intentional: Caddy depends on linq being reachable, not the other way around. Refusing to boot linq because its own reverse proxy is momentarily down would invert that dependency.
If
LINQ_CADDY_ADMIN_URL is set but Caddy is unreachable when linq boots, linq logs the reconciliation failure and continues. It does not refuse to start — unlike a misconfigured Redis URL, which is fatal at boot. See docs/adr/0012 for the full reasoning.DNS requirement
Caddy’s automatic HTTPS uses ACME (Let’s Encrypt or ZeroSSL) to issue certificates. ACME HTTP-01 validation requires:- The domain’s DNS A/AAAA record pointing at the host running Caddy
- Inbound TCP on port 80 (for the HTTP challenge) and port 443 (for HTTPS)
- Both ports reachable from the public internet at the time of challenge
Compose files with Caddy
Four Compose stacks include acaddy service:
| # | File | Postgres | Redis |
|---|---|---|---|
| 3 | 03-bundled-postgres-caddy.yml | bundled | – |
| 4 | 04-bundled-postgres-full.yml | bundled | yes |
| 7 | 07-external-postgres-caddy.yml | external | – |
| 8 | 08-external-postgres-full.yml | external | yes |
linq:3000 over the internal Compose network and is the only public entry point. This ensures API keys never travel through a plaintext bypass.
Caddy’s admin port (:2019) is also never published to the host. Only linq ever talks to it, over the Compose network. There is no way to reach the admin API from outside the container network.
LINQ_APP_HOST with Caddy
WhenLINQ_APP_HOST is set, linq pushes a route for the app host at boot alongside the domain routes. This gives the app host its certificate through exactly the same mechanism as custom domains — no extra Caddy configuration needed.
domain:app-host in Caddy. It is written only at boot; changing LINQ_APP_HOST requires a restart, which a config change needs anyway. The app host’s DNS must also point at the host for certificate issuance to succeed.
Behind Caddy, add the server in the linq UI as
https://linq.example.com — the page and the API share an origin, so there are no cross-origin headers to worry about.Local testing with Caddy
When testing locally with a Caddy stack, the domainlocalhost will receive a certificate from Caddy’s local CA rather than a public CA. To smoke-test the setup without trusting the local CA:
-k flag, import Caddy’s local CA root into your system trust store. Caddy places it at /data/caddy/pki/authorities/local/root.crt inside the Caddy container.
Troubleshooting
Caddy is not issuing certificates for a new domain
Caddy is not issuing certificates for a new domain
Check that:
- The domain’s DNS A/AAAA record resolves to your host’s public IP.
- Ports 80 and 443 are open inbound on the host’s firewall.
- No other service is binding to port 80 or 443 on the host.
- The domain was successfully created in linq — check the API response or the Domains page.
- Caddy’s own logs show the ACME attempt:
docker compose logs caddy.
Caddy restarted and short links stopped working
Caddy restarted and short links stopped working
A Caddy restart wipes its in-memory route config. linq reconciles all routes at its own boot, so restarting linq (not just Caddy) will re-push every active domain. Alternatively, force a reconciliation by restarting the
linq container:sync failure logged but domain was created
sync failure logged but domain was created
A sync failure is non-fatal — the domain row exists in Postgres and the API responded successfully. To push the route to Caddy without waiting for the next mutation, restart linq (which triggers
reconcileCaddy at boot). Check LINQ_CADDY_ADMIN_URL is reachable from the linq container and that both containers are on the same Compose network.