Caddy – Whitelist Access by IP Address
I’ve recently been looking at Caddy as a reverse proxy server and I wanted to have an IP based whitelist to restrict access to a website Caddy is proxying. To make amending the whitelist easier, I wanted to put the IPs into a separate file.
Method 1
Allow File
This is called allowlist.caddy and I place it in the same directory as my Caddyfile. Note that the allowed IP addresses listed are prefixed with not (yes, even though they allowed).
#IPs not listed here will be denied, so valid IPs are prefixed with not
@website_denied {
not remote_ip 192.168.2.55
not remote_ip 192.168.2.56
}
Caddyfile
To make use of your allowlist.caddy file, the server/site configuration I got working is:
{server.mylab.domain} {
#Import allowed IP address list from file
import /etc/caddy/allowlist.caddy
error @website_denied 403
reverse_proxy {
to https://website
transport http {
tls_server_name {$server.mylab.domain}
}
}
#Return access denied page for unauthorised IP addresses
handle_errors {
@403 `{err.status_code} == 403`
handle @403 {
root * /srv
rewrite * /403.html
file_server
}
}
}
You will need to adjust the site name and reverse proxy configuration to fit your environment.
The error code handling uses Caddy to serve a static HTML page from the srv folder. If you are running Caddy in Docker, your volume mapping in docker-compose.yml might look like:
volumes:
- ./caddy/config:/etc/caddy
- ./caddy/persist/data:/data
- ./caddy/persist/config:/config
- ./caddy/srv:/srv
Method 2
{Added May 2026)
This is another way to restrict access to a particular URL (eg a control panel).
Allow File
In my setup for this, I had a FrankenPHP container (which is based on Caddy) acting as a web server sitting behind a public facing Caddy reverse proxy container. So in my example, the allow file needs to use client_ip rather than remote_ip. As the server is behind a proxy, remote_ip is the IP of the upstream proxy, not the actual client.
You can use individual IPs, CIDR ranges in the allowed list – see the Caddy documentation here.
#IPs not listed here will be denied client_ip 192.168.2.55 192.168.2.56
If your Caddy/FrankenPHP server isn’t behind a reverse proxy, your file would be:
#IPs not listed here will be denied remote_ip 192.168.2.55 192.168.2.56
Caddyfile
This is the code in my Caddyfile to only allow the IPs in the allowed file. That file is in the same folder as the Caddyfile (obviously mapped into the container as well) and called allowed_ips.caddy.
{server.mylab.domain} {
@allowed_admin {
import allowed_ips.caddy
}
handle /admin.php {
handle @allowed_admin {
php_server
}
error "Page not found" 404
}
