Tools: Setting Up a Custom Caddy Reverse Proxy for OpenClaw on macOS (2026)

Tools: Setting Up a Custom Caddy Reverse Proxy for OpenClaw on macOS (2026)

Setting Up a Custom Caddy Reverse Proxy for OpenClaw on macOS

1. Installing mise

2. Installing xcaddy with mise

3. Finding Custom Caddy Plugins

Modules Directory

Download Page

4. Getting the GeoLite2 Database

5. Building Caddy with Custom Modules

6. Running Caddy as a macOS Service

Managing the Service

7. Production Caddyfile: GeoIP, Basic Auth, and Reverse Proxy

Generating a Password Hash

Example Caddyfile

How the Request Flow Works

The Webhook Exception

Quick Reference A step-by-step guide for macOS users running OpenClaw to set up a custom Caddy server as a reverse proxy — covering mise installation, building Caddy with xcaddy and custom plugins, configuring GeoIP filtering with basic auth, and running it as a launchd service. mise (formerly rtx) is a polyglot runtime manager. Install it and activate it in your shell: For other shells, see the mise documentation. xcaddy is the tool for building Caddy with custom modules (GeoIP, rate limiting, etc.). You can install it via mise's github backend, which pulls binaries directly from GitHub releases. The github backend offers: Verify the installation: Before building, you need to know which plugins are available. Caddy provides two official resources: https://caddyserver.com/docs/modules/ The complete registry of all registered Caddy modules. Each entry shows the module ID, description, and links to its source repository. Modules marked as non-standard are community plugins that need to be added via xcaddy. Use your browser's "Find in page" for quick lookups. https://caddyserver.com/download An interactive build page that lists all available plugins with checkboxes. You can: Tip: Use the modules directory to research what's available, then use the download page to either grab a pre-built binary or copy the exact xcaddy build command. The GeoIP filtering requires a MaxMind GeoLite2 ASN database file. Sign up for a free account at maxmind.com, then download the GeoLite2-ASN.mmdb file from your account dashboard under Download Databases. Tip: MaxMind also provides geoipupdate — a tool that keeps your database files up to date automatically. Install with brew install geoipupdate and configure it with your MaxMind license key. With xcaddy installed, build a custom Caddy binary with the modules you need: This produces a caddy binary in the current directory with MaxMind GeoIP support baked in. Move it to your PATH: Create a launchd plist to run Caddy automatically on boot: Caddy's basic_auth directive requires a bcrypt-hashed password. Generate one with: This will prompt you to enter and confirm a password, then output a bcrypt hash like: Use this hash in your Caddyfile's basic_auth block. Here's a real-world Caddyfile serving two sites with: The /googlechat path is handled separately with its own handle block, bypassing auth. This is essential for incoming webhooks that can't provide credentials. Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Command

Copy

$ -weight: 500;">curl https://mise.run | sh export PATH="$HOME/.local/bin:$PATH" eval "$(mise activate bash)" -weight: 500;">curl https://mise.run | sh export PATH="$HOME/.local/bin:$PATH" eval "$(mise activate bash)" -weight: 500;">curl https://mise.run | sh export PATH="$HOME/.local/bin:$PATH" eval "$(mise activate bash)" # Pin to a specific version mise use -g github:caddyserver/[email protected] # Or use the latest version mise use -g github:caddyserver/xcaddy # Pin to a specific version mise use -g github:caddyserver/[email protected] # Or use the latest version mise use -g github:caddyserver/xcaddy # Pin to a specific version mise use -g github:caddyserver/[email protected] # Or use the latest version mise use -g github:caddyserver/xcaddy xcaddy version xcaddy version xcaddy version # Place the database where your Caddyfile can reference it mv GeoLite2-ASN.mmdb ~/GeoLite2-ASN.mmdb # Place the database where your Caddyfile can reference it mv GeoLite2-ASN.mmdb ~/GeoLite2-ASN.mmdb # Place the database where your Caddyfile can reference it mv GeoLite2-ASN.mmdb ~/GeoLite2-ASN.mmdb xcaddy build v2.11.2 \ --with github.com/porech/caddy-maxmind-geolocation@v1.0.3 xcaddy build v2.11.2 \ --with github.com/porech/caddy-maxmind-geolocation@v1.0.3 xcaddy build v2.11.2 \ --with github.com/porech/caddy-maxmind-geolocation@v1.0.3 mv caddy /usr/local/bin/caddy mv caddy /usr/local/bin/caddy mv caddy /usr/local/bin/caddy tee ~/Library/LaunchAgents/com.caddyserver.caddy.plist << 'EOF' <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>com.caddyserver.caddy</string> <key>ProgramArguments</key> <array> <string>/usr/local/bin/caddy</string> <string>run</string> <string>--config</string> <string>/Users/username/.config/caddy/Caddyfile</string> </array> <key>RunAtLoad</key> <true/> <key>KeepAlive</key> <true/> <key>StandardOutPath</key> <string>/Users/username/.config/caddy/caddy-stdout.log</string> <key>StandardErrorPath</key> <string>/Users/username/.config/caddy/caddy-stderr.log</string> </dict> </plist> EOF tee ~/Library/LaunchAgents/com.caddyserver.caddy.plist << 'EOF' <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>com.caddyserver.caddy</string> <key>ProgramArguments</key> <array> <string>/usr/local/bin/caddy</string> <string>run</string> <string>--config</string> <string>/Users/username/.config/caddy/Caddyfile</string> </array> <key>RunAtLoad</key> <true/> <key>KeepAlive</key> <true/> <key>StandardOutPath</key> <string>/Users/username/.config/caddy/caddy-stdout.log</string> <key>StandardErrorPath</key> <string>/Users/username/.config/caddy/caddy-stderr.log</string> </dict> </plist> EOF tee ~/Library/LaunchAgents/com.caddyserver.caddy.plist << 'EOF' <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>com.caddyserver.caddy</string> <key>ProgramArguments</key> <array> <string>/usr/local/bin/caddy</string> <string>run</string> <string>--config</string> <string>/Users/username/.config/caddy/Caddyfile</string> </array> <key>RunAtLoad</key> <true/> <key>KeepAlive</key> <true/> <key>StandardOutPath</key> <string>/Users/username/.config/caddy/caddy-stdout.log</string> <key>StandardErrorPath</key> <string>/Users/username/.config/caddy/caddy-stderr.log</string> </dict> </plist> EOF # Start Caddy -weight: 500;">service launchctl load -w ~/Library/LaunchAgents/com.caddyserver.caddy.plist # Stop Caddy -weight: 500;">service launchctl unload -w ~/Library/LaunchAgents/com.caddyserver.caddy.plist # Reload config without restarting the -weight: 500;">service caddy reload --config ~/.config/caddy/Caddyfile # Start Caddy -weight: 500;">service launchctl load -w ~/Library/LaunchAgents/com.caddyserver.caddy.plist # Stop Caddy -weight: 500;">service launchctl unload -w ~/Library/LaunchAgents/com.caddyserver.caddy.plist # Reload config without restarting the -weight: 500;">service caddy reload --config ~/.config/caddy/Caddyfile # Start Caddy -weight: 500;">service launchctl load -w ~/Library/LaunchAgents/com.caddyserver.caddy.plist # Stop Caddy -weight: 500;">service launchctl unload -w ~/Library/LaunchAgents/com.caddyserver.caddy.plist # Reload config without restarting the -weight: 500;">service caddy reload --config ~/.config/caddy/Caddyfile caddy hash-password caddy hash-password caddy hash-password $2a$14$Mpdg/pw/CBar4/YMFRcwbuN2gWsOQgaCowWDHPHsc402vF18TRGRK $2a$14$Mpdg/pw/CBar4/YMFRcwbuN2gWsOQgaCowWDHPHsc402vF18TRGRK $2a$14$Mpdg/pw/CBar4/YMFRcwbuN2gWsOQgaCowWDHPHsc402vF18TRGRK site1.example.com { log { output file /Users/username/.config/caddy/site1.example.com-caddy.log } @allowed { maxmind_geolocation { db_path "/Users/username/GeoLite2-ASN.mmdb" allow_asn 48323 21497 59497 58309 } } handle /googlechat { reverse_proxy 192.168.111.98:18789 } handle { basic_auth @allowed { admin $2a$14$... } reverse_proxy @allowed 192.168.111.98:18789 respond 501 } } site2.example.com { log { output file /Users/username/.config/caddy/site2.example.com-caddy.log } @allowed { maxmind_geolocation { db_path "/Users/username/GeoLite2-ASN.mmdb" allow_asn 48323 21497 59497 58309 } } basic_auth @allowed { admin $2a$14$... } reverse_proxy @allowed 127.0.0.1:3644 respond 501 } site1.example.com { log { output file /Users/username/.config/caddy/site1.example.com-caddy.log } @allowed { maxmind_geolocation { db_path "/Users/username/GeoLite2-ASN.mmdb" allow_asn 48323 21497 59497 58309 } } handle /googlechat { reverse_proxy 192.168.111.98:18789 } handle { basic_auth @allowed { admin $2a$14$... } reverse_proxy @allowed 192.168.111.98:18789 respond 501 } } site2.example.com { log { output file /Users/username/.config/caddy/site2.example.com-caddy.log } @allowed { maxmind_geolocation { db_path "/Users/username/GeoLite2-ASN.mmdb" allow_asn 48323 21497 59497 58309 } } basic_auth @allowed { admin $2a$14$... } reverse_proxy @allowed 127.0.0.1:3644 respond 501 } site1.example.com { log { output file /Users/username/.config/caddy/site1.example.com-caddy.log } @allowed { maxmind_geolocation { db_path "/Users/username/GeoLite2-ASN.mmdb" allow_asn 48323 21497 59497 58309 } } handle /googlechat { reverse_proxy 192.168.111.98:18789 } handle { basic_auth @allowed { admin $2a$14$... } reverse_proxy @allowed 192.168.111.98:18789 respond 501 } } site2.example.com { log { output file /Users/username/.config/caddy/site2.example.com-caddy.log } @allowed { maxmind_geolocation { db_path "/Users/username/GeoLite2-ASN.mmdb" allow_asn 48323 21497 59497 58309 } } basic_auth @allowed { admin $2a$14$... } reverse_proxy @allowed 127.0.0.1:3644 respond 501 } # Install mise -weight: 500;">curl https://mise.run | sh export PATH="$HOME/.local/bin:$PATH" eval "$(mise activate bash)" # Install xcaddy mise use -g github:caddyserver/[email protected] # Build custom Caddy xcaddy build v2.11.2 \ --with github.com/porech/caddy-maxmind-geolocation@v1.0.3 # Install the binary mv caddy /usr/local/bin/caddy # Generate password hash for basic_auth caddy hash-password # macOS -weight: 500;">service management launchctl load -w ~/Library/LaunchAgents/com.caddyserver.caddy.plist launchctl unload -w ~/Library/LaunchAgents/com.caddyserver.caddy.plist # Manage Caddyfile caddy validate --config ~/.config/caddy/Caddyfile caddy reload --config ~/.config/caddy/Caddyfile caddy fmt --overwrite ~/.config/caddy/Caddyfile # Install mise -weight: 500;">curl https://mise.run | sh export PATH="$HOME/.local/bin:$PATH" eval "$(mise activate bash)" # Install xcaddy mise use -g github:caddyserver/[email protected] # Build custom Caddy xcaddy build v2.11.2 \ --with github.com/porech/caddy-maxmind-geolocation@v1.0.3 # Install the binary mv caddy /usr/local/bin/caddy # Generate password hash for basic_auth caddy hash-password # macOS -weight: 500;">service management launchctl load -w ~/Library/LaunchAgents/com.caddyserver.caddy.plist launchctl unload -w ~/Library/LaunchAgents/com.caddyserver.caddy.plist # Manage Caddyfile caddy validate --config ~/.config/caddy/Caddyfile caddy reload --config ~/.config/caddy/Caddyfile caddy fmt --overwrite ~/.config/caddy/Caddyfile # Install mise -weight: 500;">curl https://mise.run | sh export PATH="$HOME/.local/bin:$PATH" eval "$(mise activate bash)" # Install xcaddy mise use -g github:caddyserver/[email protected] # Build custom Caddy xcaddy build v2.11.2 \ --with github.com/porech/caddy-maxmind-geolocation@v1.0.3 # Install the binary mv caddy /usr/local/bin/caddy # Generate password hash for basic_auth caddy hash-password # macOS -weight: 500;">service management launchctl load -w ~/Library/LaunchAgents/com.caddyserver.caddy.plist launchctl unload -w ~/Library/LaunchAgents/com.caddyserver.caddy.plist # Manage Caddyfile caddy validate --config ~/.config/caddy/Caddyfile caddy reload --config ~/.config/caddy/Caddyfile caddy fmt --overwrite ~/.config/caddy/Caddyfile - Provenance verification — validates release authenticity - Download progress reports — visual feedback during -weight: 500;">install - Browse and search all available plugins by name or category - Select plugins you need and download a pre-built binary directly - Copy the xcaddy build command with all your selected --with flags - MaxMind GeoIP ASN filtering — only allow traffic from specific ISPs/networks - Basic authentication — password-protect the proxy - Reverse proxying — forward traffic to backend services - Separate handling for webhook endpoints (no auth required) - GeoIP check (@allowed matcher) — only ASNs 48323, 21497, 59497, and 58309 can proceed past basic auth. All others get the respond 501. - Basic auth — only requests matching @allowed are prompted for credentials. - Reverse proxy — authenticated + allowed requests are forwarded to the backend. - Fallback — anything that doesn't match gets a 501 response. - mise documentation - Caddy modules directory - Caddy download / plugin picker