A local HTTPS reverse proxy that manages certificate authorities, signed domain certificates, and hosts file entries. Use real HTTPS for local development without manual cert setup.
Install via npm (recommended, requires Node.js 18+):
npm install -g @itsbjoern/roost
roost initThis installs the roost CLI globally and downloads a prebuilt binary for your platform
from the GitHub Releases of this repository.
Download a release binary:
- Go to Releases and download the archive for your platform (Linux, macOS, or Windows).
- Extract the
roostbinary to a directory in yourPATH. - Run
roost initto complete setup.
Build from source (requires Rust):
git clone https://github.com/itsbjoern/roost.git && cd roost
cargo build --release
./target/release/roost initroost init # One-time setup (creates CA, installs to trust store)
roost domain add example.local # Add a domain (creates cert, updates /etc/hosts)
roost serve config add example.local 5001
roost serve # Start the proxyVisit https://example.local — it proxies to http://localhost:5001. Port 80 redirects to HTTPS.
Without root/sudo? Use a non-privileged port:
roost serve config ports add 8443
roost serve
# Visit https://example.local:8443You can add a domain and use its certs in your own dev server—no proxy needed. Roost creates the cert, updates hosts, and you get the paths:
roost domain add example.local
# Get paths for your server config:
roost domain path cert example.local # → ~/.roost/certs/example.local.pem
roost domain path key example.local # → ~/.roost/certs/example.local-key.pem
# Create the domain if it doesn't exist:
roost domain path cert example.local --generatePoint your local server at those paths. Using the JS API (recommended)—get the literal cert and key contents in one call:
// vite.config.ts
import { defineConfig } from 'vite';
import { getDomainCerts } from '@itsbjoern/roost';
export default defineConfig(async () => {
const { cert, key } = await getDomainCerts('example.local', { generate: true });
return {
server: {
https: { cert, key },
allowedHosts: ["example.local"],
// Force Vite to use HTTPS with HTTP/1.1 (not HTTP/2) so WebSocket upgrade works for HMR.
// When proxy is set, Vite uses https.createServer; otherwise it uses http2.createSecureServer
// which does not emit "upgrade", so wss:// connections fail.
proxy: {},
hmr: {
host: "example.local",
protocol: "wss",
},
};
});Or with Node's https.createServer:
const https = require('https');
const { getDomainCerts } = require('@itsbjoern/roost');
(async () => {
const { cert, key } = await getDomainCerts('example.local', { generate: true });
const server = https.createServer(
{ cert, key },
(req, res) => { /* ... */ }
);
server.listen(5173);
})();Your dev server then serves HTTPS directly at https://example.local:5173 using Roost's trusted certs. See JavaScript API for all helpers.
- CA management: Create and install a root CA into your system trust store so browsers accept local certs
- Domain management: Add domains (e.g.
api.example.local); Roost creates certs, updates/etc/hosts, and handles renewal - Reverse proxy: Terminates TLS and forwards
https://api.example.localtohttp://localhost:5001; explicit ports in the URL (e.g.https://api.example.local:5173) forward to that backend port - Daemon mode: Run the proxy in the background; reload config without restarting
Ports are configured in .roostrc and default to 80 and 443:
- Port 80 (when 443 is configured): Plain HTTP redirect to HTTPS
- Port 443 and others: TLS proxy to your backends
roost serve config ports add 5173 # Add Vite, etc.
roost serve config ports list # Show configured portsWhen you use an explicit port in the URL (e.g. https://example.local:5173), the proxy forwards directly to that backend port.
| Action | Required |
|---|---|
| CA install / uninstall | Admin (macOS: osascript; Linux: sudo) |
| Hosts file edit | Admin (same escalation) |
| Port 80 / 443 | Root or CAP_NET_BIND_SERVICE |
| Port 8443+ | None |
| Command | Description |
|---|---|
roost init |
One-time setup |
roost doctor |
Check configuration health (CA, hosts, certs, trust store) |
roost ca list |
List CAs (shows installed status) |
roost ca create <name> |
Create a new CA |
roost ca install [name] |
Install CA into system trust store |
roost ca uninstall [name] |
Remove CA from trust store |
roost domain add <domain> |
Add domain, create cert, update hosts. Use --exact for no wildcard; --allow to bypass TLD allowlist |
roost domain list |
List registered domains |
roost domain path cert <domain>, key <domain> |
Print path to cert or key file. Use --generate to create the domain if it doesn't exist |
roost serve |
Start proxy (foreground) |
roost serve config add <domain> <port> |
Map domain to port. Use --global to write to user config instead of project |
roost serve config remove <domain> |
Remove mapping. Use --global for user config |
roost serve config list |
List mappings (shows project or global source per mapping) |
roost serve config ports add/remove/set |
Manage listen ports. Use --global for user config |
roost serve daemon start |
Run proxy in background |
Run roost --help or roost <cmd> --help for full usage.
When you install @itsbjoern/roost as a dependency, you can call Roost from Node instead:
const { runRoost, getDomainPath, getDomainCertPath, getDomainKeyPath, getDomainPaths, getDomainCerts } = require('@itsbjoern/roost');
// or: import { getDomainCerts, getDomainPaths } from '@itsbjoern/roost';| Function | Description |
|---|---|
runRoost(args, options?) |
Run the roost binary with the given args. Returns Promise<{ stdout, stderr }>. |
getDomainCerts(domain, options?) |
Build-tool friendly. Resolve both cert and key contents (PEM strings). Returns Promise<{ cert: string, key: string }>. Pass directly to HTTPS config—no file reads. Use generate: true to create the domain if missing. |
getDomainPaths(domain, options?) |
Resolve both cert and key paths (filesystem paths). Returns Promise<{ cert: string, key: string }>. |
getDomainPath('cert' | 'key', domain, options?) |
Resolve the path to a domain's cert or key file. |
getDomainCertPath(domain, options?) |
Resolve the path to a domain's certificate file. |
getDomainKeyPath(domain, options?) |
Resolve the path to a domain's private key file. |
Path options (all optional):
generate: Iftrue, runsroost domain addwhen the domain doesn't exist, so you get valid paths without a separate setup step.exact: When usinggenerate, create a cert for the exact domain only (no wildcard).allow: When usinggenerate, allow any TLD (bypass the allowlist).
Domain→port mappings and listen ports live in .roostrc in one of two places:
- Project (default):
.roostrcin the current working directory. Best for per-project config you’ll commit to the repo. Use when adding mappings from within a project. - Global:
~/.roost/.roostrc(or%APPDATA%\roost\.roostrcon Windows). User-wide config that applies in any directory. Use for personal defaults or domains you use across many projects.
When you run roost serve, both configs are merged. For mappings, project overrides global when the same domain exists in both. For ports, the sets are combined. Use roost serve config list to see which file each mapping comes from. Add --global to config add, config remove, and ports add/remove/set to target the global file instead of the project.
- TLD allowlist: Only
.test,.local,.dev, etc. by default; use--allowto override - Wildcard certs:
domain add foo.localcoversfoo.localand*.foo.local; use--exactto disable - Config merge: Project and global
.roostrcmerge when you serve; see Global vs project config - Daemon:
roost serve daemon start|stop|status|reload; add/remove mappings triggers reload when running - Auto renewal: Certs expiring within 30 days are regenerated automatically
Data directory (~/.roost or %APPDATA%\roost on Windows):
~/.roost/
config.toml # domain -> CA mapping
ca/ # CAs (ca.pem, ca-key.pem per CA)
certs/ # Domain certs (domain.pem, domain-key.pem)
daemon.json # Daemon state when running
.roostrc (project or global) defines domain→port mappings and listen ports. Project: <cwd>/.roostrc. Global: ~/.roost/.roostrc.
[serve]
[[serve.mappings]]
domain = "api.example.local"
port = 5001
ports = [80, 443] # optional; defaults to [80, 443]Environment variables:
| Variable | Purpose |
|---|---|
ROOST_HOME |
Override data directory |
ROOST_SKIP_TRUST_INSTALL |
Skip trust store install in roost init (CI/testing) |
ROOST_HOSTS_FILE |
Override hosts file path (testing) |
./scripts/release.sh patch # 0.0.X – bug fixes
./scripts/release.sh minor # 0.X.0 – new features
./scripts/release.sh major # X.0.0 – breaking changesThe script bumps the version, commits, tags, and optionally pushes. Add -y to push without prompting. Pushing a tag triggers the release workflow to build binaries for Linux, macOS, and Windows.