trusted_proxies. Lists can come from URLs or from local files, and the last good result is
kept in a persistent on-disk cache so a config load never waits on the network.
- Table of Contents
- How it works
- Installation
- Configuration
- Directives
- Refresh and caching behaviour
- Behaviour comparison
- Building
- License
The module registers the http.ip_sources.list module. Every block that uses it keeps its own list in
memory and refreshes it in the background; requests read the current list without any I/O.
- Provisioning performs no network I/O. The last good ranges are loaded from the cache file, so a
caddy reloadon a large config is not delayed by a slow or unreachable source. - On a cold start (no usable cache) the list starts empty and is refreshed in the background immediately.
- When a refresh fails, the loaded list stays in use; a successful refresh replaces both the list and the cache.
- A refresh that returns no prefixes is ignored while a non-empty list is loaded (see
allow_empty).
Supported from Caddy v2.6.3 onwards.
xcaddy build --with github.com/tuzzmaniandevil/caddy-dynamic-clientip \
--with github.com/monobilisim/caddy-ip-listcaddy add-package github.com/tuzzmaniandevil/caddy-dynamic-clientip
caddy add-package github.com/monobilisim/caddy-ip-list@denied dynamic_client_ip list {
url https://www.cloudflare.com/ips-v4
url https://www.cloudflare.com/ips-v6
interval 12h
timeout 15s
retries 2
# Optional: override the cache file path (defaults under Caddy's data dir)
# cache_file /var/lib/caddy/ip-list-cache.json
}
abort @deniedA file source needs no TLS, no DNS and no HTTP round trip, so it can be refreshed far more often than a
URL. The format is identical to the served list: one CIDR per line, # starts a comment.
@denied dynamic_client_ip list {
file /srv/whitelist/mono.txt
file /srv/whitelist/local.txt
interval 30s
}
abort @deniedurl and file may be combined in one block; the prefixes are concatenated.
When every source in a block is a file, an unchanged file costs one stat per tick: the list is not
re-parsed and the cache is not rewritten. This is what makes a short interval affordable even when
hundreds of blocks share the same few files.
trusted_proxies list {
url https://www.cloudflare.com/ips-v4
url https://www.cloudflare.com/ips-v6
interval 12h
timeout 15s
retries 2
# cache_file /var/lib/caddy/ip-list-cache.json
}| Name | Description | Type | Default |
|---|---|---|---|
url |
URL(s) to retrieve the IP list from | string | required if no file |
file |
Local path(s) to read the IP list from | string | none |
interval |
Frequency at which the list is refreshed (capped at 15s for file-only blocks) |
duration | 1h |
timeout |
Maximum time to wait for a response from a URL | duration | no timeout |
retries |
Retries per URL per refresh (0 disables) |
int | 2 |
cache_file |
Path for the persistent cache | string | derived from the sources |
allow_empty |
Accept a refresh that returns no prefixes | bool | false |
- Cache first. The cache file is named after the sources in the block (
sha256of the URL list plus thefile:paths), so two blocks with different lists never share a cache file. - Failures keep the old list. A broken source logs a warning and leaves the current list untouched.
- Empty results keep the old list. In a
not dynamic_client_ipmatcher an empty list matches every client, so a truncated or emptied source would lock everybody out. Unlessallow_emptyis set, an empty refresh is discarded while a non-empty list is loaded. Setallow_emptyonly for a list that is genuinely meant to become empty. - Short intervals are cheap for files. An unchanged file is detected with
statand skipped. - File-only blocks ignore long intervals. When a block has
filesources and nourl,intervalis capped at 15s: an unchanged file costs onestat, so a 6h interval would only delay picking up a list written by CI. Blocks that fetch over the network keep the configured interval. - No leaks. The refresh loop stops when the configuration is unloaded, so repeated config loads do not accumulate goroutines or timers.
- Cold start. Without a usable cache, matchers see an empty list until the first background refresh completes. Keep the cache on persistent storage (the default under Caddy's data directory does this).
Note: with
not dynamic_client_ip, an empty list denies everything. Warm the cache on a passive node before promoting new sources to production.
scripts/compare.sh builds two revisions and runs the same scenarios against both:
./scripts/compare.sh # markdown table
./scripts/compare.sh --json # machine readable| scenario | 9336c17 (before) |
current |
|---|---|---|
| source returns 404, no cache | config rejected, Caddy does not start | loads, empty list, recovers |
| source returns 404, cache present | loads from cache | loads from cache |
| source returns 200, no cache | loads | loads |
The first row is why a single bad URL used to take a whole config down: Provision returned an error and
Caddy discards the entire config when a module fails to provision. Override the revisions with OLD_REF /
NEW_REF, or point at binaries you already have with OLD_BIN / NEW_BIN.
go build ./...
go test ./...To build Caddy with this module:
xcaddy build --with github.com/monobilisim/caddy-ip-listcaddy-ip-list is licensed under Apache-2.0. See LICENSE file for details.