ecaddy Docs Troubleshooting

Troubleshooting

Reference for diagnosing problems: the filesystem layout, conflict detection rules, the doctor / audit commands, common failure modes, and how to override paths for testing.

Global filesystem layout

Every path ecaddy touches derives from ECADDY_HOME, which defaults to ~/.config/caddy.

~/.config/caddy/
  Caddyfile           # global root: { admin ... } + import sites/*.caddy
  ecaddy.yml          # registry: name → { enabled, source_path }
  sites/
    fishme.caddy      # enabled fragments — loaded by Caddy
    letly.caddy
  disabled/
    traiderb.caddy    # disabled fragments — preserved, not loaded

The global Caddyfile is also symlinked at /opt/homebrew/etc/Caddyfile so brew services start caddy picks it up automatically.

The registry

ecaddy.yml is a flat YAML hash keyed by site name. It only stores metadata — no Caddyfile content, no port/domain values:

fishme:
  name: fishme
  enabled: true
  source_path: /projects/fishme/Caddyfile
letly:
  name: letly
  enabled: false
  source_path: /projects/letly/Caddyfile

If you delete ecaddy.yml, the fragments stay on disk but ecaddy "forgets" them — list shows an empty registry, and the source-path linkage is lost. To recover, re-run ecaddy run or ecaddy ensure from each project.

Overriding ECADDY_HOME

Set ECADDY_HOME to redirect every filesystem operation. The default is ~/.config/caddy; an override is useful for testing, multi-account setups, or sandboxing experiments.

ECADDY_HOME=/tmp/ecaddy_test ecaddy list
# → reads /tmp/ecaddy_test/ecaddy.yml, never touches your real config
What about the brew symlink? ECADDY_HOME controls the source-of-truth Caddyfile location, but brew services still loads /opt/homebrew/etc/Caddyfile. If you redirect ECADDY_HOME, the running Caddy service won't pick up your sandboxed config unless you also re-symlink. For testing, this is fine — the specs do exactly this and never invoke brew services.

Conflict detection

Before registering any Caddyfile, ecaddy parses it (regex-based) and checks the existing fragments for collisions:

  • Domain collision — the same *.localhost domain is already registered by another enabled site → BLOCK
  • Port collision — the same reverse_proxy localhost:PORT is already in use by another site → BLOCK

These checks run on ecaddy run, ecaddy ensure, and ecaddy up. The check reads fragments off disk, not the registry, so it's accurate even if ecaddy.yml is out of sync.

There is no bypass

If you genuinely need two projects to share a domain (rare), pick a different site name on one of them and adjust the localhost subdomain in its Caddyfile. ecaddy does not have a --force flag for conflict detection on registration — the cost of two fragments fighting for the same port is silent breakage at request time, which is worse than a clear BLOCK.

ecaddy doctor — cross-site audit

Where the per-register check looks at the new fragment vs. existing ones, doctor scans all registered sites cross-wise and TCP-probes every upstream port.

ecaddy doctor
SeverityTriggerAction
BLOCKTwo sites share a port or domainExit 1. Fix one of the fragments and re-run.
WARNPort bound by something other than the registered upstreamExit 0. Often means an old dev server is still alive — kill the orphan.
INFOUpstream not listening on the expected portExit 0. Usually just means the project isn't running.

ecaddy audit — system + TLS

audit goes wider than doctor. It probes:

  • Whether brew services reports Caddy as running
  • Whether a Caddy process is alive but unmanaged by brew (a common drift)
  • TLS handshake on every *.localhost domain (looks for unknown CA, alert 80, connection refused)
  • Whether the local CA is actually in your system keychain (browser-trust check)
ecaddy audit                # report-only
ecaddy audit --fix          # walk findings, prompt to apply each fix
ecaddy audit --site fishme  # limit to one site

In --fix mode, each finding ships with a proposed command and a verifier. If the primary fix doesn't resolve the finding (verifier still fails), audit chains to a next_fix — e.g. caddy trustsudo caddy trust — until something verifies or the chain runs out.

Common failures and fixes

"Caddy service: stopped" but a caddy process is running

An old caddy process is alive but brew services has lost track of it (often after a system update or a manual caddy stop). audit detects this and proposes:

pkill -x caddy && brew services start caddy

Browser shows ERR_CERT_AUTHORITY_INVALID on https://*.localhost

The local CA isn't trusted by your system keychain yet. caddy trust seeds it; the first attempt typically needs root:

caddy trust
# → if that fails:
sudo caddy trust

This is what ecaddy setup runs for you on first install — if you skipped that step, run it now.

Rails returns "Blocked host: fishme.localhost"

Rails rejects unfamiliar hostnames by default. Allow the pattern:

# config/environments/development.rb

config.hosts << /.*\.localhost/

Caddy logs to the wrong directory

If your project Caddyfile says output file log/caddy.log, ecaddy rewrites that to an absolute path (/projects/fishme/log/caddy.log) before installing the fragment. If you see a relative path in ~/.config/caddy/sites/fishme.caddy, you've edited the installed fragment directly — re-run ecaddy ensure or ecaddy run from your project root to restore the rewrite.

Two projects accidentally pick the same app port

ecaddy run will BLOCK with a clear message. Either change the reverse_proxy localhost:PORT in one Caddyfile (and the matching Rails -p flag) or pick a different port range per project. See the table in Getting Started for a common allocation pattern.

Local edits keep getting overwritten

ecaddy edit fishme opens ~/.config/caddy/sites/fishme.caddy — the installed fragment. Any time you re-run ecaddy run or ecaddy ensure from the project, that fragment is overwritten with the project Caddyfile (plus log-path rewriting). For lasting changes, edit the project's Caddyfile, not the installed fragment.

Forcing a clean reset

If the global config gets into a bad state, the safest reset is:

brew services stop caddy
rm -rf ~/.config/caddy
ecaddy setup
# then re-register each project from its directory:
cd /projects/fishme && ecaddy ensure -c ./Caddyfile -s fishme
cd /projects/letly  && ecaddy ensure -c ./Caddyfile -s letly

This loses no project source — it only discards the installed fragments and the registry, both of which are re-derivable from each project's Caddyfile.

Getting help

ecaddy v0.1.0 · MIT License · GitHub