A Caddy proxy manager - the self-hosted, multi-tenant alternative to Nginx Proxy Manager, built on Caddy.
Hostyt Proxy Gateway is a self-hosted control panel - a Caddy web UI - for a fleet of Caddy reverse-proxy nodes. Point your domains at backends through the web UI with automatic HTTPS, or run it as a hosting platform: customers get a VPS with a fixed backend IP and a fixed range of public ports and map their own domains to those ports. Scale it out and it becomes a multi-node Caddy fleet - a self-hosted CDN you operate yourself, with WireGuard tunnels to origin and per-node failover. The control plane configures every node over WireGuard, drives Let's Encrypt issuance, runs a WAF + GeoIP, and surfaces traffic stats.
Status: v1.4.8. Stack: Go 1.26.3, chi, MariaDB/MySQL or SQLite, Redis, Caddy 2.11. Single binary ~21 MB image, ~28 MB idle RAM.
- NPM-style single-box reverse proxy manager. Run one manager + one
Caddy node and use
/admin/hostsas a flat list of every domain: add a host (domain + backend IP:port), get automatic HTTPS via Caddy's on-demand TLS, and manage it from a dark/light web UI - a drop-in for anyone hand-editing an nginx or Caddy config, or currently on Nginx Proxy Manager. - Multi-tenant hosting-provider edge (reseller / multi-user). Give each
customer a
clientaccount scoped to a plan with domain/port/RPM quotas, or usenpm-kind plans where clients self-manage their own backend IP + port range from/app/services. Resellers own their own scoped clients, plans, and branding, with suspension fail-closed. Every admin action - including impersonation - is audit-logged with actor and IP, and the REST API v1 plus Terraform provider cover FOSSBilling/Hostyt-style automated provisioning. - Self-built multi-node CDN. Group Caddy nodes into
node_groupswithactive_active(deploy to every node with capacity) orfailover(primary + warm secondary) placement modes; the scheduler places new hosts across the group automatically. Customer/origin backends reach the fleet over a dedicated WireGuard tunnel that falls back to WSS (WebSocket-over-TLS) when UDP is blocked, so backends behind NAT or a restrictive firewall stay reachable. Certificate storage can be shared across a group (Redis-backed) so a failover or active_active peer already holds the cert before it needs to serve traffic. Node health is scraped from Caddy's Prometheus endpoint, and automatic failover moves routes off a dead node onto a healthy sibling in the same group.
- Caddy, not nginx - automatic HTTPS, HTTP/3, and a live JSON config API; no reload dance.
- Multi-node + multi-tenant - manage a fleet of proxy nodes over a WireGuard mesh, with clients, resellers, plans, and quotas. NPM is single-node, single-tenant.
- Built-in WAF (Coraza/OWASP CRS), GeoIP blocking, L4 stream proxying, access analytics, and an API + Terraform provider - not just a reverse-proxy form.
- One ~21 MB Go binary, ~28 MB idle RAM.
Searching for a caddy proxy manager or an nginx proxy manager alternative? That's this.
Fair comparison against the two closest open-source projects and the
no-panel baseline. ? marks a cell we're not confident enough about to
claim either way - check the project's own docs before relying on it.
| Hostyt Proxy Gateway | Nginx Proxy Manager | GoEdge | Plain Caddyfile | |
|---|---|---|---|---|
| Multi-node fleet | Yes - node groups, active_active/failover placement | No (single instance) | Yes - core use case (edge node cluster) | No (one instance = one config) |
| Multi-tenant / reseller | Yes - clients, plans, resellers, quotas | No¹ | Yes - user system for reselling bandwidth | No |
| WAF | Yes - Coraza/OWASP CRS, per-route toggle | No | Yes - built-in HTTP firewall | No² |
| Geo blocking | Yes - country/continent allow-deny, world map | No | Yes - region-based access rules | No² |
| L4 (TCP/UDP) proxying | Yes - SNI routing (caddy-l4) | Partial³ | ? | Manual² |
| Tunnels to origin | Yes - WireGuard, WSS fallback | No | ? | No |
| API | Yes - REST API v1 + Terraform provider | Partial⁴ | ? | Yes - native Caddy Admin API |
| Audit trail | Yes - every write logged with actor + IP | No | ? | No |
| License | MIT | MIT | Apache-2.0 | Apache-2.0 |
- Per-proxy-host user permissions exist; no client-facing portal or billing/reseller model.
- Achievable by hand-wiring community Caddy modules and JSON/Caddyfile config; nothing built-in.
- Raw TCP/UDP "Streams" passthrough; no SNI-based routing.
- Internal REST API backs its own UI; not designed or documented for third-party provisioning.
- NPM-style operator surface:
/admin/hostsis a flat list of every domain across every client. Add host in one form (domain + backend IP:port + node) - the implied client/plan/service rows are provisioned automatically under your admin account. - Self-bootstrap panel route: the install wizard pushes a virtual
Caddy route for the public APP URL → app container on the first
node, so you can sign in via
https://proxy.example.comwithout adding a host first. - Two plan kinds:
restricted(default) - admin pins backend IP + port range, client picks domain + port from the range. The hosting-provider model.npm- full self-service: the client may edit backend IP + port range from/app/services. Use for resellers / your own accounts.
- Auditable client impersonation: super-admin opens
/admin/users, clicks Impersonate, sees the client portal as them. Every action is audit-attributed to the admin withimpersonated_user_idin meta; the impersonation banner is visible on every page. Back to admin via/auth/end-impersonation. - Bulk actions on hosts: enable / disable / delete many rows from the flat list, with a single Caddy resync per affected node.
- Inline DNS pre-check on the Add-host form before submit.
- Per-host retry triggers a DNS re-check + Caddy re-push, which is the clean unblock when Let's Encrypt has been failing for a host whose DNS is already correct.
- One-command node join (Docker-Swarm style): operator generates a
token in the panel; new VPS runs one
curl | sudo bashline and is fully provisioned, including WireGuard mesh and Caddy. - WireGuard sidecar on the manager auto-applies peer add/remove via
wg syncconf- no manual interface restart on each join. - Caddy Admin API driven in JSON mode, source of truth is the DB.
- On-Demand TLS with an
/internal/askallowlist gate; ACME certificates issued automatically when a domain points at the right node. - 2FA: TOTP (QR enrollment + recovery codes), Email OTP, SMS OTP, WebAuthn/passkeys (discoverable login). OIDC (Authentik / Microsoft / generic), OAuth2 social login (GitHub, Google). Argon2id passwords, Redis-backed brute-force lockout (10 / 15 min), multi-provider CAPTCHA (Turnstile / hCaptcha / reCAPTCHA v3), AES-256-GCM encryption for all secrets at rest.
- REST API v1 with bearer-token auth for FOSSBilling / Hostyt-style provisioning integrations.
- Live admin stats: KPI cards, doughnut/line/bar charts, per-node traffic (Prometheus-scraped from Caddy).
- Dark / light mode UI with Inter font and a consistent design system.
- Dark-ops console UI: command palette (Cmd+K), collapsible nav groups, right-sheet drawer, teal design system.
- AI assistant: floating bubble, multi-provider (Anthropic / OpenAI / Gemini / OpenRouter), streaming SSE, scoped read-only tool-calling, per-user rate limit.
- WAF (Coraza): per-route toggle, rule suppression, event acknowledgement, real-time events from node-agent.
- GeoIP blocking: country allow/deny, continent blocking, CIDR always-block/allow, configurable response code, fail-closed mode, world traffic map.
- mTLS per route: per-tenant CA, client cert issue/revoke, path-based RBAC, enforcement audit log.
- L4 TCP/UDP streams: SNI routing, configurable log retention.
- Installation profiles: homelab / smallteam / advanced / provider modes with guided wizard.
- Instance sync: master/slave HPG config replication for HA setups.
- Multi-provider CAPTCHA: Turnstile, hCaptcha, reCAPTCHA v3 per-settings toggle.
- Custom fields: operator-defined fields for clients and hosts (JSON-backed).
- Advanced load balancing: uri_hash, header, cookie (with HMAC secret), query, client_ip_hash, random, random_choose, first (failover) in addition to round-robin/least-conn.
- Server-side search/pagination/sort with saved filter presets across all list pages.
- Host groups with filter and badge support.
- Hourly log rollups (14-day retention) + analytics charts on host logs page.
- Alert rules: high-error-rate detection and custom threshold alerts.
- Client self-registration behind settings toggle.
- Backup/restore: S3/SFTP/FTP targets, restore drill CLI.
- Reseller multi-tenancy: resellers own scoped clients/plans/branding, fail-closed on suspension.
- Terraform provider (
terraform-provider-hpg): manage nodes, pools, plans, clients, services, routes as code.
The feature list is wide; the exercise these features have had is not uniform. This table says what to trust with customer traffic today.
| Area | Status | What that means |
|---|---|---|
| HTTP/HTTPS reverse proxy, ACME certs | Stable | The core path. Covered by tests and by every install. |
| Multi-tenant clients, plans, quotas | Stable | Ownership and limits are enforced at a single choke point. |
| Admin panel, audit log, REST API | Stable | |
| WireGuard node mesh + customer tunnels | Beta | Works, but node join/rotation has had less field exposure than the proxy path. |
| Multi-node placement, node groups | Beta | Capacity claims are atomic; cross-replica coordination is still single-writer per process. |
| Automatic failover, DNS steering | Beta | Exercised in tests, not yet across a long-running fleet. |
| WAF, L4 streams, GeoIP, HTTP cache | Beta | Each needs a custom Caddy module on every node; flipping the gate before the fleet is upgraded takes nodes offline. |
| Manual TLS certs, mTLS + path RBAC | Beta | |
| NPM import, instance sync | Experimental | Import has a dry run that reports exactly what it would create and what needs manual work. Review it before committing. |
| AI assistant | Experimental | Optional; off by default. Not on any path that serves traffic. |
| Backup/restore | Beta | Restore is drilled by CLI; practice it on your own data before relying on it. |
Running several app replicas is supported for availability, but the config
push serializes per process, so treat the control plane as single-writer.
git clone https://github.com/host-yt/caddy-proxy-manager.git hostyt-proxy-gateway && cd hostyt-proxy-gateway
cp .env.example .env
$EDITOR .env # at minimum: APP_URL, APP_SECRET (openssl rand -hex 32), DB_PASSWORD
docker compose -f deploy/docker-compose.yml --env-file .env up -d
open http://localhost:8080 # walks through the install wizardAfter the wizard completes, sign in with the admin user you just created.
For multi-node deployments enable the WireGuard sidecar profile:
docker compose -f deploy/docker-compose.yml --env-file .env --profile mesh up -dThen in Settings → WireGuard, fill Public endpoint
(manager.example.com:51820), Save → keypair generated.
To add a remote Caddy node:
- Admin → Caddy nodes → Auto-join → Generate join command -
shows a one-time
curl … | sudo bash …line (TTL 30 min). - Paste it on the new VPS as root. It installs WireGuard, Docker, Caddy, joins the mesh, and registers itself.
Full guide: docs/MULTI_NODE.md.
Roughly 15 minutes on two cheap VPSes, and it exercises the parts that are specific to this project rather than to any proxy panel.
1. Add a customer. Admin → Clients → New client. Give it a plan (Admin → Plans) with a domain limit and a port range; that plan is what the customer is held to later, in the panel and over the API alike.
2. Join a second edge node. Admin → Caddy nodes → Auto-join → Generate join command, run the one-liner on the VPS as root. It joins the WireGuard mesh and registers itself - and then waits: a node is not eligible for placement until you approve it (Admin → Caddy nodes → Approve, after matching the fingerprint the installer printed). A stolen join token alone never starts carrying customer traffic.
3. Reach a private backend. The customer's app does not need a public IP. Admin → the node's Tunnel modal → Enable tunnel, then Clients → the customer → WireGuard peers → add a peer and run the printed installer on the backend host. The backend now has a tunnel address on that node and nothing exposed to the internet.
4. Publish a domain. Admin → Hosts → Add host: domain, the tunnel peer as the backend, port. Point the domain's DNS at the node. Caddy issues the certificate on demand; the host page shows DNS, certificate and live requests.
5. Take the node down. docker compose stop caddy on that VPS (or pull the
node's plug). Health probing marks it down; after the grace window automatic
failover moves its routes to a healthy peer in the same group and pushes the
config there, and the owning customer is notified.
6. Watch it come back. Start the node again. It is re-pushed from the database rather than trusted to have kept its own config, so it returns serving exactly what the panel says it should.
Two things worth noticing on the way: Admin → Audit log has every step with actor and IP, and the same six steps are available over the REST API and the Terraform provider - the panel is not the only entry point.
cmd/server/ entrypoint (thin)
internal/
accesslog/ access log ingest, rollups, country/ASN enrichment
adminscope/ non-super-admin client scope enforcement
aichat/ multi-provider AI client (Anthropic, OpenAI, Gemini, OpenRouter) + SSE streaming
aitools/ scoped read-only DB tool-calling for AI assistant
alert/ alert rule evaluation and notification dispatch
audit/ audit-log writer (actor, IP, impersonator, timestamp)
auth/ Argon2id, sessions, TOTP, Email/SMS OTP, WebAuthn, API keys, password reset
backup/ S3/SFTP/FTP backup targets and restore drill
caddyapi/ Caddy Admin API client + JSON config builder
captcha/ multi-provider CAPTCHA verifier (Turnstile / hCaptcha / reCAPTCHA v3)
chatstore/ AI conversation persistence
cloudflare/ Cloudflare API token + CF-Connecting-IP trust toggle
config/ env loader + validation
customfields/ operator-defined metadata fields for clients and hosts
deployment/ installation profiles (homelab / smallteam / advanced / provider)
dns/ pre-flight DNS resolver + per-route resolver controls
domain/ business logic per aggregate (routes, plans, services, nodes, …)
geoip/ MaxMind GeoLite2-Country DB download and distribution to nodes
httpserver/ chi router, handlers, middleware (CSRF, CSP, security headers, etc.)
installstate/ wizard state file + AES-256-GCM crypto helpers
instasync/ master/slave HPG config replication
jobs/ background scheduler (key rotation, rollup, geoip update, …)
mail/ SMTP send + email templates
metrics/ Caddy /metrics Prometheus scraper + delta aggregator
mtls/ per-tenant CA management, cert issue/revoke, RBAC verifier
nodejoin/ one-time join-token mint + redeem
oauth2x/ OAuth2 social login (GitHub, Google)
oidc/ coreos/go-oidc wrapper, DB-backed config
security/ scoped security checks shared across handlers
store/ DB pool + goose migration runner
systemevents/ operational event history storage
view/ html/template sets per audience (install, auth, admin, app)
wafevents/ WAF audit log ingest and event storage
webhook/ outbound webhook delivery and retry
wireguard/ Curve25519 keypair gen, config writer, IP allocator
cmd/node-agent/ Go agent on each Caddy node (WG sync, log forward, WAF events, GeoIP)
deploy/
docker-compose.yml manager stack (app + mariadb + redis + caddy + node-agent + optional wg)
caddy/ Caddy node image (xcaddy with cache-handler + L4 modules)
remote-node/ drop-in compose for an external Caddy node
wireguard/ WG sidecar image (alpine + wg-tools + watch loop)
migrations/ goose .sql files (auto-applied on boot)
scripts/ node-join.sh - bash bootstrap for remote nodes
docs/ all the docs you'll find linked below
| Install wizard | Admin dashboard |
|---|---|
![]() |
![]() |
| Host list | New host form |
|---|---|
![]() |
![]() |
| Stats | World map |
|---|---|
![]() |
![]() |
| AI assistant | Audit log |
|---|---|
![]() |
![]() |
Full install wizard walkthrough: docs/install_video/install_wizard.webm
Running more than one
appreplica? The upgrade to v1.4.4 must be a non-rolling cutover (drain old replicas → purge legacy Redis sessions → start new replicas): a pre-1.4.4 binary ignores restricted-admin session confinement and cannot be fenced. Upgrades after this one roll normally - replicas fence themselves by session generation: only a replica that can actually serve - bound listener included - advertises one, the newest generation serves, and older replicas stop serving (503, existing connections included) and shut down. Downgrades still need a full stop. See "Upgrading" indocs/DEPLOY.md.
| Doc | What's in it |
|---|---|
docs/INSTALL.md |
Step-by-step first-deploy |
docs/DEPLOY.md |
Production deploy + Portainer + reverse-proxy tips |
docs/MULTI_NODE.md |
WireGuard mesh + one-command node join |
docs/ARCHITECTURE.md |
How the pieces talk |
docs/API.md |
REST API v1 contract |
docs/SPEC.md |
Functional specification |
docs/SECURITY.md |
Threat model and security model |
docs/ROUTES.md |
Domain/alias ownership proof, custom Caddy JSON, per-route caching |
docs/WAF.md |
Web Application Firewall (Coraza) |
docs/GEOIP.md |
GeoIP country filtering |
docs/DNS_PROVIDERS.md |
DNS-01 challenge providers for wildcard TLS |
docs/CLOUDFLARE.md |
Cloudflare API token + real client IP behind the CF proxy |
docs/MTLS.md |
Mutual TLS / client certificate auth |
docs/MANUAL_CERTS.md |
Import + serve your own TLS certificates (no ACME) |
docs/TERRAFORM.md |
Terraform provider (hpg_node, hpg_client, hpg_service, ...) |
docs/ANALYTICS.md |
Access log analytics |
docs/ROADMAP.md |
Shipped features and planned work |
docs/FEATURE_MATRIX.md |
HPG vs alternatives comparison |
docs/TROUBLESHOOTING.md |
Common issues + fixes |
CHANGELOG.md |
Notable changes |
CONTRIBUTING.md |
How to develop / submit changes |
SECURITY.md |
Reporting vulnerabilities |
See LICENSE. For open-sourcing guidance see
docs/OPENSOURCING.md.







