Skip to content

Troubleshooting

X4Applegate edited this page Sep 10, 2026 · 1 revision

Troubleshooting

My domain will not get a certificate

Check, in order:

  1. The DNS A/AAAA record points at this server's public IP.
  2. Ports 80 and 443 are open on the firewall and forwarded to Caddy. HTTP-01 needs 80.
  3. You are not rate-limited by Let's Encrypt (five failures per account per hostname per hour). The Certificates page shows the node's latest ACME message; Observe → Server Logs shows the full exchange.
  4. For wildcards or internal-only hosts use DNS-01 with a provider profile, and make sure your Caddy build includes the provider module (Dockerfile.caddy does). See Certificates.

Certificate issued, but 502 Bad Gateway

TLS worked; Caddy cannot reach the upstream. Almost always a Docker network mismatch: if the log says lookup name on 127.0.0.11:53: server misbehaving, the upstream container is not on the same Docker network as Caddy. Full fix in Docker Networking. Also check that Forward port is the port inside the container, not a published host port, and use Test upstream on the form.

I changed a rule and the site still behaves the old way

Hard-refresh (Ctrl/Cmd+Shift+R). Browsers, CDNs and service workers cache aggressively, and 301 redirects are cached indefinitely. On the Caddy side the config changes on save with no restart; Caddy Config in the sidebar shows what is live.

Saving fails: "Advanced config rejected by Caddy"

The message names the directive. Common cases:

  • unrecognized directive: flush_interval — a reverse_proxy sub-directive typed bare. Since v2.42.1 CaddyUI wraps it in a reverse_proxy { … } block for you; on older versions put it inside one, or use the form option (Streaming → Flush immediately).
  • unrecognized subdirective read_buffer_size — JSON field names inside a transport block. Caddyfile spellings are read_buffer, write_buffer, max_response_header; since v2.42.2 CaddyUI respells them and moves transport options into transport http { … }.
  • A block that names an upstream or sits behind a matcher: use path-based upstream overrides or an Advanced Routes.

Saving fails: "Caddy cannot open /path for certificate"

A file-path certificate names files inside the Caddy container. The files exist on the host, but Caddy cannot see them: mount the directory into the Caddy container, or paste the PEM instead. Since v2.42.1 this is refused at save time; before that the host was saved and every later sync failed quietly.

An amber "sync failed" or "rolled back" banner

  • Sync failed — Caddy rejected the last sync; the banner carries Caddy's error. Fix the resource it names (or the port conflict, or the missing module) and press Retry sync now.
  • Rolled back — an expectation failed after a sync, the previous config was restored and automatic syncs for that server are paused. Re-apply now pushes the new config again (after you fixed things), Keep the rolled-back config ends the hold. Details on the host's Health page. See Proxy Hosts.

Security headers are not being sent

Upgrade to v2.45.2 or later. Earlier versions built the Security Headers bundle as "delete then set", and Caddy applies deletes after sets, so hosts with the bundle enabled served no Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options or Referrer-Policy at all (Nextcloud's setup check flags all four). Fixed on the next sync after upgrading; no form changes needed.

Analytics, certificate status or Server Logs are empty for a remote node

The node cannot reach the log ingest target. The fleet-wide default is caddyui:9019, a Docker service name only resolvable on CaddyUI's own network. Give the node its own Log ingest target on its Caddy Fleet entry (the CaddyUI host as that node sees it, e.g. 10.8.0.1:9019), make sure port 9019 is open on that path, and check the node's Caddy log for dial errors. The Caddy Fleet list warns about bare service names on remote nodes. See Observability.

Pages are slow or the database is huge

On SQLite CaddyUI uses a single database connection, so a long write blocks every page. Two known causes:

  • Analytics history. Before v2.43.0 raw events were never pruned; a busy install could reach tens of gigabytes. Upgrade, set Settings → Analytics → Keep raw events for N days, and let the background prune work through the backlog (v2.43.1 makes it small-batch so pages stay responsive), then Reclaim space.
  • Slow storage. SQLite on NFS or SMB is slow and lock-prone. Put the data volume on local disk, or move to MariaDB (Installation).

"attempt to write a readonly database" / "database is locked"

SQLite only. Readonly: the data directory is not writable by the container's user (uid 10001). chown -R 10001:10001 /path/to/caddyui_data and add user: "10001:10001" to the compose service. Locked: transient; CaddyUI uses WAL mode and a busy timeout. Constant lock errors mean very slow storage. On MariaDB, connection and permission errors appear in the container log: check CADDYUI_DB_DSN, reachability and the user's grants.

Caddy forgets everything after a restart

Caddy started without --resume, so it loaded its Caddyfile and discarded what the admin API had pushed. Use the command from Installation (caddy run --config /config/caddy/autosave.json --resume --adapter json), and set CADDYUI_SYNC_ON_START=1 so CaddyUI re-pushes on its own start-up too.

Is CaddyUI safe to expose on the internet?

Yes, behind HTTPS (Caddy can proxy to it) with 2FA and a login CAPTCHA enabled, ideally with the admin IP allowlist. The Caddy admin API on port 2019 must never be public.

Securing port 2019 without a VPN

Unix socket (simplest, local only). Caddyfile:

{
    admin unix//run/caddy/caddy-admin.sock
}

Compose, sharing the socket directory:

services:
  caddy:
    volumes:
      - caddy_admin:/run/caddy
  caddyui:
    volumes:
      - caddy_admin:/run/caddy
    environment:
      CADDY_ADMIN_URL: unix:///run/caddy/caddy-admin.sock

volumes:
  caddy_admin:

Reverse proxy with Basic Auth. A second Caddy block terminates TLS and proxies to the admin API:

admin.example.com {
    basic_auth {
        caddyui $2a$14$…bcrypt-hash…
    }
    reverse_proxy localhost:2019
}

Generate the hash with caddy hash-password, then add the server in CaddyUI with Admin URL https://admin.example.com, username caddyui and the plaintext password.

Firewall allowlist (belt and braces). Replace 203.0.113.5 with CaddyUI's address:

sudo ufw allow from 203.0.113.5 to any port 2019 proto tcp
sudo ufw deny 2019/tcp
sudo ufw reload
sudo iptables -A INPUT -p tcp --dport 2019 -s 203.0.113.5 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 2019 -j DROP
sudo netfilter-persistent save

Docker bypasses UFW with its own iptables rules, so when Caddy runs in Docker either bind the admin port to a private interface ("10.8.0.1:2019:2019") or do not publish it at all and reach it as http://caddy:2019 over the Compose network. Verify from another machine: curl -m 5 http://<host>:2019/config/ should hang or be refused.

Backing up and restoring

See Snapshots and Backup. Short version: config snapshots restore Caddy's live config in one click; the SQLite backup download restores the whole CaddyUI state by replacing /data/caddyui.db with the container stopped.

Still stuck?

Open an issue with the CaddyUI version from the sidebar, the Caddy version from Caddy Fleet, and the exact message from the banner or the container log.

Clone this wiki locally