authwall-proxy-caddy

Several domains behind a single Authwall. Authwall authenticates every request, then forwards it to a Caddy reverse proxy that routes each domain to its own app.

client → authwall → caddy → { apps, notes, echo }
flowchart LR
    client --> authwall --> caddy
    caddy --> apps
    caddy --> notes
    caddy --> echo

Authwall always has exactly one upstream. Here that upstream is the Caddy router, and AUTHWALL_UPSTREAM_MODE=proxy makes Authwall preserve the client's original Host header so Caddy can tell the domains apart.

Set up local hostnames

The example uses three domains, so add them to your hosts file — /etc/hosts on Linux/macOS, C:\Windows\System32\drivers\etc\hosts on Windows:

127.0.0.1 apps.mydomain.test
127.0.0.1 notes.mydomain.test
127.0.0.1 echo.mydomain.test

Run it

docker compose up

Open any of the three domains on port 3000:

Choose Sign up on any one of them and create an account. Because the session cookie is scoped to mydomain.test (AUTHWALL_COOKIE_DOMAIN), that one sign-in is valid across all three domains.

Each app is a jmalloc/echo-server that echoes the request back. The echoed Host header shows which domain reached which app — proof that Authwall preserved the Host and Caddy routed on it.

How it works

  • authwall is the only published service (port 3000); all three domains resolve to it.
  • AUTHWALL_UPSTREAM_MODE=proxy tells Authwall to keep the client's original Host header when forwarding to its single upstream, http://caddy.
  • The Caddyfile listens on plain HTTP and uses a host matcher per domain, routing each to its app (apps, notes, echo).
  • AUTHWALL_COOKIE_DOMAIN=mydomain.test shares the session across the subdomains, so users sign in once.

What to change for your app

  • Replace the apps / notes / echo services with your own apps.
  • Edit the host matchers and reverse_proxy targets in the Caddyfile to match your domains.
  • For production, give the domains real DNS records, and terminate TLS at a load balancer in front of Authwall; set AUTHWALL_PUBLIC_URL to the https:// address.

Configuration files

docker-compose.yaml

services:

  # Authwall is the entrypoint and the only published service. It authenticates
  # every request, then forwards it to its single upstream — the Caddy router.
  # AUTHWALL_UPSTREAM_MODE=proxy keeps the client's original Host header so Caddy
  # can route by domain. AUTHWALL_COOKIE_DOMAIN shares the session across the
  # subdomains, so one sign-in covers all of them.
  authwall:
    image: vbarbarosh/authwall
    restart: unless-stopped
    environment:
      AUTHWALL_PUBLIC_URL: http://apps.mydomain.test:3000
      AUTHWALL_UPSTREAM_URL: http://caddy
      AUTHWALL_UPSTREAM_MODE: proxy
      AUTHWALL_COOKIE_DOMAIN: mydomain.test
    ports:
      - 3000:3000
    volumes:
      - ./data:/app/data
    depends_on:
      - caddy

  # Reverse proxy behind Authwall. Routes each domain to its own app by Host.
  caddy:
    image: caddy:alpine
    restart: unless-stopped
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
    depends_on:
      - apps
      - notes
      - echo

  # Stand-in upstream apps, one per domain. echo-server echoes each request,
  # so the response shows which Host reached which app.
  apps:
    image: jmalloc/echo-server
    restart: unless-stopped

  notes:
    image: jmalloc/echo-server
    restart: unless-stopped

  echo:
    image: jmalloc/echo-server
    restart: unless-stopped

Caddyfile

# Reverse proxy behind Authwall.
#
# Authwall has already authenticated the request and forwarded it here with the
# client's original Host header preserved (AUTHWALL_UPSTREAM_MODE=proxy). Caddy
# matches that Host and sends each domain to its own app.
#
# This Caddy sits behind Authwall and never faces clients directly, so it
# listens on plain HTTP port 80 and needs no TLS.

:80 {
	@apps host apps.mydomain.test
	handle @apps {
		reverse_proxy apps:8080
	}

	@notes host notes.mydomain.test
	handle @notes {
		reverse_proxy notes:8080
	}

	@echo host echo.mydomain.test
	handle @echo {
		reverse_proxy echo:8080
	}
}