How are we able to rate limit based on IP with Caddy
August 1, 2024
I am not starting another Caddy vs Nginx war, but I’ve decided to go with Caddy. I was impressed when I heard that I am getting HTTPS configuration and renewal parts automated out-of-the-box with Caddy. Plus, it can take advantage of all the cores of the system, which I believe should be a good thing. I am always up for using all the available resources and putting them to good use.
If you are very interested in reading about benchmarks done with Nginx and Caddy, here’s one for you all. https://blog.tjll.net/reverse-proxy-hot-dog-eating-contest-caddy-vs-nginx/
Let’s get started with understanding how easy it is to implement IP-based rate limiting on Caddy. Here’s a simplified version of the Caddyfile we have in one of our services.
subdomain.example.com {
encode gzip zstd
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
Referrer-Policy "strict-origin-when-cross-origin"
}
reverse_proxy localhost:8001 {
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
header_down Cache-Control "public, max-age=600"
header_down Vary "Accept-Encoding"
transport http {
keepalive 30s
keepalive_idle_conns 100
}
}
rate_limit {
zone default {
key {remote_host}
events 45
window 30s
}
}
log {
output file /var/log/caddy/subdomain.example.com.log {
roll_size 10MB
roll_keep 5
roll_keep_for 720h
}
}
}
Building Caddy
Caddy doesn’t come with plugins installed with it, but you can build your caddy binary using xcaddy
and give whatever power pack you want it to have and in our case rate rate-limiting option.
From the HTTP. handlers
that support rate limiting as per Caddy’s site, we went ahead with mholt’s implementation because it was easy to understand, and gave us the simplest way to do the task we wanted to do. https://github.com/mholt/caddy-ratelimit
You build your caddy using xcaddy
by following the command
xcaddy build --with github.com/mholt/caddy-ratelimit
This will output a caddy binary which you should replace your caddy with. You can find where your caddy resides by simply running which caddy
and that is the one you want to replace with.
The rate_limit
directives
We are not going over all the directives but focus only on the rate_limit
one.
rate_limit {
zone default {
key {remote_host}
events 45
window 30s
}
}
You can define a zone under the rate_limit
directive and give it a name, in our case we had only one zone, so we gave it default
as an identifier. The way we wanted to rate-limit is by using the IP, which is the remote_host
and we wanted to allow 45 requests in a window of 30s. Anything above will return a 429 as the response.
There is so much we can factor in for writing rate_limit logic, and you can also write rate limits at the application server as well, but this helps us block a few unnecessary attacks and there by helping us not to load the application server. This prevents DOS, but not DDOS because DDOS have their IPs rotated and they will not be picked up by this rate limit logic.