"I'm on a corporate network with no public DNS and no public tunnel — but I still want HTTPS, because browsers refuse to do half of what I need over plain
http://."
Caddy ships an internal Certificate Authority. Tell asd you don't
want a public tunnel, give the service a .localhost hostname, and
Caddy issues a cert signed by its own CA. Trust the CA once per
machine and the browser stops complaining.
Two choices that influence the URL the cert is valid for:
| Choice | URL shape | Notes |
|---|---|---|
hostRoute.host: my-app.localhost |
https://my-app.localhost/ |
Works because .localhost resolves to 127.0.0.1 by spec. Same machine only. |
hostRoute.host: my-app.your-domain.lan |
https://my-app.your-domain.lan/ |
Works on your LAN if you have local DNS (a /etc/hosts entry on each machine, or an internal DNS server). |
Either way, no public tunnel — tunnel.public: false.
# packages/my-app/net.manifest.yaml
id: my-app
name: My App
endpoint:
url: "http://127.0.0.1:3000"
caddy:
hostRoute:
host: "my-app.localhost"
tunnel:
public: false # local-only — no SSH tunnel daemon
$ asd net apply
✅ Apply complete: 1 seeded, 1 routes
Caddy issues a cert on the fly for my-app.localhost signed by its
own root CA. First request takes 200ms longer (cert issuance);
subsequent requests are fast.
# Linux (Ubuntu/Debian-style)
$ sudo cp ~/.local/share/caddy/pki/authorities/local/root.crt \
/usr/local/share/ca-certificates/caddy-local.crt
$ sudo update-ca-certificates
# macOS
$ sudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain \
~/Library/Application\ Support/Caddy/pki/authorities/local/root.crt
# Windows (PowerShell as admin)
$ Import-Certificate -FilePath "$env:LOCALAPPDATA\Caddy\pki\authorities\local\root.crt" `
-CertStoreLocation Cert:\LocalMachine\Root
Each browser also has its own trust store; the OS-level trust covers
Chrome/Edge on Linux/macOS, but Firefox needs the cert added in
about:preferences#privacy → Certificates → Authorities → Import.
$ curl https://my-app.localhost/
# returns your app, no -k flag needed once the root cert is trusted
# Without trust, you'd need -k (insecure) to bypass cert verification:
$ curl -k https://my-app.localhost/
The browser shows a green lock (or no warning at least) once the CA
is trusted.
.localhost is special. RFC 6761 reserves it: every resolver
is supposed to return 127.0.0.1 without hitting DNS. So
my-app.localhost, db.localhost, whatever.localhost all
resolve locally without configuration.
Caddy's internal CA mints certs on demand. When Caddy sees a
TLS request for my-app.localhost and the host isn't a public
domain, it falls back to its internal CA. The cert is short-lived
(default 12 hours) and rotated automatically.
tunnel.public: false skips the tunnel daemon. No SSH
connection, no tunnel server, no public URL. Caddy is still
fronting the request, just on localhost. Useful for: corporate
networks, air-gapped dev, services that should never be public,
databases / admin tools that don't belong on the internet.
OS trust ≠ browser trust ≠ tooling trust. OS keystore covers
Chrome / Edge / curl. Firefox has its own. Python / Node /
Ruby HTTP clients all have their own bundle quirks — most read
the OS bundle, some don't. If a specific tool still complains,
point it at the Caddy root cert via its CA_CERTS env var
equivalent.
tunnel.public: true and re-run asd net apply. Caddy keeps the local route and adds a public one.hostRoute.host to a domain you control on your LAN and add a hosts file / DNS entry on each device.reference/net-manifest — tunnel.public field.