A Web Application Firewall (WAF) that sits in front of and protects your web assets from online threats. In this case, Teler-WAF is a free WAF that protects Caddy web server and the sites behind it against from OWASP Top 10 Threats, known vulnerabilities, botnet, malicious crawlers and other attacks. This tutorial will explain the basics of installing the Teler module in Caddy running on Debian 13.
The tutorial starts with building Caddy with the Teler-WAF module using xCaddy. Then configuring the CaddyFile and Teler YAML config file to protect specific web assets.
Quick Comparison of Free WAF Solutions
| WAF Name | Caddy Native | Bot Detection | Cost | Debian Friendly | Notable Features |
|---|---|---|---|---|---|
| Caddy WAF | Yes | Yes | Free | Yes | Regex bot rules, IP blacklists |
| Teler WAF | Yes | Yes | Free | Yes | Botns, crawlers, brute force |
In this case, I went with Teler because it’s simpler to install and configure.
Install Go
Teler WAF is a Go-based middleware for Caddy. Ensure Go is installed:
sudo apt update
sudo apt install golangInstall xcaddy
Install xcaddy to GOPATH/bin (ensure GOPATH/bin is on PATH)
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latestCreate a folder and build Caddy with teler WAF
Build a Caddy binary including the teler module; the teler module’s README shows enabling CGO for the build and passing the module path with –with
mkdir teler
cd teler
CGO_ENABLED=1 xcaddy build \
--with github.com/teler-sh/teler-caddy@latest \
--output dist/caddyReplace the running binary
Stop the service, replace the binary, and restart; the Caddy docs recommend overlaying the newly built binary (the same pattern used by the builder image in Docker).
sudo systemctl stop caddy
sudo cp dist/caddy /usr/bin/caddy
sudo systemctl start caddyVerify the module is present
List compiled modules to confirm teler is included; the command-line docs and man page show list-modules for verification.
caddy list-modules --packages | grep telerCreate YAML Config File
cd /etc/caddy
sudo nano /etc/caddy/teler-waf.conf.yamlExample minimal YAML policy fields to consider:
excludes: [] # do not disable any threat categories
whitelists:
# allow ACME HTTP-01
- 'request.URI startsWith "/.well-known/acme-challenge/"'
# allow health check
- 'request.URI == "/health"'
# allow localhost
- 'request.IP in ["127.0.0.1","::1"]'
# add your API Routes
- 'request.Method == "POST" && request.URI == "/api/users/login"'
- 'request.Method == "POST" && request.URI == "/api/docs/search"'
customs: # optional examples; safe to start empty
- name: "Block common scanner UAs"
rules:
- dsl: 'request.Headers matches "(curl|wget|nikto|acunetix|sqlmap|nmap|masscan|zgrab)"'
log_file: "/var/log/caddy/teler-waf.log"
verbose: true
no_stderr: false
no_update_check: false
in_memory: false
Enable Teler in the Caddyfile
{
#Add a global options block so the teler directive executes before
#other handlers across the config.
order teler_waf first
}
(teler) {
route {
teler_waf {
#pass in the config file
load_from YAML /etc/caddy/teler-waf.conf.yaml
}
}
}
:80 {
# Set this path to your site's directory.
root * /var/www/html
# Enable the static file server.
file_server
#enable file compression
encode gzip
#enable Teler-WAF
import teler
}Validate installation
Test the Caddyfile with the custom binary to catch config errors:
/usr/bin/caddy validate --config /etc/caddy/CaddyfileSend a simple request with curl and watch logs; the teler demo shows curl being flagged as “bad crawler” in Caddy’s logs.
curl -i https://example.com/
tail -f /var/log/caddy/teler-waf.logFurther customizing Teler YAML
Regex in one line
Combine endpoints with the matches operator and alternation to allow several exact paths in a single rule while scoping to the intended HTTP method(s) for that action flow.
- 'request.Method == "POST" && request.URI matches "^/(api/users/login|api/docs/search)(/|$)"'This pattern whitelists POSTs to /api/users/login, /api/docs/search including optional trailing slashes, all in one rule without weakening other routes
Prefix plus methods
If many endpoints share a prefix, allow mutating methods for the whole subtree with startsWith instead of listing each route, which dramatically reduces YAML churn during app changes.
- 'request.Method in ["OPTIONS","POST","PUT","PATCH","DELETE"] && request.URI startsWith "/api/"'
