Caddy – Whitelist Access by IP Address

Last modified date

Comments: 0

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
	}

Chris

Leave a Reply

Your email address will not be published. Required fields are marked *

Post comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.