HTTP Basic Auth That Actually Stays On

basic_auth: is enforced at the Caddy layer, persisted to state so it survives every future route rebuild — including the bug where it silently stopped applying.

6 min read v0.23.2 v0.24.3 v0.14.0

Adding basic auth

basic_auth:
  user: admin
  password: "${NEO_BASIC_AUTH_PASSWORD}"
  bypass:
    - /api/*
    - /webhooks/*

Enforced entirely at Caddy — the app container never sees it, and bypass: paths skip auth entirely (useful for a webhook endpoint that can't send a browser-style credential prompt).

Secrets via ${VAR} interpolation

The password above is resolved at deploy time from your merged environment — env:, env_file, --env, then the OS environment — so the real password lives in a gitignored env_file, not in .neo.yml itself. ${VAR:-default} works too, for a fallback when the variable isn't set.

Why it's persisted to server state

Basic auth used to live only in the live Caddy config. Running neo domain to add a second domain, or neo caddy update to refresh the proxy image, rebuilt the route from scratch — and silently dropped auth in the process, since nothing told those commands it should be there. It's now saved in /etc/neo/state.json alongside the app, and every route rebuild reapplies it from there.

The bug this tutorial exists to explain

Adding basic_auth to an app that already had a route could leave the site open. Caddy keeps route IDs unique; a plain "add" over an existing route either errored or left the old, unauthenticated route serving traffic ahead of the new one — while Neo reported success. Route creation now always replaces rather than stacks.

If you suspect a server is affected (deployed before this fix, added basic_auth to an existing app):

neo caddy routes    # AUTH column shows what's ACTUALLY being enforced
neo caddy reload --app my-app   # rebuilds the route from state — no redeploy needed

none in the AUTH column means the live route has no auth handler regardless of what .neo.yml says — neo caddy reload fixes it immediately.

Env changes apply auth too

A neo env set restart used to skip reapplying basic_auth even when the password had changed — it now re-resolves and reapplies auth on every env-triggered restart, not just on a full deploy.

Try it yourself — Neo is free, with every feature unlocked.

Get started free