vite-plugin-caddy-multiple-tls
v1.7.1
Published
Vite plugin that uses Caddy to provide local HTTPS with derived domains.
Maintainers
Readme
vite-plugin-caddy-multiple-tls
What it does
Runs Caddy alongside Vite to give you HTTPS locally with automatic, per-branch domains like <repo>.<branch>.localhost, so you can use real hostnames, cookies, and secure APIs without manual proxy setup.
Usage
// vite.config.js
import { defineConfig } from 'vite';
import caddyTls from 'vite-plugin-caddy-multiple-tls';
const config = defineConfig({
plugins: [
caddyTls(),
]
});
export default config;Will give this in the terminal, allow you to connect to your app on HTTPS with a self-signed and trusted cert.
The plugin defaults server.host = true and server.allowedHosts = true (plus preview equivalents) so custom hostnames work without extra config. When a domain is resolved, it also defaults server.hmr to use wss on port 443 and the resolved host, isolating multiple Vite instances without extra config. Override these in your Vite config if you need different values.
> vite
🔒 Caddy is proxying your traffic on https
🔗 Access your local server
🌍 https://my-repo.my-branch.localhost
By default, the plugin derives <repo>.<branch>.localhost from git.
If repo or branch can't be detected, pass repo/branch or use domain.
If you want a fixed host without repo/branch in the URL, pass a single domain:
// vite.config.js
import { defineConfig } from 'vite';
import caddyTls from 'vite-plugin-caddy-multiple-tls';
const config = defineConfig({
plugins: [
caddyTls({
domain: 'app.localhost',
})
]
});
export default config;You can also pass multiple explicit domains:
// vite.config.js
import { defineConfig } from 'vite';
import caddyTls from 'vite-plugin-caddy-multiple-tls';
const config = defineConfig({
plugins: [
caddyTls({
domain: ['app.localhost', 'api.localhost'],
})
]
});
export default config;To derive a domain like <repo>.<branch>.<baseDomain> automatically from git (repo name first, then branch):
// vite.config.js
import { defineConfig } from 'vite';
import caddyTls from 'vite-plugin-caddy-multiple-tls';
const config = defineConfig({
plugins: [
caddyTls({
baseDomain: 'local.conekto.eu',
})
]
});
export default config;You can override auto-detection with repo or branch if needed.
If you run different projects that derive the same <repo>.<branch> host, add instanceLabel to keep domains unique:
// vite.config.js
import { defineConfig } from 'vite';
import caddyTls from 'vite-plugin-caddy-multiple-tls';
const config = defineConfig({
plugins: [
caddyTls({
instanceLabel: 'web-1',
})
]
});
export default config;This derives a host like <repo>.<branch>.web-1.localhost.
The plugin now treats hostname ownership as explicit. If another live Vite server already owns the resolved domain, it will refuse takeover instead of deleting the other server's route. Use instanceLabel, domain, or stop the other server first.
If a previous Vite process crashed and left stale ownership behind, the plugin will reclaim it automatically and clean up the stale Caddy route before continuing.
For a zero-config experience, use baseDomain: 'localhost' (the default) so the derived domain works without editing /etc/hosts.
internalTls defaults to true when you pass baseDomain or domain. You can override it if needed.
For non-.localhost domains (like local.example.test), keep internalTls: true to force Caddy to use its internal CA for certificates.
If your Caddy Admin API is not on the default http://localhost:2019, set caddyApiUrl. Empty or whitespace values fall back to the default.
// vite.config.js
import { defineConfig } from 'vite';
import caddyTls from 'vite-plugin-caddy-multiple-tls';
const config = defineConfig({
plugins: [
caddyTls({
caddyApiUrl: 'http://localhost:2020',
})
]
});
export default config;If your Caddy Admin API enforces a specific allowed origin that differs from caddyApiUrl, set caddyAdminOrigin.
// vite.config.js
import { defineConfig } from 'vite';
import caddyTls from 'vite-plugin-caddy-multiple-tls';
const config = defineConfig({
plugins: [
caddyTls({
caddyApiUrl: 'http://127.0.0.1:2019',
caddyAdminOrigin: 'http://localhost:2019',
})
]
});
export default config;Troubleshooting
Cannot claim ... another Vite server already owns this domain
This means another live dev server is already using the resolved hostname.
- Stop the other server if you want this one to use the same host.
- Add
instanceLabelif both servers should run at the same time. - Pass an explicit
domainif you want total control over the hostname.
client is not allowed to access from origin ''
This error comes from Caddy Admin API origin enforcement, not from Caddy being down.
- Check
caddyApiUrlpoints to the correct Admin API endpoint. - If Admin API expects a different origin than the API URL host, set
caddyAdminOrigin. - Verify behavior directly:
curl -i http://127.0.0.1:2019/config/
curl -i -H 'Origin: http://127.0.0.1:2019' http://127.0.0.1:2019/config/[!IMPORTANT]
Hosts file limitation: If you use a custom domain, you must manually add each generated subdomain to your/etc/hostsfile (e.g.,127.0.0.1 repo.branch.local.example.test). System hosts files do not support wildcards (e.g.,*.local.example.test), so you lose the benefit of automatic domain resolution thatlocalhostprovides.
Recommended base domain: .localhost
Why localhost is the best option for local development:
- Reserved by RFC 6761 (never on the public internet).
- Automatic resolution on macOS:
*.localhostmaps to127.0.0.1and::1without DNS or/etc/hosts. - Subdomain support:
api.localhost,foo.bar.localhost, etc. - Secure context in browsers for HTTPS, service workers, and cookies.
- Works well with Caddy and other local reverse proxies.
Example usage:
app.localhost
api.app.localhost[!NOTE] Linux users: Unlike macOS, most Linux distributions don't automatically resolve
*.localhostsubdomains. The plugin will detect Linux and show you the exact command to run:🐧 Linux users: if the domain doesn't resolve, run: echo "127.0.0.1 my-repo.my-branch.localhost" | sudo tee -a /etc/hostsIf you want to avoid
/etc/hostsedits on Linux, setloopbackDomainto a public loopback domain:caddyTls({ loopbackDomain: 'localtest.me', })Supported values:
localtest.me,lvh.me,nip.io(maps to127.0.0.1.nip.io). These rely on public DNS, so they can fail offline or on restricted networks.Why these work: they use wildcard DNS so any subdomain resolves to
127.0.0.1, meaning the request loops back to your machine after DNS.
localtest.meandlvh.me: static wildcard -> always127.0.0.1(great for subdomain testing).nip.io: dynamic parsing of the IP in the hostname (e.g.app.192.168.1.50.nip.io) so you can target LAN devices. Why use them: subdomains behave like real domains, no/etc/hostsedits, and closer parity for cookies/CORS rules.When using loopback domains, ensure your Vite config allows the Host header and binds to all interfaces, e.g.
server: { allowedHosts: true, host: true }.For a permanent fix that handles all
*.localhostdomains automatically, install dnsmasq:sudo apt install dnsmasq echo "address=/.localhost/127.0.0.1" | sudo tee /etc/dnsmasq.d/localhost.conf sudo systemctl restart dnsmasq
Development
This repo uses npm workspaces. Install from the root with npm install, then run workspace scripts like npm run build --workspace packages/plugin or npm run dev --workspace playground.
The published package README is synced from the root README.md via packages/plugin/scripts/sync-readme.sh.
Contributing
See CONTRIBUTING.md to see how to get started.
License
MIT
