Caddy

Introduction

The Caddy module renders declarative sites and endpoints for public or Tailscale ingress. Service-owning modules should declare their routes; host configurations generally enable Caddy, define users and roles, and set host-specific site behavior.

options.nix is the authoritative option reference.

Requirements

Caddy uses the Podman server for its container, storage datasets for runtime state, and agenix secrets for OAuth and basic-auth credentials. Public domains must resolve to the host before ACME certificate issuance can succeed.

Endpoints

Declare endpoints under a logical site:

caddy.sites.media.endpoints.example = {
  type = "proxy";
  path = "/example";
  host = "example";
  port = 8080;
  role = "admin";
};

Proxy endpoints require host and port. Authentication defaults to OAuth; set auth = null only for an intentionally public endpoint.

Useful endpoint settings include:

  • scheme = "https" for an HTTPS upstream
  • spoofBasic = true when the upstream should receive shared web basic-auth credentials
  • headerUp for additional upstream request headers
  • stripPrefix = true or handlePath = true for applications that need path-prefix handling
  • type = "share" for a static share endpoint rather than a reverse proxy

The module validates common errors during evaluation, including incomplete proxies, missing or undeclared OAuth roles, duplicate endpoint paths within a site, and duplicate domain/listen-port pairs.

Sites And Domains

A site groups domains, endpoints, redirects, logging, and response behavior:

caddy.sites.example = {
  domains = [
    {
      host = "example.bovbel.com";
      tls = "public";
    }
  ];

  endpoints.app = {
    type = "proxy";
    path = "/";
    host = "app";
    port = 8080;
    auth = null;
  };
};

Domain TLS can be public or tailscale; listenPort can override the default listener. Prefer these declarations over hand-written Caddyfile fragments.

Persistence

The module declares storage.datasets.app.children.caddy and mounts it into the container. Preserve that dataset when rebuilding or recovering the host; authentication state and certificates must not be redirected to an ephemeral root path.

Route Audit

Caddy hosts receive a generated route audit at /etc/caddy/routes.md. Use it to inspect the effective domains, paths, authentication, and upstreams after deployment.

Troubleshooting

Inspect /etc/caddy/routes.md for the effective routes and caddy-render.service for validation failures. The rendered configuration and state live under /storage/app/caddy/{Caddyfile,data,config}; check caddy.service, apps-network.service, and the container log when those paths are present but routing fails. Authentication environment failures are reported by podman-server-caddy-token-secret-env.service or podman-server-caddy-basic-auth-env.service, while bans are managed by fail2ban.service.

Options

caddy.enable

Enable containerized Caddy proxy and auth.

Type: boolean

Default:

false

Declared by:

caddy.caddyfile

Rendered Caddyfile template derivation.

Type: package

Declared by:

caddy.cookieLifetime

OAuth cookie lifetime in seconds.

Type: positive integer, meaning >0

Default:

7884000

Declared by:

caddy.email

ACME contact email for Caddy.

Type: string

Default:

"paul@bovbel.com"

Declared by:

caddy.roles

Roles recognized by the authentication portal.

Type: list of string

Default:

[
  "admin"
  "user"
]

Declared by:

caddy.routeSummary

Markdown summary of configured Caddy sites and endpoints.

Type: package (read only)

Declared by:

caddy.share.enable

Enable share endpoint.

Type: boolean

Default:

true

Declared by:

caddy.sites

Caddy sites keyed by logical service name.

Type: attribute set of (submodule)

Default:

{ }

Example:

{
  media = {
    domains = [
      {
        host = "media.example.com";
      }
    ];
    endpoints = {
      app = {
        host = "media";
        path = "/";
        port = 8080;
        role = "admin";
        type = "proxy";
      };
    };
  };
}

Declared by:

caddy.sites.<name>.domains

Domains that expose this site.

Type: list of (submodule)

Default:

[ ]

Declared by:

caddy.sites.<name>.domains.*.host

Hostname served by this domain.

Type: string

Declared by:

caddy.sites.<name>.domains.*.listenPort

Explicit port on which Caddy listens for this domain.

Type: null or 16 bit unsigned integer; between 0 and 65535 (both inclusive)

Default:

null

Declared by:

caddy.sites.<name>.domains.*.tls

TLS certificate source for this domain.

Type: one of “public”, “tailscale”

Default:

"public"

Declared by:

caddy.sites.<name>.endpoints

Site endpoints keyed by logical name.

Type: attribute set of (submodule)

Default:

{ }

Declared by:

caddy.sites.<name>.endpoints.<name>.auth

Authentication method, or null to disable authentication.

Type: null or one of “oauth”, “basic”

Default:

"oauth"

Declared by:

caddy.sites.<name>.endpoints.<name>.handlePath

Whether to use Caddy’s handle_path directive.

Type: boolean

Default:

false

Declared by:

caddy.sites.<name>.endpoints.<name>.headerUp

Additional header_up directive arguments passed to Caddy.

Type: list of string

Default:

[ ]

Declared by:

caddy.sites.<name>.endpoints.<name>.host

Upstream host for a proxy endpoint.

Type: null or string

Default:

null

Declared by:

caddy.sites.<name>.endpoints.<name>.path

Request path matched by this endpoint.

Type: string

Declared by:

caddy.sites.<name>.endpoints.<name>.port

Upstream port for a proxy endpoint.

Type: null or 16 bit unsigned integer; between 0 and 65535 (both inclusive)

Default:

null

Declared by:

caddy.sites.<name>.endpoints.<name>.role

Role required to access this endpoint.

Type: null or string

Default:

null

Declared by:

caddy.sites.<name>.endpoints.<name>.scheme

Upstream protocol for a proxy endpoint.

Type: one of “http”, “https”

Default:

"http"

Declared by:

caddy.sites.<name>.endpoints.<name>.spoofBasic

Whether to derive an upstream basic authorization header from the authenticated user.

Type: boolean

Default:

false

Declared by:

caddy.sites.<name>.endpoints.<name>.stripPrefix

Whether to strip the matched path prefix before proxying.

Type: boolean

Default:

false

Declared by:

caddy.sites.<name>.endpoints.<name>.type

Endpoint handler type.

Type: one of “proxy”, “share”

Declared by:

caddy.sites.<name>.log

Whether to enable Caddy access logging for this site.

Type: boolean

Default:

false

Declared by:

caddy.sites.<name>.notFound

Whether unmatched requests receive a not-found response.

Type: boolean

Default:

true

Declared by:

caddy.sites.<name>.redirect

Target to which the site root redirects.

Type: null or string

Default:

null

Declared by:

caddy.sites.<name>.securityHeaders

Whether to add the standard security response headers.

Type: boolean

Default:

true

Declared by:

caddy.tokenLifetime

OAuth token lifetime in seconds.

Type: positive integer, meaning >0

Default:

7884000

Declared by:

caddy.users

Users authorized through the Caddy authentication portal.

Type: list of (submodule)

Default:

[ ]

Declared by:

caddy.users.*.email

Email address identifying the user.

Type: string

Declared by:

caddy.users.*.roles

Roles granted to the user.

Type: list of string

Default:

[ ]

Declared by: