A modern web interface for managing your Caddy server. Built from scratch in a single conversation with Claude.
caddy/ui is a self-hosted management interface for Caddy. It runs as two Docker containers alongside your existing Caddy instance and communicates with Caddy's built-in admin API. Your Caddyfile remains the source of truth — the UI reads from it, writes to it, and never takes ownership away from you.
- Dashboard — Live server status, TLS state, server block summary with custom display names, upstream health overview, and Caddy process info (version, uptime, memory, last reload)
- Caddyfile Editor — Edit your Caddyfile with syntax highlighting, live validation,
caddy fmtformatting, automatic site block sorting, backup/restore, and full version history with inline preview and one-click rollback - Route Manager — View all reverse proxy routes across all server blocks, with live upstream healthchecks, uptime percentages, search/filter by domain, upstream, note, or server, clickable domain and upstream links, edit routes in-place, and per-route notes
- TLS Certificates — View cert status, expiry dates, and sortable columns for all managed domains. Detect and delete orphaned certs. Download Caddy's root CA cert with per-OS install instructions
- Access Logs — Tail live log output with SSE streaming, real-time keyword search, ERROR/WARN/INFO level filters, and log export
- Log Configuration — Enable, disable, and configure Caddy access logging directly from the UI
- Metrics — Request count, RPS, avg response time, status code breakdown, and p50/p95/p99 percentiles powered by Caddy's built-in Prometheus endpoint
- Dark/Light Theme — Toggle between dark and warm off-white themes, persisted across sessions
- URL-Based Navigation — Full browser history support, bookmarkable URLs, and deep links (e.g.
/routes?filter=srv0) - Authentication — Optional JWT-based login screen protecting the UI and all API endpoints
- Mobile Friendly — Responsive layout with collapsible sidebar
graph LR
FE["caddy/ui frontend\nReact + Nginx\n:9877"]
BE["caddy/ui backend\nNode.js + Express\n:9876"]
CA["Caddy\n:2019 admin API\n:80 / :443"]
CF[("Caddyfile\n/etc/caddy/Caddyfile")]
LG[("Access Logs\n/var/log/caddy")]
SN[("Server Names\n/etc/caddy-ui")]
TLS[("Certificates\n/data/caddy/caddy")]
HX[("History\n/etc/caddy-ui/history")]
RN[("Route Notes\n/etc/caddy-ui")]
FE -->|"/api/* proxy"| BE
BE -->|"admin API"| CA
BE -->|"TCP healthcheck + uptime"| CA
BE -->|"Prometheus metrics"| CA
BE -->|"/adapt validation"| CA
BE <-->|"read / write / backup"| CF
BE <-->|"read / stream / export"| LG
BE <-->|"read / write"| SN
BE <-->|"read / delete / CA download"| TLS
BE <-->|"snapshot / restore"| HX
BE <-->|"read / write"| RN
CA <-->|"reload from"| CF
- Docker and Docker Compose
- An existing Caddy container with the admin API enabled on
0.0.0.0:2019
Add the following to your Caddyfile global block:
{
admin 0.0.0.0:2019
email {$EMAIL}
}Note: the
CADDY_ADMINenvironment variable does not configure this — it must be set in the Caddyfile.
mkdir -p /docker/caddy/logs
mkdir -p /docker/caddy-uiopenssl rand -base64 32services:
caddy:
image: caddy:latest
container_name: caddy
restart: unless-stopped
ports:
- 80:80
- 443:443
environment:
- CADDY_LOG_PATH=/var/log/caddy/access.log
- DOMAIN=example.com
- EMAIL=you@example.com
- TZ=America/New_York
volumes:
- /docker/caddy/Caddyfile:/etc/caddy/Caddyfile
- /docker/caddy/data:/data
- /docker/caddy/config:/config
- /docker/caddy/logs:/var/log/caddy
networks:
- caddy-ui
caddy-ui-backend:
image: zackwag/caddy-ui-backend:latest
container_name: caddy-ui-backend
restart: unless-stopped
ports:
- 9876:3001
environment:
- TZ=America/New_York
# Optional -- leave unset to disable auth
- CADDY_UI_USER=admin
- CADDY_UI_PASSWORD=yourpassword
- JWT_SECRET=your-long-random-secret
volumes:
- /docker/caddy/Caddyfile:/etc/caddy/Caddyfile
- /docker/caddy/logs:/var/log/caddy
- /docker/caddy-ui:/etc/caddy-ui
- /docker/caddy/data:/data/caddy
networks:
- caddy-ui
depends_on:
- caddy
caddy-ui-frontend:
image: zackwag/caddy-ui-frontend:latest
container_name: caddy-ui-frontend
restart: unless-stopped
ports:
- 9877:80
networks:
- caddy-ui
depends_on:
- caddy-ui-backend
networks:
caddy-ui:
driver: bridgedocker compose up -dOpen http://your-server:9877 in your browser.
All backend variables have sensible defaults. Only set what you need to override.
| Variable | Default | Description |
|---|---|---|
CADDY_ADMIN_URL |
http://caddy:2019 |
URL of Caddy's admin API |
CADDY_CONFIG_PATH |
/etc/caddy/Caddyfile |
Path to the Caddyfile inside the container |
CADDY_CONTAINER_NAME |
caddy |
Name of the Caddy container for Docker exec |
CADDY_DATA_PATH |
/data/caddy/caddy |
Path to Caddy's data directory |
CADDY_LOG_PATH |
/var/log/caddy/access.log |
Path to Caddy's access log |
CADDY_SERVER_NAME |
srv0 |
Primary server block name for new routes |
CADDY_UI_PASSWORD |
— | Password for UI authentication |
CADDY_UI_PUBLIC_METRICS |
false |
Expose /api/metrics/raw without auth |
CADDY_UI_USER |
— | Username for UI authentication (leave unset to disable) |
HISTORY_PATH |
/etc/caddy-ui/history |
Path to the Caddyfile snapshot directory |
JWT_SECRET |
— | Secret key for signing JWT tokens |
PORT |
3001 |
Port the backend listens on |
ROUTE_NOTES_PATH |
/etc/caddy-ui/route-notes.json |
Path to the route notes file |
SERVER_NAMES_PATH |
/etc/caddy-ui/server-names.json |
Path to the server display names file |
Authentication is disabled by default. Set CADDY_UI_USER, CADDY_UI_PASSWORD, and JWT_SECRET to enable it. All API endpoints are protected and the login screen appears automatically.
Caddy supports {$VAR_NAME} syntax in the Caddyfile. Set the vars in your Caddy container's environment and they will be substituted at reload time:
{
admin 0.0.0.0:2019
email {$EMAIL}
}
blog.{$DOMAIN} {
reverse_proxy 192.168.4.88:8250
}Enable Caddy's metrics endpoint from the Metrics tab, or add metrics to your Caddyfile global block manually. Set CADDY_UI_PUBLIC_METRICS=true to expose /api/metrics/raw without auth for Prometheus scraping.
scrape_configs:
- job_name: caddy
static_configs:
- targets: ['caddy-ui-backend:3001']
metrics_path: /api/metrics/rawThe status endpoint returns enriched data for use with Homepage:
- Caddy:
href: https://caddy.home
icon: caddy.png
widget:
type: customapi
url: http://caddy-ui-backend:3001/api/status
mappings:
- field: online
label: Status
format: text
remap:
- value: true
to: Online
- value: false
to: Offline
- field: routeCount
label: Routes
format: number
- field: upstreamsOnline
label: Upstreams
format: number
- field: uptime
label: Uptime
format: textcd backend && docker build -t caddy-ui-backend .
cd frontend && docker build -t caddy-ui-frontend .caddy-ui/
├── backend/
│ ├── src/
│ │ ├── index.js
│ │ ├── caddy.js
│ │ ├── middleware/
│ │ │ └── auth.js
│ │ └── routes/
│ │ ├── auth.js
│ │ ├── caddyfile.js
│ │ ├── health.js
│ │ ├── logs.js
│ │ ├── metrics.js
│ │ ├── routenotes.js
│ │ ├── routes.js
│ │ ├── servernames.js
│ │ ├── status.js
│ │ └── tls.js
│ ├── Dockerfile
│ └── package.json
├── frontend/
│ ├── src/
│ │ ├── components/
│ │ │ ├── CaddyFile.jsx
│ │ │ ├── Dashboard.jsx
│ │ │ ├── Login.jsx
│ │ │ ├── Logs.jsx
│ │ │ ├── Metrics.jsx
│ │ │ ├── Routes.jsx
│ │ │ ├── Sidebar.jsx
│ │ │ ├── TLS.jsx
│ │ │ └── Toasts.jsx
│ │ ├── utils/
│ │ │ ├── api.js
│ │ │ └── format.js
│ │ ├── App.jsx
│ │ ├── main.jsx
│ │ └── styles.js
│ ├── Dockerfile
│ ├── nginx.conf
│ ├── vite.config.js
│ ├── index.html
│ └── package.json
└── README.md
| Image | Link |
|---|---|
| Frontend | zackwag/caddy-ui-frontend |
| Backend | zackwag/caddy-ui-backend |
| Version | Description |
|---|---|
v1.10.1 |
Replace docker exec validation with Caddy /adapt API, remove Docker socket dependency, fix route insertion index out of bounds for non-standard Caddy configs, remove version field from Dashboard |
v1.10 |
URL-based navigation with React Router, full RESTful API audit, inline style cleanup, Homepage widget support, enriched status endpoint, CADDYFILE_PATH renamed to CADDY_CONFIG_PATH |
v1.9 |
Dark/light theme toggle, log export, root CA cert download, Caddy version via Docker socket, env var support in Caddyfile |
v1.8 |
Metrics tab, upstream uptime tracking, simplified dashboard process card |
v1.7 |
JWT auth, Caddy process info, metrics toggle, public metrics endpoint |
v1.6 |
Edit routes in-place, route notes, Caddyfile syntax highlighting |
v1.5 |
Caddyfile version history with snapshots, log search and level filters |
v1.4 |
Dashboard health summary, route search/filter, Caddyfile backup and restore |
v1.3 |
Upstream healthchecks, clickable domain/upstream links, http/https scheme detection |
v1.2 |
TLS certificate tab, orphaned cert cleanup, all server routes visible, mobile layout |
v1.1 |
Mobile responsive layout, hamburger menu |
v1.0 |
Initial release |
MIT