Access-list item passwords were stored and returned in plaintext. Hash on write with bcrypt, compare hashes in the proxy engine (with legacy plaintext fallback), and omit passwords from admin GET JSON.
Helix Proxy
A pure-Go reverse proxy with embedded web UI. Supports proxy hosts, TCP/UDP streams, redirections, dead hosts, certificates (Let's Encrypt + custom), access lists, audit, and more. Single static binary. Single built-in admin (no multi-user registration). Production requires ADMIN_PASSWORD on first boot (the well-known default "password" is rejected); development may leave it unset until you change it (bootstrap lock).
Key features / differences from traditional setups:
- Entirely replaces nginx: pure Go reverse proxy (http) + TCP/UDP stream proxy engine. No nginx binary, no config files on disk for routing, live updates.
- Single binary: the modern Svelte SPA UI is built and embedded (
//go:embed) into the Go executable. - Storage: default is a single
data/db.bolt(cwd-relative, bbolt embedded). - No magic paths: everything defaults to paths relative to the process CWD (
data/db.bolt,data/www/,data/certs/, ...). All overridable with env vars (DATA_DIR,WWW_DIR, etc.). - www/html lives in data/ by default (single volume tree).
Quick start (binary)
make # builds UI (placeholder) + Go binary with embed
ADMIN_PASSWORD='choose-a-real-password' ./helix-proxy
# Defaults (overridable via env):
# Admin UI + API: 127.0.0.1:8081 (ADMIN_HOST / ADMIN_PORT)
# Proxy HTTP: :8080 (PROXY_HTTP_PORT)
# Proxy HTTPS: :18443 (PROXY_HTTPS_PORT)
Visit http://127.0.0.1:8081
Data (db, certs, logs, www html) lives in ./data relative to where you ran the binary.
Docker (recommended)
docker compose up -d
# compose sets ADMIN_PORT=81 PROXY_HTTP_PORT=80 PROXY_HTTPS_PORT=443
# proxy :80/:443 published; admin binds 127.0.0.1:81 (not published by default)
# or
docker build -t helix-proxy:dev .
docker run --env ADMIN_PASSWORD='choose-a-real-password' \
-p 80:80 -p 443:443 \
-e ADMIN_PORT=81 -e PROXY_HTTP_PORT=80 -e PROXY_HTTPS_PORT=443 \
-v $PWD/data:/app/data --workdir /app helix-proxy:dev
Publishing admin (-p 81:81) also needs -e ADMIN_HOST=0.0.0.0. Do not publish :81 on untrusted networks.
Without those env overrides the binary still defaults to admin 127.0.0.1:8081 and proxy :8080 / :18443 inside the container.
PUID/PGID + DISABLE_IPV6 example (see docker-compose.yml for full):
# user: "0:0" # root to allow chown+drop inside
environment:
- PUID=1000
- PGID=1000
- DISABLE_IPV6=1
# PUID_NO_DROP=1 # for low ports (80/443) + PUID: do chown as root but do not drop (stay root to bind low ports; default with PUID drops to non-root after chown, so use high ports or NET_BIND_SERVICE cap or run without PUID for low ports)
Binary auto-chowns data tree (if started root) then drops privs (unless PUID_NO_DROP); umask support via UMASK env. Files 0600, dirs 0755. Note: privilege drop happens early (before listeners); low-port binds require either root (with PUID_NO_DROP), capabilities, high ports in config, or external setuid wrapper.
See docker-compose.yml for a full example (ADMIN_PORT=81 / proxy 80/443 via env; publishes 80/443; admin stays on loopback unless you set ADMIN_HOST / publish the admin port).
Paths (all overridable)
data/db.bolt(orDATA_DIR)data/certs/,data/logs/,data/letsencrypt-acme-challenge/data/www/(default site / custom html;WWW_DIRorHTML_DIR)data/.jwt_secret(auto-generated admin API signing key ifJWT_SECRETis unset)- etc.
Status
Core features implemented and verified:
- cwd-relative + env-overridable paths/storage (single
data/db.boltprimary via bbolt) - pure-Go engine: proxy hosts (full: locations, advanced_config parser, ssl_forced, block_exploits, websocket, hsts, caching w/ HIT/MISS, access lists, custom certs, LE), streams (tcp/udp +ssl term), redirection hosts (full forward_http_code/preservePath/scheme + CRUD), dead hosts (per-dead custom content + CRUD)
- certificates: custom PEM (meta keys compat) + full Let's Encrypt issuance/renewal via lego (http-01). In PROXY_MODE=development all LE certs are self-signed test certs (domain-based sim tricks removed).
- single admin (
ADMIN_PASSWORDrequired in production; default"password"refused; hashed + stored in DB; no registration or multi-user) - audit (userId=1), settings (default_site + letsencrypt_email etc)
- live reload on all CRUD, dual https/http + SNI, embedded Svelte SPA (full tabs, pickers, edits)
- single binary (go build embeds UI after make ui-build), docker multi-stage + full PUID/PGID/umask
- no nginx, no .conf files, no external processes for proxying
LE note: for real certs use a public DNS domain pointing at your server (port 80/http reachable). By default (production), real LE http-01 is used. Set PROXY_MODE=development and all letsencrypt cert requests will produce self-signed test certs instead (for dev/testing). Real LE will be used otherwise (requires valid email, port 80 reachable etc).
Development
make ui-build(once real Svelte UI added to ui/)makemake docker
Contributions / v2 features: open an issue or just ask to implement the next piece (advanced config, full certs with lego, streams, full Svelte UI, SQL backend, etc.).