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 --> echoAuthwall 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.testRun it
docker compose upOpen 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
authwallis the only published service (port 3000); all three domains resolve to it.AUTHWALL_UPSTREAM_MODE=proxytells Authwall to keep the client's originalHostheader when forwarding to its single upstream,http://caddy.- The
Caddyfilelistens on plain HTTP and uses ahostmatcher per domain, routing each to its app (apps,notes,echo). AUTHWALL_COOKIE_DOMAIN=mydomain.testshares the session across the subdomains, so users sign in once.
What to change for your app
- Replace the
apps/notes/echoservices with your own apps. - Edit the
hostmatchers andreverse_proxytargets in theCaddyfileto 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_URLto thehttps://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-stoppedCaddyfile
# 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
}
}