A production-ready Caddy web server setup with automatic HTTPS, DNS-01 challenge support, comprehensive bot protection, CAPTCHA-free spam protection, and advanced security features.
- 🔒 Automatic HTTPS with Let's Encrypt
- 🌐 DNS-01 challenge support (CloudNS)
- 🛡️ Built-in bot protection and rate limiting
- 🤖 ALTCHA - Privacy-friendly CAPTCHA alternative
- 🔐 Caddy-Defender - Advanced threat detection and mitigation
- 🚀 Easy deployment with Docker
- 📝 Example configurations included
- 🔧 30+ reusable configuration snippets
- caddy-dns/cloudns: DNS-01 challenge provider for CloudNS
- caddy-ratelimit: Advanced rate limiting and traffic control
- caddy-altcha: CAPTCHA-free spam protection using cryptographic challenges
- caddy-defender: Blocks AI bots and cloud services from training on your content
Note: Plugin versions are pinned in the Dockerfile for reproducibility. Update the version tags periodically to get security fixes and new features. See each plugin's GitHub releases page for available versions.
-
Set your environment variables:
export ACME_EMAIL=your-email@example.com export CLOUDNS_AUTH_ID=your-auth-id export CLOUDNS_AUTH_PASSWORD=your-auth-password # Required if using ALTCHA spam protection export ALTCHA_HMAC_KEY=$(openssl rand -base64 32)
-
Copy and customize your site configuration:
cp /usr/local/share/caddy/caddy.example /sites/mysite.caddy # Edit /sites/mysite.caddy with your domains and settings -
Build and run:
docker build -t caddy-custom . docker run -d \ -p 80:80 \ -p 443:443 \ -e ACME_EMAIL=${ACME_EMAIL} \ -e CLOUDNS_AUTH_ID=${CLOUDNS_AUTH_ID} \ -e CLOUDNS_AUTH_PASSWORD=${CLOUDNS_AUTH_PASSWORD} \ -e ALTCHA_HMAC_KEY=${ALTCHA_HMAC_KEY} \ -v $(pwd)/sites:/sites \ -v caddy_data:/data \ -v caddy_config:/config \ caddy-custom
Tip: If your site configurations are managed via Git or another version control system, you can mount /sites as read-only to prevent accidental modifications:
-v $(pwd)/sites:/sites:roThe setup includes comprehensive bot protection features. See sites/bot-protection.example for detailed examples.
- Bad Bot Blocking: Blocks known malicious bots and scrapers
- Good Bot Whitelisting: Allows legitimate search engine bots- Rate Limiting: Configurable per-zone rate limits
- Exploit Path Protection: Blocks common attack vectors
- Suspicious Request Filtering: Blocks requests without proper headers
Different rate limits for different use cases:
- General: 300 requests/minute per IP
- API: 100 requests/minute per IP
- Login: 5 requests/5 minutes per IP
- Checkout: 10 requests/minute per IP
- Products: 200 requests/minute per IP
your-domain.com {
# Import bot protection snippet
import bot_protection
# Apply general rate limiting
rate_limit general
# Your application
reverse_proxy http://localhost:8080
}Edit the blocked bots list in bot-protection.example:
(bot_protection) {
@bad_bots {
header User-Agent *YourBotToBlock*
}
handle @bad_bots {
abort
}
}Adjust rate limits for your needs:
rate_limit {
zone custom {
key {remote_host}
events 500 # number of requests
window 1m # time window
}
}ALTCHA provides privacy-friendly spam protection using cryptographic proof-of-work challenges instead of traditional CAPTCHAs. No user tracking, no privacy concerns, no accessibility issues.
See sites/altcha.example for detailed configuration examples.
- Privacy-Friendly: No cookies, no tracking, no third-party services
- Accessible: No image recognition or audio challenges
- Lightweight: Pure cryptographic challenge-response
- Customizable: Adjustable difficulty levels for different use cases
- Session Support: Memory, Redis, or file-based session backends
- POST Preservation: Can restore POST data after verification
-
Generate an HMAC key (required):
export ALTCHA_HMAC_KEY=$(openssl rand -base64 32)
Important: ALTCHA configurations will fail if
ALTCHA_HMAC_KEYis not set. Always ensure this environment variable is defined before starting Caddy. -
Set the order directive and use ALTCHA handlers:
form.example.com { import cloudns_dns # REQUIRED: Set order for altcha_verify to run before reverse_proxy order altcha_verify before reverse_proxy # Challenge generation endpoint import altcha_challenge_basic # Challenge UI page import altcha_challenge_page # Protect form submissions @forms { path /contact /submit method POST } altcha_verify @forms { hmac_key {env.ALTCHA_HMAC_KEY} session_backend memory:// challenge_redirect /captcha preserve_post_data true } reverse_proxy http://localhost:8080 }
altcha_challenge_basic: Challenge endpoint with 100k max_number (~20ms solve time)altcha_challenge_strict: Strict challenge with 1M max_number (~200ms solve time)altcha_challenge_page: Serves ALTCHA widget HTML at /captchaaltcha_verify_memory: Verification with memory:// session backendaltcha_verify_redis: Verification with redis://localhost:6379/0 backend
ALTCHA requires two handlers:
-
Challenge Generation (
altcha_challenge): Creates cryptographic challenges- Route:
/api/altcha/challenge - Returns JSON with challenge data
- Configure:
hmac_key,algorithm,max_number,expires
- Route:
-
Verification (
altcha_verify): Validates solutions- Applied to protected routes via matchers
- Configure:
hmac_key,session_backend,challenge_redirect,preserve_post_data
contact.example.com {
import cloudns_dns
order altcha_verify before reverse_proxy
import security_headers
# Challenge endpoint (strict protection)
import altcha_challenge_strict
# Challenge UI page
import altcha_challenge_page
# Protect contact form with Redis backend
@contact {
path /contact/submit
method POST
}
altcha_verify @contact {
hmac_key {env.ALTCHA_HMAC_KEY}
session_backend redis://localhost:6379/0
challenge_redirect /captcha
preserve_post_data true
}
# Rate limit form submissions
@contact_rate path /contact/submit
handle @contact_rate {
import rate_limit_contact
reverse_proxy http://localhost:8080
}
reverse_proxy http://localhost:8080
}Caddy-Defender blocks AI training bots and cloud scrapers that ignore robots.txt. It uses IP range lists to identify known AI bot networks (OpenAI, DeepSeek, AWS, etc.).
Purpose: This plugin protects your content from AI training scrapers. It is NOT for general security, malware, or DDoS protection.
See sites/defender.example for comprehensive examples.
- AI Bot Detection: Blocks OpenAI, DeepSeek, GitHub Copilot, and other AI scrapers
- Cloud Provider Ranges: AWS, Google Cloud, Azure IP ranges
- Multiple Actions: Block, garbage data, redirect, tarpit, or drop connections
- Custom Ranges: Add your own IP ranges
- robots.txt Enforcement: Technical enforcement of robots.txt policy
blog.example.com {
import cloudns_dns
import security_headers
# Block all major AI training bots
import defender_block_ai
reverse_proxy http://localhost:8080
}block: Return 403 Forbidden with optional messagegarbage: Return random garbage data to pollute AI trainingredirect: HTTP 302 redirect to specified URLtarpit: Slow response to waste bot resourcesdrop: Silently drop connection (no response)
defender_block_ai: Block all AI bots (OpenAI, DeepSeek, GitHub Copilot, AWS, GCloud, Azure)defender_block_all_ai: Same as defender_block_ai with explicit rangesdefender_garbage_ai: Return garbage data to poison AI trainingdefender_redirect_ai: Redirect AI bots to /ai-bot-notice pagedefender_tarpit_ai: Slow down AI bot requestsdefender_drop_ai: Silently drop AI bot connections
Built-in ranges (updated regularly):
openai- OpenAI GPT crawlersdeepseek- DeepSeek AI training botsgithubcopilot- GitHub Copilot training sourcesaws- Amazon Web Servicesgcloud- Google Cloud Platformazurepubliccloud- Microsoft Azure
premium.example.com {
import cloudns_dns
import security_headers
# Block AI bots on premium content only
@premium {
path /premium/* /members/*
}
handle @premium {
defender block {
ranges openai deepseek githubcopilot aws gcloud azurepubliccloud
message "AI training bots not permitted on premium content."
}
reverse_proxy http://localhost:8080
}
# Allow public content
handle {
reverse_proxy http://localhost:8080
}
}Different actions for different bots:
creative.example.com {
import cloudns_dns
import security_headers
# Block specific AI companies
defender block {
ranges openai deepseek
message "OpenAI and DeepSeek bots are not permitted."
}
# Return garbage to GitHub Copilot
defender garbage {
ranges githubcopilot
}
# Tarpit cloud providers
defender tarpit {
ranges aws gcloud azurepubliccloud
}
reverse_proxy http://localhost:8080
}For maximum protection, combine multiple security features:
ultra-secure.example.com {
import cloudns_dns
import security_headers_strict
order altcha_verify before reverse_proxy
# Layer 1: Block AI training bots
import defender_block_ai
# Layer 2: Block malicious bots
import bot_protection
import exploit_protection
# Layer 3: ALTCHA challenge endpoint
import altcha_challenge_strict
import altcha_challenge_page
# Layer 4: ALTCHA verification on forms
@forms {
path /contact /register /reset-password
method POST
}
altcha_verify @forms {
hmac_key {env.ALTCHA_HMAC_KEY}
session_backend redis://localhost:6379/0
challenge_redirect /captcha
preserve_post_data true
}
# Layer 5: Rate limiting on form submissions
@forms_rate {
path /contact /register /reset-password
method POST
}
handle @forms_rate {
import rate_limit_forms
reverse_proxy http://localhost:8080
}
# Layer 6: General rate limiting
import rate_limit_general
reverse_proxy http://localhost:8080
}The setup includes 30+ prebuilt snippets in _snippets.inc and prebuilt matchers in _matchers.inc.
Note: Snippets are globally imported in the main Caddyfile from /usr/local/share/caddy/, so you can use them directly in any .caddy file. However, matchers must be imported within individual site blocks due to Caddy's scoping rules.
Simply use any snippet with the import directive in your .caddy files:
your-domain.com {
import cloudns_dns
import security_headers
import bot_protection
import rate_limit_general
reverse_proxy http://localhost:8080
}security_headers- Standard security headerssecurity_headers_strict- Strict security with CSPbot_protection- Block malicious botsbot_protection_strict- Strict bot filteringexploit_protection- Block common attack paths
rate_limit_general- 300 req/minrate_limit_api- 100 req/minrate_limit_login- 5 req/5minrate_limit_strict- 50 req/minrate_limit_lenient- 1000 req/minrate_limit_checkout- 10 req/5min (for payment/checkout pages)rate_limit_forms- 5 req/5min (for form submissions)rate_limit_contact- 5 req/5min (for contact forms)rate_limit_admin_login- 3 req/10min (for admin login attempts)
cors_permissive- Allow all originscors_restricted- Same-origin only
altcha_challenge_basic- Basic challenge endpoint (100k max_number, ~20ms)altcha_challenge_strict- Strict challenge endpoint (1M max_number, ~200ms)altcha_challenge_page- Serves ALTCHA widget HTML at /captchaaltcha_verify_memory- Verification with memory:// session backendaltcha_verify_redis- Verification with Redis session backend
defender_block_ai- Block all AI bots with 403defender_block_all_ai- Same as defender_block_ai (explicit ranges)defender_garbage_ai- Return garbage data to poison AI trainingdefender_redirect_ai- Redirect AI bots to /ai-bot-noticedefender_tarpit_ai- Slow down AI bot requestsdefender_drop_ai- Silently drop AI bot connections
www_to_apex- Redirect www to apex domainapex_to_www- Redirect apex to wwwforce_https- Force HTTPS
cloudns_dns- CloudNS DNS-01 configurationcompression- Gzip/Zstd compressionstatic_cache- Aggressive static asset cachingstatic_cache_moderate- Moderate cachinglog_common- JSON loggingfile_server_defaults- File server configurationreverse_proxy_defaults- Common proxy headers
Prebuilt matchers in _matchers.inc (optional - import in site blocks if needed):
- Methods:
@safe_methods,@unsafe_methods - Content:
@api_request,@form_submit - Devices:
@mobile,@desktop - Paths:
@admin,@api,@static_assets,@images,@media,@documents - Security:
@no_referer,@no_user_agent,@local,@external - Bots:
@search_engines,@social_media_bots,@monitoring_bots
If you want to use the prebuilt matchers, import them inside your site block:
example.com {
# Import matchers inside the site block
import /usr/local/share/caddy/_matchers.inc
import cloudns_dns
import security_headers
# Now you can use the matchers
handle @admin {
import rate_limit_strict
reverse_proxy http://localhost:9000
}
handle @static_assets {
import static_cache
reverse_proxy http://localhost:8080
}
}example.com {
import cloudns_dns
import security_headers
import compression
# Block bots and exploits
import bot_protection
import exploit_protection
# Different rate limits for different paths
handle @admin {
import rate_limit_strict
reverse_proxy http://localhost:9000
}
handle @api {
import cors_permissive
import rate_limit_api
reverse_proxy http://localhost:3000
}
# Static assets with caching
handle @static_assets {
import static_cache
reverse_proxy http://localhost:8080
}
# Default
import rate_limit_general
reverse_proxy http://localhost:8080
}See sites/example-with-snippets.caddy for complete working examples.
- caddy.example: Basic reverse proxy with redirects
- redirect.example: Simple domain redirects
- bot-protection.example: Comprehensive bot protection examples
- altcha.example: ALTCHA integration examples for spam protection
- defender.example: Caddy-Defender advanced security examples
- example-with-snippets.caddy: Complete examples using reusable snippets
- _snippets.inc: Reusable configuration snippets (import this file!)
- _matchers.inc: Prebuilt request matchers
| Variable | Description | Required |
|---|---|---|
ACME_EMAIL |
Email for Let's Encrypt | Yes |
CLOUDNS_AUTH_ID |
CloudNS authentication ID | Yes (if using DNS-01) |
CLOUDNS_AUTH_PASSWORD |
CloudNS authentication password | Yes (if using DNS-01) |
ALTCHA_HMAC_KEY |
HMAC key for ALTCHA (generate with openssl rand -hex 32) |
Yes (if using ALTCHA) |
/
├── Caddyfile # Main Caddy configuration
├── Dockerfile # Multi-stage build with plugins
├── docker-entrypoint.sh # Startup script
├── sites/ # Site configurations (source)
│ ├── _snippets.inc # 30+ reusable configuration snippets
│ ├── _matchers.inc # Prebuilt request matchers
│ ├── caddy.example # Basic reverse proxy example
│ ├── redirect.example # Simple redirects example
│ ├── bot-protection.example # Bot protection examples
│ ├── altcha.example # ALTCHA spam protection examples
│ ├── defender.example # Caddy-Defender security examples
│ └── example-with-snippets.caddy.example # Examples using snippets
└── README.md
Inside the running container:
/etc/caddy/Caddyfile- Main configuration/usr/local/share/caddy/- Shared resources (snippets, matchers, examples)/sites/- Your site configurations (mount your configs here)/data/- Caddy data (certificates, etc.)/config/- Caddy config cache
This project includes a VS Code devcontainer for easy development. See .devcontainer/README.md for detailed instructions.
- Open in VS Code: Open this project in VS Code with the Dev Containers extension installed
- Reopen in Container: Press
F1→ "Dev Containers: Reopen in Container" - Start Developing: All required tools and extensions are pre-installed
# Build the custom Caddy image
docker build -t caddy-custom .
# Run with your configurations
docker run -d \
-p 80:80 \
-p 443:443 \
-v $PWD/sites:/sites:ro \
-e ACME_EMAIL=your@email.com \
caddy-custom# Validate Caddy configuration
docker run --rm -v $PWD/sites:/sites caddy-custom caddy validate --config /etc/caddy/Caddyfile
# Check for syntax errors
docker run --rm -v $PWD/sites:/sites caddy-custom caddy fmt --overwrite /sites/yoursite.caddy- Always use HTTPS: The setup automatically provisions SSL certificates
- Rate Limiting: Use appropriate rate limits for your traffic patterns
- Monitor Logs: Check Caddy logs for blocked requests
- Update Regularly: Keep Caddy and plugins up to date
- Whitelist Carefully: Only whitelist bots you actually want to allow
- Use Read-Only Mounts: When managing configs via Git, mount
/sitesas read-only (:ro) to prevent accidental modifications
The entrypoint script gracefully handles read-only mounts. If you see this issue, ensure your .caddy files exist in the /sites directory before starting the container. Example files won't be copied to read-only mounts.
Adjust the matchers in your bot_protection snippet to be less aggressive.
Increase the events or window values in your rate_limit configuration.
Add it to the @good_bots matcher in your configuration.
MIT