Learn how to maximize protection and reduce security & operational costs.

Download guide

Join us for CrowdSec Community Office Hours: June Session!

Register now
caddy and crowdsec

Secure Caddy with CrowdSec: Remediation and WAF Guide

Caddy is a powerful web server written in Go, known for its simple configuration and extensible architecture through modules and its API.

Since Caddy is written in Go, it has no runtime dependencies. However, this also means that any modules you want to use must be compiled manually. To simplify this, Caddy provides the xcaddy binary, which allows you to build a custom Caddy instance with your chosen modules by passing them as arguments.

In this blog post, we’ll explore Caddy, install the package, and extend it using the Remediation Component by hslatman, which supports CrowdSec decisions as well as the AppSec Component.

Installing Caddy

To keep things simple, this blog post won’t cover all deployment options. We’ll use Caddy’s official repositories as outlined in their custom build instructions. However, you can find examples in our Docker Compose repository for building a custom container image.

First, we’ll add the repository as outlined in the official documentation. We won’t copy the commands here, as it’s best to refer directly to the documentation for the most up-to-date instructions.

This will install Caddy, set up the systemd service, and start the web server. Visiting your server’s IP address should now display the default Caddy welcome page.

Before adding the Remediation Component to Caddy, it’s a good idea to configure Caddy using the official documentation to ensure your services remain accessible. However, if you prefer to proceed with adding the module first, you can adjust your configuration afterward.

Installing XCaddy

By default, xcaddy is not included in the Caddy package, as most users won’t need it. Since we’ll be using it to build a custom Caddy binary, we’ll install it following these instructions.

Please note that you’ll also need to install Go on your system, as xcaddy compiles the binary from scratch. Since most distributions provide older versions of Go, it’s recommended to manually install the latest version from the official website. Alternatively, I’ve created a helper script that downloads the latest version to /usr/local/ and adds it to your PATH.

Remediation Component Module

As mentioned earlier, we’ll be using the module developed by hslatman, which supports both CrowdSec decisions and the AppSec component. In short, this module queries the Local API exposed by CrowdSec, retrieves all active decisions, and blocks requests from IP addresses with an active decision.

Note that currently only ban decisions are supported by the remediation component, as hslatman maintains this project in their free time. If you have the capacity to help out, consider visiting the GitHub page and offering a hand with issues or development.

Let’s get started on integrating the module using xcaddy, firstly we can look at the instructions provided by hslatman:


xcaddy build \
	--with github.com/mholt/caddy-l4 \
	--with github.com/caddyserver/transform-encoder \
	--with github.com/hslatman/caddy-crowdsec-bouncer/http@main \
	--with github.com/hslatman/caddy-crowdsec-bouncer/appsec@main \
	--with github.com/hslatman/caddy-crowdsec-bouncer/layer4@main

Running the above command will begin the build process. If you encounter errors related to packages like slog, it likely means your installed Go version is older than 1.24. In that case, uninstall it and follow the earlier steps to install a newer version.

If you didn’t encounter any errors, you’ll now have a caddy binary in the root of the directory where you ran the command. This binary includes all standard Caddy features along with the Remediation Component module. Next, follow the instructions in the custom build section to let the Caddy package know you’re using a custom build, and move the new binary to the appropriate location.

In the future, to update Caddy, you’ll need to run the caddy upgrade command manually. This is because the custom build process sets a diversion on the binary provided by the upstream packages.

Configuring the Remediation Component

So far, we’ve set up Caddy and compiled it with the Remediation Component module. To make it functional, we need to configure it to know where to pull decisions from. This is straightforward in Caddy thanks to its global configuration, which allows us to define all necessary settings at the top level of the Caddyfile.


{
  crowdsec {
	api_url http://127.0.0.1:8080
	api_key 
	ticker_interval 15s
  }
}

The example above shows a simplified version of the configuration. In short, api_url points to where the CrowdSec Local API is listening by default, which is 127.0.0.1:8080, but it may differ depending on your setup, so update it accordingly. The api_key is generated by running cscli bouncers add <name>. You can choose any name; for this blog post, we’ll use caddy. After running the cscli command, copy the generated API key and replace <api_key> in the example with that value.

Once you have updated the Caddyfile you can restart the webserver via:


systemctl restart caddy

You know if the configuration is working via the Caddy system logs, you should see, for example:


May 21 11:02:50 bookworm caddy[45323]: {"level":"info","ts":1747825370.0536578,"logger":"crowdsec","msg":"initializing streaming bouncer","instance_id":"4538971a"}

And via cscli bouncers list, you should see an updated Last API Pull timestamp:

However, this configuration only enables pulling decisions from the Local API. To enforce these decisions on specific routes, you’ll need to add the crowdsec keyword to the route handler, for example:


:80 {
    	# Set this path to your site's directory.
    	root * /usr/share/caddy
    	route {
            	crowdsec
            	file_server
# reverse_proxy localhost:8080
# php_fastcgi localhost:9000
    	}	
}

The configuration above is adapted from the default Caddyfile provided during installation. If you’ve already customized your Caddy setup, you’ll need to update your route definitions to include the crowdsec keyword.

Now we can test the Remediation Component by adding a temporary decision on our IP address. Be aware that if you’re also running other Remediation Components, such as the firewall remediation, you might get disconnected from SSH and see no response from Caddy.

Add a temporary decision:


cscli decisions add -i 192.168.121.1 -d 2m

Replace 192.168.121.1 with your WAN IP address or your local address if testing locally. If you’re only using the Caddy Remediation Component, you should start receiving 403 response codes within 15 seconds instead of seeing the default Caddy welcome page.

Logging options

Before diving into logging options, it’s best to first install the Caddy collection. This ensures that once logging and CrowdSec are configured, log lines can be parsed correctly without errors.


cscli collections install crowdsecurity/caddy

If you’ve followed the guide so far, you’ve set up Caddy and the Remediation Component. However, for CrowdSec to detect patterns of malicious behavior, it needs access to Caddy’s logs. While Caddy offers many logging options, we’ll keep it simple by writing logs to a local file and configuring CrowdSec to monitor it.

First, we’ll configure Caddy to log to a file. Start by creating a directory for the logs:


mkdir -p /var/log/caddy/
Then, add the following global options to your Caddyfile:

{
  crowdsec {
	api_url http://127.0.0.1:8080
	api_key 
	ticker_interval 15s
  }
  log {
	output file /var/log/caddy/access.log {
  	roll_size 30MiB
  	roll_keep 5
	}
  }
}

This configuration overrides the default logger to write to access.log in the Caddy log directory. I personally reduce roll_size and roll_keep from the defaults of 100MiB and 10, but feel free to adjust them to suit your needs.

Now we need to enable logging for the specific sites we want to track. To do this, simply add the log keyword to the relevant site blocks. For example, building on our previous configuration:


:80 {
    	# Set this path to your site's directory.
	log
    	root * /usr/share/caddy
    	route {
            	crowdsec
            	file_server
# reverse_proxy localhost:8080
# php_fastcgi localhost:9000
    	}	
}

Now, when a request hits the website, it will be logged to the file, along with Caddy’s startup logs, based on the configuration we set.

Next, we need to configure CrowdSec to monitor the log file and parse Caddy’s default JSON logs. Create a caddy.yaml file inside /etc/crowdsec/acquis.d/ with the following content:


filename: /var/log/caddy/access.log
labels:
  type: caddy

Then, restart CrowdSec using:


systemctl restart crowdsec

After restarting, check the CrowdSec log file. If it successfully found and began monitoring the log file, you’ll see a confirmation entry there.


time="2025-05-27T15:09:34Z" level=info msg="loading acquisition file : /etc/crowdsec/acquis.d/caddy.yaml"
time="2025-05-27T15:09:34Z" level=info msg="Adding file /var/log/caddy/access.log to datasources" type=file

You can periodically run cscli metrics show acquisition to confirm that CrowdSec is correctly parsing the log lines.

Wrap up

By combining Caddy’s flexible web server architecture with CrowdSec’s remediation and detection capabilities, you can build a robust and adaptive security setup. From integrating the Remediation Component to configuring log ingestion, this guide covered the key steps to protecting your services from abusive traffic while keeping observability intact.

If you’re interested in contributing, consider supporting the Remediation Component on GitHub or sharing feedback with the CrowdSec community on Discord or Discourse. As always, stay secure and happy self-hosting!

Beyond the wrap up!

In this edition of Beyond the Wrap-Up, we’ll cover the WAF capabilities of the Caddy Remediation Component. As mentioned earlier, CrowdSec includes an AppSec Component that allows web servers to forward HTTP requests for analysis against a set of rules. The good news is that, thanks to CrowdSec’s crowdsourced intelligence, these rules can be deployed with minimal to no false positives, unlike many traditional rule sets.

First, we’ll configure the AppSec component of CrowdSec. This involves installing the rule collections you want to use. By default, we recommend installing two collections: one for virtual patching (vpatch) and another for generic detections.


cscli collections install crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-generic-rules

Once the collections are installed, configure the AppSec component using an acquisition file, similar to how we set up Caddy log monitoring, but with different attributes. Create an appsec.yaml file in /etc/crowdsec/acquis.d/ with the following content:


appsec_config: crowdsecurity/appsec-default
labels:
  type: appsec
listen_addr: 127.0.0.1:7422
source: appsec

This sets up the AppSec component to listen on port 7422 on loopback, ensuring it’s not accidentally exposed to external requests. You can change the port if needed, just remember to update it accordingly when configuring Caddy later.

Once the collections are installed and the AppSec configuration is in place, restart CrowdSec with:


systemctl restart crowdsec

Now, head back to your Caddy configuration and add the appsec_url property to the global block we set up earlier:


{
  crowdsec {
	api_url http://127.0.0.1:8080
	api_key 
	ticker_interval 15s
	appsec_url http://127.0.0.1:7422
  }
}

This alone isn’t enough to get Caddy to send requests to the AppSec component. Just like before with the crowdsec keyword, you now also need to add the appsec keyword to the route handler:


:80 {
    	# Set this path to your site's directory.
	log
    	root * /usr/share/caddy
    	route {
            	crowdsec
		appsec
            	file_server
# reverse_proxy localhost:8080
# php_fastcgi localhost:9000
    	}	
}

Note that the order of middleware matters. To optimize resource usage, place the appsec keyword after crowdsec. This way, if the IP is already flagged as malicious, the request will be blocked by the crowdsec middleware before reaching the AppSec component.

Great! Now restart Caddy with:


systemctl restart caddy

Once the configuration reloads, try accessing a common sensitive path like /.env. The request should now be blocked by the AppSec component. You can confirm this even further by checking cscli alerts list, and you should see an alert for crowdsecurity/vpatch-env-access.

We also offer other collections on the Hub, such as the CoreRuleSet (CRS). However, some users find configuring CRS to be a more involved task, especially when tuning it to avoid false positives. That’s why we recommend starting with our managed collections by default. For more comprehensive protection, it’s worth setting aside time to explore and fine-tune CRS later on.

And that concludes the *Beyond the Wrap-Up* section. You now have proactive protection through CrowdSec decisions and WAF capabilities via the AppSec component. Using both together offers a strong defense against behavioral threats and exploitation of known CVEs.

WRITTEN BY

You may also like

Securing Automated Application Deployment with CrowdSec and Coolify
Tutorial

Securing Automated Application Deployment with CrowdSec and Coolify

Learn how to secure your automated app deployments with CrowdSec and Coolify and defend against abusive IPs and unwanted traffic.

how to mitigate distributed denial of service (ddos)
Tutorial

How to Mitigate Distributed Denial of Service (DDoS) Attacks

Read along for a general introduction to DDoS as a concept, the different types of DDoS attacks, and how to successfully mitigate them.

advanced application security with the crowdsec waf
Ambassador Post

Implementing the CrowdSec WAF for Advanced Web Application Security

Transform your Security Engine into a WAF with this get-started guide and learn how to integrate and configure the AppSec Component with NGINX on Debian 12.