- Rework the admin UI (topbar, sidebar, cards, tables, badges, stat tiles) into a dark theme with a light/dark toggle, matching the visual style requested for the project. - Fix Dockerfile: chown /data to the distroless nonroot uid/gid before the VOLUME declaration so a freshly created named volume is seeded with writable permissions instead of root:root (was causing "unable to open database file" on first run). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QTXFrNP1imCW7jtfL5SZrw
vibeproxy
A small, self-contained reverse proxy manager, written in Go. Think
"Nginx Proxy Manager, but simpler and with no external dependencies" —
vibeproxy handles HTTP/HTTPS reverse proxying, TCP/UDP streams, TLS
termination, and Let's Encrypt certificates itself, using Go's standard
networking stack (net/http, httputil.ReverseProxy, crypto/tls,
net). It never shells out to nginx, HAProxy, or Caddy.
The admin UI is server-rendered Go templates + Bootstrap 5 + a little
vanilla JavaScript — no React/Vue/Node build step. Everything (templates,
CSS, JS, SQL migrations) is embedded into a single binary via go:embed,
so deployment is just:
./vibeproxy serve
Features
- HTTP and HTTPS reverse proxying, routed by Host header / SNI
- Redirect hosts (301/302/307/308, with path & query preservation)
- TCP and UDP stream proxying, with optional PROXY protocol v1 to backends
- Multiple backends per proxy host: round robin, least-connections, or weighted load balancing, with TCP/HTTP health checks and configurable failure/recovery thresholds — unhealthy backends never receive traffic
- TLS termination, HTTP/2, WebSockets, keep-alive, streaming responses
- Let's Encrypt (HTTP-01 and DNS-01, via the lego ACME client library) plus custom/uploaded certificates, with automatic renewal
- Reusable Access Lists (CIDR allow/deny + optional HTTP Basic Auth)
- Per-host custom request/response headers
- Configurable trusted proxy networks —
X-Forwarded-Foris only trusted from networks you explicitly configure - SQLite storage (WAL mode, foreign keys, migrations) with the active configuration cached in memory — the hot request path never touches the database, and configuration changes apply live without a restart
- REST API under
/api/v1, authenticated with revocable API tokens - Single administrator account (HTTP Basic Auth, Argon2id password hashing, CSRF-protected forms)
- Request logs (with configurable retention) and an audit log of every configuration change
- Prometheus metrics,
/healthz,/readyz, graceful shutdown, automatic and on-demand SQLite backups
Quick start
go build -o vibeproxy ./cmd/vibeproxy
sudo setcap 'cap_net_bind_service=+ep' ./vibeproxy # to bind :80/:443 without root
./vibeproxy serve
On first run, vibeproxy creates the admin account and prints a random
temporary password to stdout — save it, it's not shown again. Change it
under Settings → Administrator Account after logging in.
By default:
:80and:443— proxy traffic (HTTP/HTTPS reverse proxy + redirect hosts + Let's Encrypt HTTP-01 challenges):8081— admin web UI, REST API,/healthz,/readyz,/metrics. Put this behind a firewall or VPN — Basic Auth protects it, but it shouldn't be exposed to the internet.
All of this is configurable via flags or environment variables:
| Flag | Env var | Default | Purpose |
|---|---|---|---|
--data-dir |
VIBEPROXY_DATA_DIR |
./data |
database, certs, backups |
--db-path |
VIBEPROXY_DB_PATH |
./data/vibeproxy.db |
SQLite file |
--http-addr |
VIBEPROXY_HTTP_ADDR |
:80 |
HTTP proxy listener |
--https-addr |
VIBEPROXY_HTTPS_ADDR |
:443 |
HTTPS proxy listener |
--admin-addr |
VIBEPROXY_ADMIN_ADDR |
:8081 |
admin UI + API |
Everything else (ACME email, renewal window, log retention, trusted proxy CIDRs, backup schedule, ...) is runtime configuration stored in SQLite and editable from Settings, so it takes effect without a restart.
CLI
vibeproxy serve # run the proxy, admin UI, and API
vibeproxy version # print the version
vibeproxy config check # validate config + run migrations, then exit
vibeproxy admin reset-password # generate a new admin password
vibeproxy database backup # write an on-demand SQLite backup
vibeproxy database migrate # apply pending migrations
vibeproxy certificates renew # renew any due Let's Encrypt certs
systemd
sudo useradd --system --home /var/lib/vibeproxy --shell /usr/sbin/nologin vibeproxy
sudo cp vibeproxy /usr/local/bin/vibeproxy
sudo cp systemd/vibeproxy.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now vibeproxy
sudo journalctl -u vibeproxy -f # first run prints the admin password here
The unit grants CAP_NET_BIND_SERVICE so vibeproxy can bind :80/:443
without running as root, and sandboxes the process with ProtectSystem,
ProtectHome, PrivateDevices, etc. It stores everything under
/var/lib/vibeproxy (mode 0700).
Docker
docker compose up -d
See docker-compose.yml / Dockerfile. The container runs as a
non-root user, so it listens on 8080/8443 internally — docker-compose.yml
maps host 80/443 to those.
REST API
All endpoints are under /api/v1 on the admin listener, authenticated
with a bearer token (create one from API in the UI — it's shown once):
curl -H "Authorization: Bearer vp_xxxxxxxx" \
http://localhost:8081/api/v1/proxy-hosts
Resources: proxy-hosts, streams, certificates, access-lists,
settings, logs/requests, logs/audit, stats, tokens. See
internal/api for the full route list.
Project layout
cmd/vibeproxy/ CLI entry point (cobra commands)
internal/
api/ REST API (/api/v1)
app/ wires every subsystem together for `serve`
auth/ password hashing, Basic Auth, API tokens, CSRF
access/ CIDR access lists + Basic Auth evaluation
backup/ SQLite backup/restore
certificates/ ACME (via lego) + custom cert storage & renewal
config/ process bootstrap flags/env
database/ SQLite connection, migrations, repositories
health/ backend health checking
loadbalancer/ backend pools: round robin / least-conn / weighted
logging/ buffered request logging + audit log + retention
metrics/ Prometheus collectors
models/ domain types shared across packages
proxy/ the HTTP/HTTPS reverse proxy engine
runtime/ in-memory active-configuration snapshot
stats/ dashboard/API statistics
streams/ TCP/UDP proxying
web/ server-rendered admin UI (Go templates + embedded assets)
templates/, static/
systemd/ vibeproxy.service
Design notes
- Hot path stays out of SQLite.
internal/runtimeholds an immutable, atomically-swapped snapshot of proxy hosts, backends, access lists, and settings. Every proxied request/connection reads that snapshot; database writes only happen when an admin changes configuration, at which pointStore.Reloadrebuilds the snapshot. X-Forwarded-Foris not trusted by default. The real client IP is taken from the TCP peer address unless the peer is inside a configured "trusted proxy" CIDR (Settings → Trusted Proxy Networks), in which case the right-most untrusted entry inX-Forwarded-Foris used instead.- Certificates never touch logs. Private keys are written to disk
with
0600permissions (directories0700) and are never logged or returned by the API. - Reloads don't drop connections. Stream (TCP/UDP) reconciliation diffs the desired vs. running listener set and only restarts streams whose configuration actually changed; unaffected streams, and all in-flight HTTP requests on the previous configuration snapshot, are left alone.
Testing
go test ./...
Covers load-balancing strategies and health-check thresholds, access-list CIDR evaluation, password/API-token hashing, SQLite migrations and proxy-host CRUD round-trips, an end-to-end TCP stream proxy test, and a render-smoke-test for every admin UI page.