README.md
Rama main · réplica de lectura del repositorio original.
# Caddy UI
A modern, self-hosted web UI for [Caddy](https://caddyserver.com/) — manage proxy hosts, redirections, SSL certificates, and advanced routes through a clean interface, without touching config files.
[](LICENSE)
[](https://hub.docker.com/r/applegater/caddyui)
[](https://hub.docker.com/r/applegater/caddyui)
[](https://go.dev/)
**Documentation:** the [CaddyUI wiki](https://github.com/X4Applegate/caddyui/wiki) covers installation, every Settings page, proxy hosts and the Caddyfile-to-form mapping, certificates, DNS providers, the fleet, observability, the REST API and troubleshooting. The same guide ships inside the app under **Docs**.
**Lighthouse:** Performance **99** · Accessibility **100** · Best Practices **100** · SEO **100** *(measured on `/login` from Google's PageSpeed Insights against a residential-ISP install — `:v2.12.53` and later)*
---
## Install
```bash
docker pull applegater/caddyui:latest
```
Docker images (`applegater/caddyui` and the companion `applegater/caddyui-caddy`) are multi-arch — `linux/amd64` and `linux/arm64` — so `docker pull` picks the right one for x86-64 servers and arm64 hosts (Raspberry Pi, Ampere, Graviton, Apple Silicon) automatically. They include SBOM/provenance attestations. The CaddyUI container runs non-root (UID 10001). Native Linux release archives are also available for both `amd64` and `arm64`.
Docker remains the recommended install path, but tagged releases also include
native Linux binary archives for `amd64` and `arm64` hosts. See the
[non-Docker systemd install](#non-docker-systemd-install) below if you want to
run CaddyUI directly in an LXC, VM, or bare-metal host.
---
## Screenshots
| Dashboard | Proxy Hosts |
|-----------|-------------|
|  |  |
| Edit Proxy Host | Certificates |
|-----------------|--------------|
|  |  |
| Caddy Servers | Snapshots |
|---------------|-----------|
|  |  |
| User Management | Settings & Notifications |
|-----------------|--------------------------|
|  |  |
---
## Features
### Fleet deployment, certificates, layer4 routing, and hardening in v2.53 – v2.66
- **Request explorer and latency percentiles** *(v2.66.0)* — **Analytics → Search requests** filters traffic by host, response status, user agent, path, client IP and time window in one view, so *"all 500 errors from this bot in the last hour"* is a single query rather than a chain of drill-downs. The fleet-wide status chips link into it, request-chart bars are split by response class and drill through to their own time bucket, each host lists the exact user-agent strings hitting it, and response times now report p50/p95/p99. Also fixes the response-time panel, which had been returning zeros on any real data set. French joins English and Simplified Chinese; Russian followed in v2.66.1.
- **See who signs in with what** *(v2.68.0)* — the Users page shows, per account, whether it authenticates through your identity provider, with a password, or both. The provider is recorded on each sign-in rather than guessed from how the account was created, and accounts with no usable password are flagged **SSO only** so you know which ones would lose access if single sign-on were switched off.
- **Sign in with Google (or any OIDC provider), restricted to your own domains** *(v2.67.0)* — CaddyUI has spoken OIDC since v2.56, so Google needs no special support: point the Issuer URL at `https://accounts.google.com` and register the callback. What v2.67.0 adds is **Allowed email domains**, because a public provider authenticates every account in the world — with auto-create on, that would be a CaddyUI account for anyone who finds your login page. List your domains and everyone else is refused, for existing accounts as well as new ones. Local password sign-in is unaffected.
- **SSO roles from your IdP, and a recoverable admin allowlist** *(v2.65.0)* — accounts created on first SSO sign-in can take a configured **default role** instead of always landing read-only, or get one mapped from a **groups claim** with `group|role` rules (first match wins, case-insensitive, applied at creation only so nobody is demoted by signing in). The **admin panel IP allowlist** now matches the real visitor address behind a trusted proxy rather than the proxy itself — previously an allowlist naming your own address locked you out, and naming the proxy let everyone in — and **Settings → Security shows the address you are connecting from**, so an entry that would lock you out is visible before you save. If it happens anyway, `CADDYUI_ADMIN_ALLOWLIST` in your compose file overrides the stored list (`=off` disables the gate) without touching what you saved.
- **Client certificate authentication (mTLS)** *(v2.64.0)* — a proxy host can require a certificate from the visitor, not just present one. Under **Proxy Hosts → edit → Security → Client certificate authentication**, pick a mode — *Require and verify* (the usual meaning of mTLS), *Verify if given*, or *Require* / *Request* for the cases where something behind Caddy does the checking — and list the trusted CA certificate files as Caddy sees them inside its own container. Caddy then refuses the TLS handshake unless the client presents a certificate signed by one of them, and your backend can read it through the usual placeholders (`SSL_CLIENT_CERT: {http.request.tls.client.certificate_der_base64}` under *Custom headers* for EJBCA and friends). A verifying mode with no trusted CA is refused at save time, because Caddy would accept it and then turn away everyone. Distinct from **Upstream mTLS Client Certificate**, which points the other way.
- **Automatic deployment targets** *(v2.57.0)* — pick the servers a Caddy Fleet entry always feeds under **Caddy Fleet → edit server → Automatic deployment targets**, and every proxy host, redirection and advanced route you create or edit on that server in the web UI is deployed to them on save, instead of re-ticking **Also deploy to** every time. Built for a fleet where one Caddy is the configuration source for a fixed set of edge nodes; the resource forms show those servers as *default*, the per-resource picker stays for one-off extras, and **node-local** keeps a single resource off every other server. Quick enable/disable and maintenance toggles and bulk actions follow the targets too, and a save that a target refuses says which one and why instead of looking like it worked *(v2.68.3)*. Deleting a resource does not delete its copies, imports and the REST API don't deploy, and adding a target does not copy what already exists — use **Sync from current** for those. Off until you pick targets.
- **Interface languages** *(v2.62.0)* — CaddyUI can now be shown in other languages: each person picks one under **Profile → Language** (or follows their browser), with a site-wide default under Settings. Translations are plain JSON files in `web/i18n/` with English as the source and the fallback for anything not yet translated, so a partial translation never breaks a page. The navigation, menus, list-page titles, the Dashboard, Profile and Settings › General are on the catalog *(v2.63.0)*; more of the interface follows release by release. Available: **English**, **Français** *(v2.66.0)*, **Русский** *(v2.66.1)* and **简体中文 (Simplified Chinese)** *(v2.62.1)* — the last three contributed by the community. Want to translate? See [docs/TRANSLATING.md](docs/TRANSLATING.md).
- **Coraza WAF in middleware profiles** *(v2.61.0)* — switch on the [Coraza](https://coraza.io) web application firewall with the OWASP Core Rule Set for every host that uses a profile: **Detection only** (logs what it would block, blocks nothing — start here) or **Block** (403), paranoia level 1–4, optional custom SecLang directives. It runs first in the host's route, so an attack is refused before auth or the upstream. Needs a Caddy built with the Coraza module — the published `applegater/caddyui-caddy` image includes it from v2.61.0 — and CaddyUI checks every affected server before saving, so a Caddy without the module is refused up front instead of rejecting its whole config.
- **Layer4 on a shared port** *(v2.61.0)* — a Layer4 proxy can ride a port that is already in use. Choose *Share the HTTPS / HTTP port* and match **TLS SNI** names, **SSH**, **RDP** or **PostgreSQL**: CaddyUI adds a [`caddy-l4` listener wrapper](https://github.com/mholt/caddy-l4/blob/master/docs/servers.md) to the HTTPS (`:443`) or HTTP (`:80`) server, so SSH for `ssh.example.com` can travel over 443 next to normal HTTPS. Everything that matches no shared proxy carries on to Caddy as before; SNI routes can pass the TLS connection through or terminate it. TCP only.
- **Middleware profiles** *(v2.60.0)* — reusable bundles of HTTP policy, configured once under **Middleware Profiles** (admin-only) and picked on any number of proxy hosts: **security headers** (HSTS, X-Frame-Options, Referrer-Policy, Permissions-Policy, CSP), **forward auth** (Authentik, Authelia — URL, method, copied headers, header prefix, skip paths), **IP allowlist / blocklist** and **extra request/response headers**. A host-level value wins over the profile, empty host fields inherit it, and the header lists merge by name; the host row itself is never rewritten, so detaching a profile restores exactly what the host had. Editing a profile re-syncs every server that has a host using it, deployed copies on fleet targets follow the same profile, and a profile in use cannot be deleted. Needs no extra Caddy module.
- **Configurable fallback status** *(v2.60.0)* — **Settings → General → Fallback status code** (any `400`–`599`, default `404`) is answered for a request that matches no proxy host, redirection or Advanced Route, identically on HTTP and HTTPS (including hosts under a managed wildcard certificate). The old *Custom 404 page HTML* is now **Custom fallback page HTML** and is sent with the chosen status.
- **Propagate deletions** *(v2.59.0, opt-in)* — a source server can also delete, not just create, what it deployed. Tick **Propagate deletions to automatic deployment targets** on its Caddy Fleet entry and deleting a proxy host, redirection, advanced route or Layer4 proxy there (UI, bulk or REST API) deletes only the paired copies on its automatic targets — never target-local rows, rows created independently on a target, or copies on one-off **Also deploy to** servers. An unreachable target keeps the deletion pending (shown on the server's edit page with **Retry now** / **Discard**) and CaddyUI retries every minute until that target converges. Off by default.
- **Layer4 Proxies** *(v2.58.0)* — a dedicated, admin-only section for TCP/UDP proxies (name, protocol, listen address/port, upstream host/port, enabled, node-local). CaddyUI generates the `caddy-l4` config and pushes it; proxies follow the same fleet behaviour as Proxy Hosts (automatic deployment targets, one-off **Also deploy to**, node-local opt-out; deleting does not remove copies). Requires a Caddy built with `caddy-l4` (`applegater/caddyui-caddy`). The raw block below stays available as the Advanced mode.
- **Layer4 app support** *(v2.56.0)* — paste a complete `layer4 { ... }` block (TCP/UDP routing via [`caddy-l4`](https://github.com/mholt/caddy-l4): SNI/protocol multiplexing, raw proxying, and more) into a Caddy server's own edit form. CaddyUI adapts it through that server's admin API and merges only the resulting `apps.layer4` into the config it pushes, with fleet replication via **Also copy to**. The published `applegater/caddyui-caddy` image now includes the module.
- **Share one managed ACME certificate across fleet nodes** *(v2.55.0)* — an opt-in toggle on a Managed ACME certificate's **Also configure on** picker lets one source server perform the ACME order while CaddyUI pushes the resulting certificate and private key to the other selected servers, instead of every node ordering and renewing its own. Avoids multiplying CA rate-limit usage and keeps DNS API credentials off edge nodes.
- **Configurable ACME DNS-01 resolver** *(v2.54.0)* — **Settings → DNS → ACME DNS-01 resolvers** points *Caddy's own* DNS-01 propagation checks at specific DNS servers, for split-horizon setups where the same zone is served privately inside the network and publicly for ACME validation. Distinct from the Verification resolver below, which only affects CaddyUI's own deploy/readiness checks.
- **Certificate reads confined to allowlisted directories** *(v2.53.0)* — a certificate or key path can no longer name an arbitrary file on the host; reads are confined to the database directory, every Caddy node's Data directory, conventional certificate locations, and any directory an admin explicitly adds under Settings → Security.
### Analytics, certificates and local services in v2.39 – v2.52
- **OIDC / SSO login** *(v2.52.0)* — sign in through an external identity provider (Authelia, Authentik, Keycloak, Google, …) alongside local password + TOTP; configure under Settings → Security. Standard auth-code flow with state/nonce and `go-oidc` token verification; verified-email matching with opt-in auto-provisioning.
- **Scheduled off-host backups** *(v2.52.0)* — automatic SQLite snapshots to a directory on an interval (keep the newest N), plus a **Back up now** button, under Settings → Backup. Point the directory at a mounted volume or share to keep copies off the host.
- **Per-host rate limiting** *(v2.51.0)* — cap a host at *N requests per M seconds, per client IP* (429 on excess), via the `caddy-ratelimit` module now built into `Dockerfile.caddy`. Rebuild your custom Caddy image to use it.
- **Block an IP from analytics** *(v2.50.0)* — a visitor's drill-down page gains one-click **Block on this host** and **Block everywhere (fleet-wide)** actions; the global blocklist is managed under Settings → Security. Turns "who's probing me?" into a one-click 403.
- **Configurable verification resolver** *(v2.49.0)* — point the deploy/readiness DNS checks at trusted, internal, or split-horizon resolvers (plain DNS servers or a DoH URL) under **Settings → DNS**, instead of the default Cloudflare DNS-over-HTTPS — for networks where outbound DNS to Cloudflare is filtered.
- **Drill down by status class** *(v2.48.0)* — click a class in the **Status mix** card or the **HTTP status breakdown** legend to see the exact status codes within it, the top paths that produced them and the top visitors that received them (paths and IPs link on into their own drill-downs).
- **Deeper analytics drill-down** *(v2.47.0)* — click a visitor (client IP) to see their top paths, status mix, exact error detail and user-agents, or click a path to see which visitors requested it. An **Error detail** table shows errors by exact status code + path (`404 /wp-login.php ×840`) instead of just a "4xx" total — handy for spotting probing/fuzzing.
- **Internal CA (`tls internal`) for local services** *(v2.46.0)* — a proxy host can now be served from Caddy's built-in self-signed CA instead of ACME, for internal or local-network services where a publicly trusted certificate isn't wanted or possible. Tick **Internal CA (self-signed)** in the host's TLS section (Auto TLS only); CaddyUI issues via Caddy's `internal` issuer and skips DNS-01 for that host. No special Caddy build required — clients just need to trust Caddy's root CA.
- **Live TLS probe for file-path certificates** *(v2.39.0)* — a certificate stored as a file path inside the Caddy container now shows expiry, issuer and days left from what the node actually serves, instead of nothing; Inspect gains a **Live check** card and warns when a renewed file has not been reloaded by Caddy.
- **Fleet sync for every certificate** *(v2.41.0)* — **Sync configuration** and **Also configure on** copy PEM and file-path certificates too (a readable file-path certificate travels as stored PEM), and hosts created on a target keep referencing the copied certificate instead of falling back to Auto TLS.
- **Export a managed certificate to a directory** *(v2.42.0)* — mount the node's Caddy data volume, set its **Data directory** on the Caddy Fleet entry, and CaddyUI copies the chain and key out after every issuance and renewal for a mail server or any other service, with **Export now** on the certificate form.
- **Changes Caddy rejects are refused up front** *(v2.42.1)* — proxy hosts, redirections and certificates are validated against Caddy before they are saved (for example a certificate file Caddy cannot open), and a sync that fails afterwards shows an amber banner on every page with the exact error and **Retry sync now**.
- **Advanced config understands `reverse_proxy { … }`** *(v2.40.0 – v2.42.2)* — sub-directives such as `flush_interval -1`, `header_up` and `transport http { … }` are merged into the host's own handler; bare sub-directives, JSON-style option names and misplaced transport options are repaired instead of rejected.
- **Analytics retention that actually runs** *(v2.43.0 / v2.43.1)* — the raw-event prune loop now starts (it never had), retention is a setting, and Settings → Analytics shows a **Storage** panel with file size, oldest event and reclaimable space plus **Prune now** and **Reclaim space**.
- **Settings is a set of pages** *(v2.44.0)* — General, Notifications, DNS, Security, Analytics, Integrations, AI assistant and Backup, each saving only its own settings.
- **Gandi managed DNS** *(v2.45.0)* — a LiveDNS Personal Access Token drives A-record management and DNS-01 issuance; `Dockerfile.caddy` builds the `caddy-dns/gandi` module.
- **Security headers really are sent** *(v2.45.2)* — hosts with the Security Headers bundle enabled were serving none of them, because Caddy applies header deletes after sets; fixed on the next sync.
### Post-apply checks, monitoring and hardening in v2.26 – v2.38
- **Expectations: post-apply checks with automatic rollback** *(v2.38.0)* — declare the requests a host must keep answering (method, path, expected status or Location, maximum latency, valid TLS). CaddyUI runs them after every sync; if one fails it loads the previous live config back into Caddy, pauses automatic syncs for that server and shows a banner with **Re-apply now** / **Keep the rolled-back config**. Suggested by Caddy's maintainer.
- **Each node ships its logs to a target it can reach** *(v2.37.0)* — a per-node **Log ingest target** on the Caddy Fleet entry, so remote nodes' Analytics, certificate status and Server Logs work beyond the Docker network.
- **Advanced routes on custom ports keep their listener** *(v2.36.1)* — a `:7070 { … }` block becomes its own Caddy server instead of being folded into :443; the Certificates page also lists Advanced routes' Auto-TLS domains *(v2.36.2)*.
- **Monitoring you can shape** — health checks with any HTTP method *(v2.36.0)*; **Automatic / Custom / Off** status monitoring per host, with Custom path, method, expected status, interval and timeout *(v2.28.0)*; **Off** silences all three probes *(v2.35.0)*; an always-present link to each host's public health history *(v2.35.1)*.
- **Node-local hosts and routes** *(v2.33.0)* — resources whose upstream only resolves on one node (a Docker service name, a VPN address) are excluded from fleet sync and **Also deploy to**, and counted rather than silently dropped.
- **Hardening** — a CSRF token on every state-changing request *(v2.29.0)*; a Content-Security-Policy on CaddyUI's own pages and byte-identical generated config *(v2.30.0)*; no third-party scripts at runtime *(v2.27.0)*.
- **Everyday improvements** — live Caddyfile and JSON preview on the proxy host form *(v2.26.0)*; **Also deploy to** for Advanced routes *(v2.27.0)*; the Analytics page four times faster at scale *(v2.31.0)*; full dates on Server Logs and Live Traffic with a **Clear** that sticks *(v2.35.5, v2.37.1)*.
### Fleet observability in v2.25
- **Actual node attribution** — Live Traffic, Analytics, per-host drill-downs, and CSV exports identify the Caddy instance that handled each request, including shared hostnames behind a load balancer.
- **Ephemeral Server Logs** — administrators can stream structured runtime logs from one managed node at a chosen level. Full capture is memory-only, preserves normal Caddy output, and stops automatically after 15 minutes.
- **Certificate lifecycle states** — managed ACME certificates show obtaining, renewing, retrying, issued, revoked, and failure states with the latest Caddy message instead of an unconditional healthy badge.
### What's new in v2.21
- **Fleet file access logs** — independently write native JSON or console access logs to selected Caddy servers with HTTP/HTTPS scope and rotation controls while preserving CaddyUI Visitor Analytics.
- **CrowdSec request protection** — configure the LAPI, masked bouncer key, streaming, refresh, hard-fail, selected environments, host/path exclusions, module validation, and live connection testing from Settings.
- **Correct client IPs** — configure `client_ip_headers` alongside trusted proxy ranges so logging and CrowdSec evaluate Caddy's resolved client address behind Cloudflare or another load balancer.
- **Batteries-included custom Caddy** — `Dockerfile.caddy` includes the CrowdSec HTTP bouncer module and the repository Compose stack persists `/var/log/caddy`.
### Certificate cleanup in v2.20.1
- **Unused certificates are now named** — the Operations recommendation identifies affected PEM/file-path certificates and opens a filtered cleanup view with an **Unused** marker on each row.
- **No false positives for managed ACME** — standalone and wildcard certificates automated by Caddy are no longer classified as unused simply because they are not attached through a resource certificate ID.
- **Dismiss without permanent silence** — administrators can hide the recommendation for the selected environment. It returns automatically when the set of unused certificates changes.
### Enterprise workflows in v2.20
- **Guided first-run journey** — create the administrator account, verify the Caddy control plane, secure access, optionally configure DNS automation, and publish the first service through one live readiness guide.
- **Publish-service wizard** — new proxy hosts follow Hostname → Upstream → Policy → Review, with native validation and a deployment summary before anything is written to Caddy.
- **Enterprise semantic colors** — blue is action/current context, green is healthy/enabled/completed, amber is pending/degraded/maintenance, red is failure/offline/destructive, violet is TLS/automation/managed policy, and gray is disabled/unknown/informational.
- **Professional fleet operations** — Caddy environments are organized as an inventory with health, endpoint, management policy, last contact, and scoped actions; connecting a server follows Identify → Connect → Authorize.
- **Faster daily workflows** — a global Create menu, persistent environment context, consistent **Publish service** language, and a one-click advanced editor keep both new and experienced operators moving.
The guide reads real configuration state rather than a one-time “completed”
flag. DNS automation is optional and never prevents an otherwise operational
environment from reaching 100% readiness.
### Database and performance improvements in v2.19
- **MariaDB backend** — keep embedded SQLite for simple installs or connect CaddyUI to MariaDB for concurrent enterprise workloads, replication, and platform-managed backups.
- **Safe database migration** — copy an existing SQLite installation into an empty MariaDB database with a read-only, batched migration command. Large analytics histories can be intentionally skipped.
- **Faster Proxy Hosts** — request counters now use domain-scoped index lookups instead of scanning the complete analytics history, and live probes wait until after the initial browser paint.
- **Database-aware operations** — automatic schema migrations, portable sessions/retention/rollups, MariaDB-specific backup guidance, a Compose overlay, and dedicated MariaDB CI coverage.
### Enterprise operations UI in v2.18
- **Enterprise operations center** — the dashboard now leads with the active environment, route inventory, live system and upstream health, prioritized risks, fleet status, and recent configuration activity instead of a loose collection of cards.
- **Workflow-first application shell** — navigation is regrouped around Traffic, Observe, Configuration, Developer, and Administration workflows. The current Caddy environment is always visible and switching servers is faster.
- **Visible command search and primary actions** — search resources from the persistent top bar, create a proxy host, sync Caddy, or manage the fleet without hunting through menus. Read-only viewers never see the create action.
- **Shared enterprise design system** — semantic surfaces, status treatments, panels, buttons, metrics, tables, focus states, and responsive rules now provide a durable foundation for the rest of the product.
- **First-class mobile operations** — one compact mobile toolbar, responsive route/system metrics, stacked attention queues, fleet cards, and mobile host cards preserve the full operational picture without horizontal scrolling.
- **Theme and accessibility coverage** — the redesign supports light, dark, Carbon Orange, Forest, Rose, and Indigo themes, with keyboard focus visibility and reduced-motion handling.
### Certificate automation in v2.17
- **Visible Auto TLS certificate selection** — the **Caddy-managed (ACME)** table now names the managed wildcard used by each covered domain and labels uncovered hosts **Direct certificate**, so the UI no longer implies every hostname received a separate certificate. *(v2.17.3)*
- **Enforced Auto wildcard reuse** — Auto TLS suppresses redundant exact-host issuance when a managed wildcard covers the name. *(v2.17.2)*
- **Live per-server renewal status** — edit a managed certificate to inspect each server's live expiry, issuer, deployment, and renewal health. *(v2.17.2)*
- **Multi-server managed wildcard reuse** — copy managed certificate definitions to selected Caddy instances, or let proxy **Also deploy to** automatically discover and replicate a covering wildcard on each target. Every server keeps its own ACME order and private key. *(v2.17.1)*
- **Standalone managed wildcard certificates** — **Certificates → New → Managed ACME (DNS-01)** obtains and renews `*.example.com` or `*.sub.example.com` without a proxy host, pasted PEM, or exported private key. *(v2.17.0)*
See the [complete managed wildcard workflow](#managed-wildcard-certificates-v217) below.
### Earlier highlights
- **Import SSL from Porkbun** — new button on the Certificates page (visible when Porkbun credentials are saved) that opens a domain picker and pulls the SSL bundle directly from your Porkbun account into CaddyUI as a stored PEM certificate. *(v2.14.0)*
- **Porkbun API v3 full compatibility** — fixed JSON type mismatches where the Porkbun API returns integer fields (`whoisPrivacy`, `autoRenew`, `securityLock`, `apiAccess`, `notLocal`) as quoted strings; all domain and DNS decode paths now handle both forms transparently. SSL retrieve. *(v2.14.1, v2.14.2)*
- **⌘K / Ctrl+K command palette** — global search across every proxy host, redirection, raw route, and certificate. `↑/↓ Enter Esc`. Color-coded type pills. *(v2.11.5)*
- **Bulk multi-select on every list page** — checkbox + select-all + floating Enable / Disable / Delete bar on `/proxy-hosts`, `/redirection-hosts`, `/raw-routes`, and `/certificates`. *(v2.11.6, .9, .10)*
- **Drag-to-reorder rows** — HTML5 ⠿ handle on `/proxy-hosts` and `/redirection-hosts`. *(v2.11.11)*
- **Live route preview** on the proxy-host edit form — the exact Caddy route JSON and a Caddyfile excerpt your form would push, refreshing as you type. *(v2.11.13, v2.26.0)*
- **Multi-server health widget** on the dashboard — one card per registered Caddy server with status, host count, version, last seen. *(v2.11.16)*
- **AI assistant — bring your own backend** *(v2.11.15, v2.12.36+)* — opt-in floating chat button that answers Caddy / TLS / DNS questions and writes production-grade Caddyfile snippets on demand. Pick any backend in Settings → AI assistant: **Ollama (local)** for fully-offline on your GPU, **Ollama Cloud** for hosted MoE models that won't fit on a homelab card (qwen3-coder:480b, gpt-oss:120b, etc.), **Anthropic Claude** (Haiku 4.5 / Sonnet 4.6 / Opus 4.7), or **any OpenAI-compatible API** (also covers OpenRouter, Groq, Together, vLLM, LM Studio). Conversation memory (multi-turn), markdown rendering, and a custom system prompt setting are shared by every backend.
- **AI auto-fill — chat your hosts into existence** *(v2.12.11+)* — describe what you want in plain English (*"set up nextcloud at cloud.example.com pointing to nextcloud:80"*, *"redirect old.example.com to new.example.com permanently"*) and the assistant emits a structured `create_proxy_host(...)` / `create_redirection(...)` tool call. The chat panel renders a confirmation card with the exact arguments — click **Apply** and the resource is created, the form is filled in for you, and Caddy is auto-synced. Every AI-driven exec writes an `ai_tool_call` activity-log entry so admins can audit what the AI did. Works on Claude 4.x, GPT-4, qwen2.5+, llama3.1+, and gemma2.
- **DNS-01 certificate issuance** — select Managed DNS on a proxy, redirection, or advanced route and CaddyUI reuses that credential profile for Let's Encrypt DNS-01. Supports Cloudflare, Porkbun, Namecheap, GoDaddy, DigitalOcean, Hetzner, Amazon Route 53, and Gandi; the connected Caddy needs the matching `caddy-dns` plugin. Wildcards are supported, and public A-record creation can be disabled for internal/split-DNS services. *(Route 53 added in v2.23.0; DNS-01-only mode in v2.23.1)*
- **Per-hostname DNS-record pre-flight** on multi-domain routes — checklist showing which records will be created vs already exist. *(v2.12.1)*
- **Managed DNS on redirections** — closes the long-standing gap where redirects had no DNS plumbing. Now creates A records per hostname on save, deletes on row removal. *(v2.12.2)*
- **Settings pages** — General, Notifications, DNS, Security, Analytics, Integrations, AI assistant and Backup, each with its own save. *(v2.44.0; per-section save since v2.12.6)*
- **Active-server-scoped dashboard cards** — Requests / Visitors / Bandwidth Today now reflect only the server selected in the picker, including traffic to wildcard SAN hostnames. *(v2.12.8, v2.12.9)*
### Managed wildcard certificates (v2.17+)
Use one DNS-01 wildcard across matching proxy hosts without exporting or copying its private key:
1. Go to **Settings → DNS** and save credentials for your DNS provider.
2. Go to **Certificates → New**, choose **Managed ACME (DNS-01)**, select that credential profile, and enter a subject such as `*.example.com`.
3. If you manage multiple Caddy servers, select them under **Also configure on**. By default each server performs its own ACME order and keeps its own private key. Since v2.55.0, ticking **Push this certificate instead of letting each server obtain its own** flips this: this server performs the ACME order and CaddyUI pushes the resulting certificate and key to the others instead — useful for avoiding CA rate limits or keeping DNS credentials off edge nodes.
4. Leave matching proxy hosts on **Auto** TLS. CaddyUI detects the covering wildcard and prevents redundant exact-host certificate orders.
5. Edit the managed certificate later to see live deployment, issuer, expiry, and renewal health for every configured server.
Your Caddy build must include the matching `caddy-dns` provider module. The easiest path is the prebuilt **`applegater/caddyui-caddy`** image (all eight DNS providers, the CrowdSec bouncer, the rate-limit module, the Coraza WAF, and `caddy-l4` for [layer4 app support](#fleet-deployment-certificates-layer4-routing-and-hardening-in-v253--v262)); it's the published build of [`Dockerfile.caddy`](Dockerfile.caddy), which you can also build yourself. The stock `caddy:2-alpine` image does not include these modules.
### Routing
- **Proxy Hosts** — point domains at upstream services with one-click TLS via Caddy's automatic HTTPS
- **Redirections** — 301/302/307/308 redirect rules across hostnames; path-based rules with `redirect_rules`; sunset-date 410 conversion; wildcard subdomain capture
- **Advanced Routes** — import raw Caddyfile blocks or write JSON directly for anything the UI can't model; a block on a custom port (`:7070 { … }`) keeps its own listener, and routes can be deployed to other nodes with **Also deploy to**
- **Certificates** — upload custom PEM / path-based certificates or create standalone Caddy-managed DNS-01 wildcard certificates; live TLS probe for expiry of file-path certificates, export of a managed certificate's chain and key to a directory after every renewal, fleet sync for every kind, expiry alerts via email or webhook
- **Managed DNS** — optionally create the A record for a new proxy host, redirection, or advanced route without leaving CaddyUI; records created by CaddyUI are auto-deleted with the row. The same credentials drive DNS-01 certificate issuance even when public A-record creation is disabled. Providers: **Cloudflare, DigitalOcean, Hetzner, Porkbun, GoDaddy, Namecheap, Amazon Route 53, Gandi**. Zone lookup is automatic from the domain.
- **Paste Caddyfile import** — convert a Caddyfile into managed proxy hosts / redirections / advanced routes; smart classifier auto-routes each block to the right table *(v2.10.7+)*. Re-classify button rescues older imports
- **Import from Caddy** — pull your existing live Caddy config into the DB on first run
- **Per-host options surface (~70 fields)** — request / response headers (X-Forwarded-*, security headers, cache hints), path-based upstream overrides, multi-upstream routing, active health checks, upstream TLS settings, request blocking (deny by path / query / user-agent / method), plus an **Advanced config** box for extra Caddyfile directives and a `reverse_proxy { … }` block merged into the host's own handler
- **Post-apply checks** — expectations per host, run after every sync, with automatic rollback of the live config when one fails
- **Validated before saving** — changes Caddy would reject are refused with a plain explanation, and a sync that fails afterwards is shown on every page until it succeeds
- **Branded error pages** — CaddyUI-styled 404 / 502 / 503 / 504 pages are injected into Caddy automatically
### Multi-server
- **Remote Caddy management** — manage multiple Caddy instances from a single UI; switch with a dropdown. Edge hosts only need Caddy — no CaddyUI container required (see [Agent mode](#agent-mode-edge-only-caddy-no-caddyui))
- **Per-server scoping** — proxy hosts, redirections, routes, and certificates are all scoped to the active server; cross-server conflicts can't happen
- **Fleet sync and Also deploy to** — copy a whole environment, or one host, redirection, route or certificate, onto other nodes; node-local resources stay where they belong, and each node has its own public IP, log ingest target and data directory; a source server can also list **automatic deployment targets** that every UI save is mirrored to
- **Interface languages** *(v2.62.0)* — per-user language with English fallback; contribute a translation via [docs/TRANSLATING.md](docs/TRANSLATING.md)
- **Coraza WAF** *(v2.61.0)* in middleware profiles, and **Layer4 on a shared port** (SSH/RDP/PostgreSQL beside HTTPS on 443)
- **Middleware profiles** *(v2.60.0)* — reusable forward auth / security headers / IP restrictions / extra headers attached to many proxy hosts; **Configurable fallback status** for unmatched requests
- **Propagate deletions** *(v2.59.0)* — opt-in deletion of paired copies on automatic deployment targets, with retry for unreachable nodes
- **Layer4 Proxies** *(v2.58.0)* — managed TCP/UDP proxies in their own section, with fleet deployment like Proxy Hosts
- **Layer4 app support** *(v2.56.0)* — a per-server `layer4 { ... }` Caddyfile block for TCP/UDP routing, replicated to other nodes via **Also copy to**
### Access control
- **Three-role RBAC** — `admin` (full control), `user` (manage their own resources), `view` (read-only). Admin-only pages: Users, Groups, Settings, Caddy Servers, Snapshots
- **Per-user ownership** — proxy hosts, redirections, advanced routes, and certificates each belong to one user; only the owner, their group peers, and admins can edit or delete them. Admins can reassign ownership from any resource's edit form
- **Groups** *(v2.7.4, collaborative since v2.52.5)* — admin bundles `user`-role accounts into a team; every member sees every other member's resources in their list views, with a `Team` chip so it's clear which rows are "mine" vs. "my teammate's". Since v2.52.5 a shared row is also **manageable** by its group peers, not just visible — global (admin-owned) rows stay admin-only
- **2FA / TOTP** — per-user time-based one-time passwords
- **Login CAPTCHA** — optional Cloudflare Turnstile or reCAPTCHA v3 gate on the login form
- **Hardened by default** — CSRF tokens on every state-changing request, a Content-Security-Policy on CaddyUI's own pages, no third-party scripts at runtime, admin IP allowlist, login attempt limits
### Observability
- **Fleet observability** *(v2.25.0)* — attribute requests to the node that actually handled them, stream temporary in-memory runtime logs from **Observe → Server Logs**, and surface per-node ACME progress/errors from structured Caddy TLS events
- **Prometheus metrics** *(v2.24.0)* — enable base HTTP metrics, per-host labels, and Caddy 2.11+ catch-all host observation on selected fleet servers. CaddyUI validates each target, preserves metrics it does not own, and shows the protected Admin URL's `/metrics` scrape target
- **Visitor analytics** *(v2.7.0)* — opt-in per-host traffic counters; top hosts, 24 h sparkline, status-code mix, unique visitors. Per-server filter for multi-Caddy fleets. The Caddy log writer uses soft-start failover so Caddy can boot while CaddyUI is offline. Raw events are kept for a configurable number of days (default 30) with per-day rollups for long-range charts, and a Storage panel reclaims space *(v2.43.0)*
- **Upstream health** — live health check per proxy; polls Caddy's own admin API so Docker-internal hostnames work correctly
- **App health** — detects whether the upstream actually responds, not just whether its TCP port is open
- **Activity log** — every create / edit / delete / sync action is logged with actor, timestamp, and resource
Certificate lifecycle monitoring and Server Logs share the structured ingest
target configured under **Settings → Analytics**, even when visitor analytics
is disabled. Every managed Caddy node must be able to reach that private
host and port (default `caddyui:9019` on the repository Docker network); a
node on another host gets its own **Log ingest target** on its Caddy Fleet
entry *(v2.37.0)*. Do not publish the ingest listener to the internet.
### Operational
- **SQLite or MariaDB** — embedded SQLite by default; optional MariaDB backend for concurrent enterprise workloads, replication, and managed backups
- **Snapshots** — automatic and manual Caddy configuration snapshots on both backends; one-click full-file backup on SQLite
- **Email notifications** — SMTP support (STARTTLS / TLS / plain) for cert-expiry and upstream-health alerts
- **Webhook notifications** — generic JSON POST for cert-expiry (pair with any notifier that accepts webhooks)
- **Update notifications** — the Operations dashboard announces a newer Docker Hub release
- **Settings pages** — eight focused pages instead of one long form, each saving only its own settings
- **Dark mode** — toggleable, remembers your choice; system preference respected on first visit
- **PWA** — installable on desktop and mobile; offline-capable service worker
---
## AI Assistant
CaddyUI ships an opt-in chat assistant that knows the entire CaddyUI surface area — every page, every Settings field, every proxy-host form section — and can both **answer questions** about Caddy / TLS / DNS and **auto-fill new resources for you** via tool calling.
### Pick a backend
Go to **Settings → AI assistant** and choose one of:
| Backend | Best for | What you need |
|---|---|---|
| **Ollama (local)** | Fully-offline, runs on your GPU, no cloud calls | An Ollama container reachable from CaddyUI; recommended models: `qwen2.5:14b`, `qwen2.5-coder:14b`, `gemma2:9b`, `llama3.1:8b` |
| **Ollama Cloud** | Hosted MoE models that won't fit on a homelab GPU | API key from [ollama.com/settings/keys](https://ollama.com/settings/keys); models: `qwen3-coder:480b-cloud`, `gpt-oss:120b`, `deepseek-v3.1:671b` |
| **Anthropic Claude** | Strongest reasoning + Caddy knowledge out of the box | API key from [console.anthropic.com](https://console.anthropic.com); models: `claude-haiku-4-5-20251001` (fast), `claude-sonnet-4-6` (balanced), `claude-opus-4-7` (max) |
| **OpenAI-compatible** | OpenAI itself, OpenRouter, Groq, Together, self-hosted vLLM, LM Studio | Base URL + API key + model name |
Switching providers preserves the inactive providers' credentials, so you can flip back and forth without re-entering keys.
### What you can ask
**Knowledge questions** — the assistant has the full CaddyUI app map in its system prompt, so it points you at the *exact page → section → field* for whatever you're trying to do:
> *"How do I strip the X-Powered-By header globally?"*
> → Settings → General → Globally stripped response headers.
> *"Where do I configure SSE flushing?"*
> → Proxy host form → Timeouts → Stream flush interval ms (use `-1`).
**Caddyfile help** — production-grade snippets, not 3-line stubs. The default system prompt steers the model toward `encode zstd gzip` + path blocking + security headers + the `X-Forwarded-*` trio. Strip back to minimum only when you ask for *"just the basics"*.
### AI auto-fill (tool calling)
Describe what you want and the assistant calls a tool to create it:
> *"Set up nextcloud at cloud.example.com pointing to nextcloud:80 with auto-SSL"*
> → emits `create_proxy_host(domains="cloud.example.com", forward_host="nextcloud", forward_port=80, ssl_enabled=true, ssl_forced=true)`.
The chat panel renders a **confirmation card** with the exact arguments. You click **Apply** and CaddyUI:
1. Creates the proxy host (or redirection).
2. Auto-syncs Caddy with the new config.
3. Writes an `ai_tool_call` row to the activity log so admins can see exactly what happened.
Tools currently available:
- `create_proxy_host(domains, forward_scheme, forward_host, forward_port, ssl_enabled, ssl_forced)`
- `create_redirection(domains, forward_scheme, forward_domain, forward_http_code, preserve_path)`
Models with native tool support — Claude 4.x, GPT-4 / GPT-4o, qwen2.5+, llama3.1+, gemma2 — will use these. Older or smaller models silently ignore the tools field and just chat.
### Custom system prompt
If you want a different persona, in-house naming conventions, or a leaner / chattier voice, paste your own steering text into **Settings → AI assistant → Custom system prompt**. Leaving it blank uses the built-in default that has the full CaddyUI knowledge map and the production-grade Caddyfile examples.
### Privacy
API keys are stored in CaddyUI's selected database. The Settings UI never renders saved keys back to the form (a `••••••••` placeholder shows when one is set; leave the field blank to keep the existing key). Local Ollama means the conversation never leaves your machine; cloud backends send the conversation to your chosen provider per their respective terms.
### Community integrations
- [CaddyUI-MCP](https://github.com/loryanstrant/CaddyUI-MCP) — a community-maintained MCP server that wraps CaddyUI's `/api/v1` REST API for agent-driven automation. `GET /api/v1/servers` supplies the fleet ID/name/type mapping integrations need without scraping HTML. Use a dedicated CaddyUI user and the narrowest API token scope that fits your workflow.
---
## Quick Start
### Docker Compose (recommended)
```yaml
services:
caddy:
# Batteries-included Caddy: every CaddyUI DNS provider (for ACME DNS-01),
# the CrowdSec bouncer, and the rate-limit module. Published build of
# Dockerfile.caddy. Swap for a stock/custom Caddy if you don't need those.
image: applegater/caddyui-caddy:stable
container_name: caddyui-caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- caddy_data:/data
- caddy_config:/config
environment:
CADDY_ADMIN: 0.0.0.0:2019
command:
- /bin/sh
- -c
- |
mkdir -p /config/caddy
[ -f /config/caddy/autosave.json ] || echo '{}' > /config/caddy/autosave.json
exec caddy run --config /config/caddy/autosave.json --resume --adapter json
networks:
- caddyui
caddyui:
image: applegater/caddyui:latest
container_name: caddyui
restart: unless-stopped
depends_on:
- caddy
ports:
- "8081:8080"
volumes:
- caddyui_data:/data
environment:
CADDYUI_DB: /data/caddyui.db
CADDYUI_LISTEN: :8080
CADDY_ADMIN_URL: http://caddy:2019
networks:
- caddyui
volumes:
caddy_data:
caddy_config:
caddyui_data:
networks:
caddyui:
driver: bridge
```
> **💡 Fresh install:** On first boot Caddy has no saved config yet and would crash-loop without one. The `command` above seeds an empty `{}` config automatically on first start — you don’t need to do anything extra.
Open **http://localhost:8081** and complete the first-run setup (create an admin account).
### Docker Run (standalone)
```bash
docker run -d \
--name caddyui \
-p 8081:8080 \
-v caddyui_data:/data \
-e CADDY_ADMIN_URL=http://your-caddy-host:2019 \
applegater/caddyui:latest
```
### MariaDB (optional enterprise backend)
SQLite remains the zero-configuration default. MariaDB is available for larger
installations that need concurrent UI reads, continuous analytics ingestion,
database replication, or platform-managed backups.
Use the included Compose overlay:
```bash
export CADDYUI_MARIADB_PASSWORD='replace-with-a-long-random-password'
export CADDYUI_MARIADB_ROOT_PASSWORD='replace-with-a-different-random-password'
docker compose -f docker-compose.yml -f docker-compose.mariadb.yml up -d
```
For an existing MariaDB server, configure CaddyUI directly:
```yaml
environment:
CADDYUI_DB_DRIVER: mariadb
CADDYUI_DB_DSN: caddyui:password@tcp(mariadb.example.internal:3306)/caddyui
```
Create the database and user before starting CaddyUI. CaddyUI creates and
migrates its tables automatically. The DSN is never written to application
logs.
To copy an existing SQLite installation, first create a timestamped copy of
`caddyui.db`, stop the normal CaddyUI container, start the empty MariaDB
service, and run:
```bash
docker compose -f docker-compose.yml -f docker-compose.mariadb.yml up -d mariadb
docker compose -f docker-compose.yml -f docker-compose.mariadb.yml run --rm --no-deps \
caddyui migrate-db \
--from-sqlite /data/caddyui.db
docker compose -f docker-compose.yml -f docker-compose.mariadb.yml up -d
```
The source SQLite file is opened read-only and the MariaDB destination must be
empty. Add `--skip-analytics` to migrate configuration, users, certificates,
health history, and activity while leaving large `access_events` and
`access_daily` tables behind. This is strongly recommended when the SQLite
database grew primarily because of visitor analytics.
MariaDB backups are managed with `mariadb-dump`, snapshots, or your database
platform's backup/PITR tooling. Caddy configuration snapshots inside CaddyUI
continue to work on both backends.
### Non-Docker systemd install
Tagged GitHub releases include `linux/amd64` and `linux/arm64` tarballs with
the `caddyui` binary and a sample `caddyui.service` unit. This is useful for
LXC/Proxmox labs, VMs, or hosts where you prefer to run CaddyUI directly under
systemd.
1. Install Caddy separately and make sure its admin API is reachable from the
CaddyUI host. For a same-host install the default is
`http://127.0.0.1:2019`.
2. Download the archive for your CPU architecture from the
[latest GitHub release](https://github.com/X4Applegate/caddyui/releases).
3. Extract the archive and run the packaged installer:
```bash
tar -xvf ./caddyui_vX.Y.Z_linux_ARCH.tar.gz
cd caddyui_vX.Y.Z_linux_ARCH
./install.sh
```
The packaged service stores SQLite data at `/var/lib/caddyui/caddyui.db`,
listens on `127.0.0.1:8080`, talks to Caddy at `http://127.0.0.1:2019`, and
binds the optional visitor-analytics ingest listener to `127.0.0.1:9019`. The
ingest listener only matters if you enable CaddyUI visitor analytics; set
`CADDYUI_INGEST_LISTEN=` in a systemd drop-in to disable it. Override these
values if your Caddy admin API is on another host:
```bash
sudo systemctl edit caddyui
```
```ini
[Service]
Environment=CADDYUI_LISTEN=0.0.0.0:8080
Environment=CADDY_ADMIN_URL=http://10.8.0.2:2019
# Optional: disable visitor-analytics ingest if you do not use analytics.
Environment=CADDYUI_INGEST_LISTEN=
```
Then restart:
```bash
sudo systemctl restart caddyui
```
### Agent mode (edge-only Caddy, no CaddyUI)
For multi-host setups you only need **one** CaddyUI container. Every other
host — the "agent" or "edge" nodes — runs only Caddy, and the central CaddyUI
manages them remotely through Caddy's admin API (typically tunneled over
WireGuard or Tailscale).
On each edge host:
```yaml
services:
caddy:
image: applegater/caddyui-caddy:stable
container_name: caddy
restart: unless-stopped
# --resume is required so admin-API pushes persist across Caddy restarts.
command:
- /bin/sh
- -c
- |
mkdir -p /config/caddy
[ -f /config/caddy/autosave.json ] || echo '{}' > /config/caddy/autosave.json
exec caddy run --config /config/caddy/autosave.json --resume --adapter json
ports:
# Bind the admin API to your private tunnel IP (WireGuard / Tailscale).
# Do NOT expose :2019 on a public interface.
- "10.8.0.2:2019:2019"
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- caddy_data:/data
- caddy_config:/config
volumes:
caddy_data:
caddy_config:
```
Then in the central CaddyUI, go to **System → Caddy Servers → Add Server** and
point it at `http://10.8.0.2:2019` (or whatever private address the edge listens
on). All proxy hosts, certificates, and routes for that edge are managed from
the central UI — no database, no UI container, no extra port to expose on the
edge.
> **Why `--resume`?** In default mode Caddy loads only the Caddyfile at boot
> and discards anything pushed to the admin API. With `--resume` Caddy boots
> from `autosave.json` (the last live config it received via the admin API),
> so CaddyUI's pushes survive `docker compose restart`.
>
> **Fresh install:** On first boot no `autosave.json` exists yet, so Caddy would
> crash-loop. The `command` above seeds an empty `{}` config on first start so
> Caddy comes up cleanly without any extra steps. It has to run through
> `/bin/sh -c` as shown: a plain `command: mkdir -p …; exec caddy …` has no shell
> to interpret the `;`, so only `mkdir` would run and the container would exit
> straight away.
---
## Environment Variables
| Variable | Default | Description |
|---|---|---|
| `CADDYUI_DB_DRIVER` | `sqlite` | Database backend: `sqlite` or `mariadb` |
| `CADDYUI_DB` | `/data/caddyui.db` | Path to the SQLite database |
| `CADDYUI_DB_DSN` | *(unset)* | MariaDB DSN; required when `CADDYUI_DB_DRIVER=mariadb` |
| `CADDYUI_LISTEN` | `:8080` | Listen address |
| `CADDYUI_INGEST_LISTEN` | `:9019` | Structured Caddy log ingest for analytics, certificate lifecycle, and temporary runtime streams; set empty to disable all three |
| `CADDY_ADMIN_URL` | `http://caddy:2019` | Caddy admin API base URL |
| `CADDYFILE_PATH` | `/etc/caddy/Caddyfile` | Optional — path to a Caddyfile mounted into the CaddyUI container. Its `(name) { ... }` snippet definitions are auto-loaded so `import <name>` works in Advanced routes, Caddyfile paste import, and a proxy host's Advanced config; site blocks and global options in it are never used |
| `CADDYUI_PUBLIC_URL` | _(empty)_ | Externally visible origin, e.g. `https://caddyui.example.com`. Used for the links in password-reset and invitation emails; without it the link is built from the request's `Host` header. |
| `CADDYUI_SYNC_ON_START` | *(unset)* | Set to `1` to push DB state to Caddy on startup |
---
## Configuration
All configuration is done through the web UI. No config files needed beyond the environment variables above.
### First Run
1. Open the UI at your configured port.
2. Create the first administrator account in the account setup wizard.
3. Continue into **Getting Started**, which checks the real state of the selected environment.
4. Verify the bootstrap Caddy connection created from `CADDY_ADMIN_URL`.
5. Enable administrator TOTP and save the recovery codes.
6. Add DNS credentials only when you need managed records or DNS-01/wildcard certificates.
7. Select **Publish a service** and complete Hostname → Upstream → Policy → Review.
Add additional Caddy environments under **Administration → Caddy Fleet**.
The readiness guide stays available after setup, so it can be used as an
operational checklist whenever the environment changes.
### SMTP Notifications
Configure under **Administration → Settings → Email (SMTP)**:
- Supports STARTTLS (port 587), implicit TLS (port 465), and plain (port 25)
- Cert-expiry emails fire once per 24 h per domain when within the configured threshold
- Upstream health emails fire on state change (healthy → down, down → recovered), checked every 5 minutes
### Multi-Server
Add additional Caddy instances under **Administration → Caddy Fleet**. Switch the active environment with the selector in the application shell. All proxy hosts, redirections, routes, and certificates are scoped per server.
To deploy the selected environment's complete managed routing configuration to another Caddy, choose **Sync from current** on the target fleet row. The sync is a non-destructive merge: it creates missing proxy hosts, redirects, advanced routes, and managed certificate definitions; updates resources paired by earlier deployments; and leaves target-only resources in place. Target DNS records, custom certificate selections, ACME private keys, and uploaded private keys remain server-specific. Repeating either a full sync or an individual form's **Also deploy to** action is idempotent and does not create duplicate routes.
To make one Caddy the standing configuration source for a fixed set of edge nodes, open the source under **Administration → Caddy Fleet → edit** and tick the servers under **Automatic deployment targets**. From then on every proxy host, redirection and advanced route created or edited on that server in the web UI is mirrored to those servers on save — the same idempotent per-resource deployment as **Also deploy to**, without re-selecting the targets each time. The resource forms list them as *default* (always on); the **Also deploy to** picker still adds one-off extra targets for a single save, and marking a resource **node-local** keeps it on this server only. This is separate from the Layer4 **Also copy to** picker on the same form. Deleting a resource does not delete its copies on the targets unless you opt in with **Propagate deletions to automatic deployment targets** on the same form (*v2.59.0* — see below). Quick enable/disable toggles on hosts, bulk actions, Caddyfile import and the REST API do not deploy; resources that already exist are deployed the next time they are saved, or in bulk with **Sync from current** on the target. Certificates keep their own per-certificate targets.
Edge / remote hosts do **not** need to run a CaddyUI container — just Caddy with its admin API reachable over a private network (WireGuard, Tailscale, VPC). See [Agent mode](#agent-mode-edge-only-caddy-no-caddyui) for the minimal compose file.
---
## Building from Source
CaddyUI can be built and run directly on Linux without Docker. Install Caddy
separately first, then make sure its admin API is reachable from the CaddyUI
host. For same-host installs, Caddy's default admin endpoint is usually
`http://127.0.0.1:2019`.
Install Go 1.25 or newer, then build:
```bash
git clone https://github.com/X4Applegate/caddyui.git
cd caddyui
git checkout "$(git tag --sort=-v:refname | head -n1)"
version="$(git describe --tags --always --dirty)"
CGO_ENABLED=0 go build \
-trimpath \
-ldflags "-s -w -X main.Version=${version}" \
-o caddyui \
./cmd/caddyui
```
To test it before installing:
```bash
mkdir -p ./data
CADDYUI_DB="$PWD/data/caddyui.db" \
CADDYUI_LISTEN=127.0.0.1:8080 \
CADDY_ADMIN_URL=http://127.0.0.1:2019 \
CADDYUI_INGEST_LISTEN=127.0.0.1:9019 \
./caddyui
```
Open `http://127.0.0.1:8080` and complete the first-run setup.
To install your locally built binary as a systemd service:
```bash
id -u caddyui >/dev/null 2>&1 || sudo useradd --system --home /var/lib/caddyui --shell /usr/sbin/nologin caddyui
sudo install -d -o caddyui -g caddyui -m 0750 /var/lib/caddyui
sudo install -m 0755 caddyui /usr/local/bin/caddyui
sudo install -m 0644 packaging/systemd/caddyui.service /etc/systemd/system/caddyui.service
sudo systemctl daemon-reload
sudo systemctl enable --now caddyui
```
### Docker Build
```bash
docker build --build-arg VERSION=v1.0.0 -t caddyui:v1.0.0 .
```
### Dependencies
- [Go 1.25+](https://go.dev/)
- [Caddy 2.x](https://caddyserver.com/) with the admin API enabled (default)
- No external database required — embedded SQLite is the default; MariaDB is optional
---
## Architecture
```
cmd/caddyui/ Entry point, env config, startup
internal/
auth/ Session, password hashing, TOTP
caddy/ Admin API client, Caddyfile parser, importer
db/ SQLite/MariaDB init, migrations, and data migration
models/ Data types and DB queries
server/ HTTP handlers, routes, notifiers, health poller
web/
templates/ Go html/template pages
static/ CSS, icons, PWA manifest & service worker
```
CaddyUI stores state in SQLite by default or MariaDB when configured. It communicates with Caddy exclusively through Caddy's HTTP admin API — no SSH and no direct Caddy configuration-file manipulation.
---
## Upgrading
1. Pull the new image tag from Docker Hub.
2. Recreate the container (Portainer: **Recreate** → enable **Re-pull image**; CLI: `docker compose pull && docker compose up -d`).
3. Database migrations run automatically on startup.
4. Check the [CHANGELOG](CHANGELOG.md) for any breaking changes.
---
## AI Assistance Disclosure
This project is developed with assistance from **Claude (Anthropic)**. Claude helps with debugging, feature implementation, code review, and documentation. All code is reviewed and tested by the project maintainer before release.
Bug reports and issues are triaged by the maintainer with Claude's assistance. If you find a bug, please open an issue — it will be looked at.
> **Note on privacy:** No proprietary code, credentials, database contents, or user data are ever shared with Claude. Only code structure and logic are discussed.
---
## License
CaddyUI is open source under the [Apache License 2.0](LICENSE).
- **Free** for everyone — personal, commercial, and internal use alike.
- **Modify and redistribute** freely, including as part of your own products or hosted/managed services.
- **Patent grant** included, and your contributions are welcome under the same license.
The only conditions are the usual Apache-2.0 ones: keep the license and copyright notices, and state significant changes you make.
---
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md).
## Security
See [SECURITY.md](SECURITY.md) for how to report vulnerabilities.
## Code of Conduct
See [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md).