Caddyfile config with crowdsec middleware: can I reroute/pass-through to static website, after time passed that displays response “allowed by Crowdsec”

1. The problem I’m having:

I am testing on my local server the functionality/configuration of Caddyfile together with middleware calls like crowdsec. The general functionality is working, but I can’t figure out how to configure some special reroutes based on the Caddyfile Concepts and if there is some possibility after Crowdsec “says you can pass”, to display a response “Allowed by CrowdSec!” and after 2 seconds, reroute to static file server

{
   log {
      level DEBUG
   }
   crowdsec {
      api_url http://localhost:port
      api_key {env.CROWDSEC_API_KEY}
      ticker_interval 3s
   }
}
localhost {
   route {
      crowdsec
      respond "Allowed by CrowdSec!"
  }
}

This config works fine, and works as expected. After accessing I get a webpage response “allowed by Crowdsec”

If I ban my IP (for 5 minutes) I get an Error Code: 403 Forbidden, witch is perfect.

To call a static webpage hosted in caddy, and keep the crowdsec running, I adapted the Caddyfile as follows

{
   log {
      level DEBUG
   }
   crowdsec {
      api_url http://localhost:port
      api_key {env.CROWDSEC_API_KEY}
      ticker_interval 3s
   }
}
localhost {
   route {
      crowdsec
      #respond "Allowed by CrowdSec!"
     root * /var/www
     encode gzip
     file_server
   }
}

So now, if crowdsec allows, my static webpage is called.

Now this is good, but I want to learn a little bit more about how to write caddy config an manage this “middleware” calls. And also about working with the proper Caddyfile concepts.

Yes I read through the documentation, but I think my brain is to wired with the python or bash coding… I work my way through this problems by showing feedback on the screen, to make is visible, showing error codes, etc.

IF crowdsec middleware = TRUE, THEN show respond “Allowed by CrowdSec!” for 2 seconds, and after reroute to static webpage
IF crowdsec middleware = FALSE THEN show respond “NOT-Allowed by CrowdSec!” for 2 seconds, and after reroute to google.com, or do nothing

Anyway, I wanted to know if someone could give me a hint how to manage following use case.

  1. When localhost is called, then route to crowdsec middleware, if TRUE, then respond “Allowed by CrowdSec!” and after 2 seconds reroute to static page on file_server
  2. If the answer of Crowdsec is FALSE, then respond “Not allowed by Crowdsec! And after 2 seconds, reroute to (let’s say google.com).
  3. Is there a way to catch from crowdsec a feedback (TRUE/FALSE)?

I am aware that my ask might not be feasible with capabilities of Caddyfile, but it does not hurt to ask someone who has some experience. Anyhow, thanks so much for your help!

2. Error messages and/or full log output:

No errors so far

3. Caddy version:

caddy version v2.9.1 h1:OEYiZ7DbCzAWVb6TNEkjRcSCRGHVoZsJinoDR/n9oaY=

4. How I installed and ran Caddy:

Debian host bookworm on raspberry pi

a. System environment:

xcaddy image running on podman container with installed crowdsec-caddy-bouncer

b. Command:

N/A

c. Service/unit/compose file:

N/A

d. My complete Caddy config:

{
   log {
      level DEBUG
   }
   crowdsec {
      api_url http://localhost:port
      api_key {env.CROWDSEC_API_KEY}
      ticker_interval 3s
   }
}
localhost {
   route {
      crowdsec
      #respond "Allowed by CrowdSec!"
     root * /var/www
     encode gzip
     file_server
   }
}

5. Links to relevant resources:

I do not have much experience with CrowdSec, so there most likely is a better way to do what you want. But here’s my quick’n’dirty version that does what you want.

Caddyfile:

{
	http_port 8080

	crowdsec {
		api_url http://127.0.0.1:8181
		api_key {env.CROWDSEC_API_KEY}
		ticker_interval 3s
	}

	servers {
		trusted_proxies static 127.0.0.1
	}
}

## 0 - Message
## 1 - TTL
## 2 - HTTP Status
## 3 = Target

(cs_status) {
	header Content-Type text/html
	respond <<HTML
		<html>
			<head>
				<meta charset="UTF-8">
				<meta http-equiv="refresh" content="{args[1]};url={args[3]}" />
				<title>{args[0]}</title>
			</head>
			<body>
				<p>{args[0]}</p>
				<p>You will be redirected in {args[1]} seconds. If not, <a href="{args[3]}">click here</a>.</p>
			</body>
		</html>
		HTML {args[2]}
}

:8080 {

	handle /cs-pass/* {
		import cs_status "Allowed by CrowdSec!" 2 503 /
	}

	handle /cs-fail/* {
		import cs_status "Failed by CrowdSec!" 2 403 https://google.com
	}

	reverse_proxy 127.0.0.1:8081 {
		
		@cs_PASS {
			status 200
			header X-CS-Status pass
		}
		
		@cs_FAIL {
			status 403
			header X-CS-Status fail
		}
		
		handle_response @cs_PASS {
			redir * /cs-pass/
			copy_response_headers {
				include Set-Cookie
			}
		}

		handle_response @cs_FAIL {
			redir * /cs-fail/
		}
		
		header_down -Server
	}
}

:8081 {

	route {
		header X-CS-Status fail
		crowdsec

		@csCookieMissing not header_regexp Cookie (?i)(;\s*)?(cs-status=pass)
		handle @csCookieMissing {
			header X-CS-Status pass
			header +Set-Cookie cs-status=pass
		}
		handle {
			header -X-CS-Status
		}

		respond "File Server!"
	}
}

Test results:

## cscli decisions list
## No active decisions

$ curl http://localhost:8080 -I
HTTP/1.1 302 Found
Location: /cs-pass/
Server: Caddy
Set-Cookie: cs-status=pass
Date: Tue, 15 Apr 2025 04:49:32 GMT
$ curl http://localhost:8080/cs-pass/ -v
* Host localhost:8080 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:8080...
* Connected to localhost (::1) port 8080
* using HTTP/1.x
> GET /cs-pass/ HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.12.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 503 Service Unavailable
< Content-Type: text/html
< Server: Caddy
< Date: Tue, 15 Apr 2025 04:49:59 GMT
< Content-Length: 273
<
<html>
	<head>
		<meta charset="UTF-8">
		<meta http-equiv="refresh" content="2;url=/" />
		<title>Allowed by CrowdSec!</title>
	</head>
	<body>
		<p>Allowed by CrowdSec!</p>
		<p>You will be redirected in 2 seconds. If not, <a href="/">click here</a>.</p>
	</body>
* Connection #0 to host localhost left intact
</html>
$ curl http://localhost:8080 -v -H 'Cookie: cs-status=pass'
* Host localhost:8080 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:8080...
* Connected to localhost (::1) port 8080
* using HTTP/1.x
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.12.1
> Accept: */*
> Cookie: cs-status=pass
>
* Request completely sent off
< HTTP/1.1 200 OK
< Content-Length: 12
< Content-Type: text/plain; charset=utf-8
< Date: Tue, 15 Apr 2025 04:54:30 GMT
< Server: Caddy
<
* Connection #0 to host localhost left intact
File Server!

When you land at your website and pass the CrowdSec check, Caddy sends you a cookie and redirects you to /cs-pass/ where you’ll see the “Passed message” for 2 seconds and then get redirected back to / (you can adjust the code to send you to the original URL) with browser sending back the cookie, which indicates to Caddy that it should not send you back to the “Passed message” again.

## cscli decisions list
## ╭───────┬────────┬──────────────┬───────────────────────────────┬────────┬─────────┬────┬────────┬────────────┬──────────╮
## │   ID  │ Source │  Scope:Value │             Reason            │ Action │ Country │ AS │ Events │ expiration │ Alert ID │
## ├───────┼────────┼──────────────┼───────────────────────────────┼────────┼─────────┼────┼────────┼────────────┼──────────┤
## │ 17212 │ cscli  │ Ip:127.0.0.1 │ manual 'ban' from 'localhost' │ ban    │         │    │ 1      │ 3h59m55s   │ 6        │
## │ 17211 │ cscli  │ Ip:::1       │ manual 'ban' from 'localhost' │ ban    │         │    │ 1      │ 3h59m50s   │ 5        │
## ╰───────┴────────┴──────────────┴───────────────────────────────┴────────┴─────────┴────┴────────┴────────────┴──────────╯

$ curl http://localhost:8080 -I
HTTP/1.1 302 Found
Location: /cs-fail/
Server: Caddy
Date: Tue, 15 Apr 2025 04:53:04 GMT
$ curl http://localhost:8080/cs-fail/ -v
* Host localhost:8080 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:8080...
* Connected to localhost (::1) port 8080
* using HTTP/1.x
> GET /cs-fail/ HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.12.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 403 Forbidden
< Content-Type: text/html
< Server: Caddy
< Date: Tue, 15 Apr 2025 04:53:31 GMT
< Content-Length: 305
<
<html>
	<head>
		<meta charset="UTF-8">
		<meta http-equiv="refresh" content="2;url=https://google.com" />
		<title>Failed by CrowdSec!</title>
	</head>
	<body>
		<p>Failed by CrowdSec!</p>
		<p>You will be redirected in 2 seconds. If not, <a href="https://google.com">click here</a>.</p>
	</body>
* Connection #0 to host localhost left intact
</html>

When you land at your website and fail the CrowdSec check, Caddy redirects you to /cs-fail/ where you’ll see the “Failed message” for 2 seconds and then get redirected back to https://www.google.com.

3 Likes

Nice! Thanks a lot @timelordx , I will test this out this week and give feedback.
What I can already see, is that I only grabbed the surface of possibilities, and that is great!
I will have to review and deepen my knowledge about the syntax and concepts of Caddyfile.
Can anyone recommend any ebook with lost of examples about capabilities of caddy (didn’t search the forum jet)?
Thanks so much, will write back soon after testing and understanding the code.

Caddy has a really solid documentation site - that’s definitely the best place to start.

If there’s one thing I’d recommend everyone read, it’s this page about directive order. It’s especially helpful for folks coming from a programming background who might assume things run in the order they’re written.

Hello @timelordx, had some time to review your code and understand it, by using documentation. Below you will find my comments that help me understand the Caddyfile better :slight_smile:
I am still having some problem understanding sequence. I know that its many from top to down and there are some prioritised sequences (so n o mater the top down order, some come first).

Global Configs
{
	#The port for the server to use for HTTP default is 80 but that would be for PROD, for local network 8080 is fine**
	http_port 8080
	
	#caddy module crows config
	crowdsec {
		api_url http://127.0.0.1:8181
		api_key {env.CROWDSEC_API_KEY}
		ticker_interval 3s
	}
	#Server Options, settings that span multiple sites
	servers {
		#Trust requests only from specific proxy servers, in a pod man env all requests should come from localhost
		trusted_proxies static 127.0.0.1
	}
}

##Argumenrts
## 0 - Message
## 1 - TTL
## 2 - HTTP Status
## 3 = Target

#Snipped cs_status meaning crowdsec status display through html page. Through this snipped a webpage is called by calling “cs_status” and you can pass arguments (in this case up to 4)
(cs_status) {
	header Content-Type text/html
	respond <<HTML
		<html>
			<head>
				<meta charset="UTF-8">
				<meta http-equiv="refresh" content="{args[1]};url={args[3]}" />
				<title>{args[0]}</title>
			</head>
			<body>
				<p>{args[0]}</p>
				<p>You will be redirected in {args[1]} seconds. If not, <a href="{args[3]}">click here</a>.</p>
			</body>
		</html>
		HTML {args[2]}
}


#Site block for all calls with port 8080
:8080 {
	#handle directive, if it matches “/cs-pass/*” then call sub directive import that includes/calls snipped with arguments
	handle /cs-pass/* {
		import cs_status "Allowed by CrowdSec!" 2 503 /
	}
	#handle directive, if it matches “/cs-pass/*” then call sub directive import that includes/calls snipped with arguments
	handle /cs-fail/* {
		import cs_status "Failed by CrowdSec!" 2 403 https://google.com
	}
	#reverse proxy directive, proxy request to backend upstream 127.0.0.1:8081 (without matcher)
	reverse_proxy 127.0.0.1:8081 {
		#named matcher 
		@cs_PASS {
			status 200
			header X-CS-Status pass
		}
		#named matcher
		@cs_FAIL {
			status 403
			header X-CS-Status fail
		}
		handle_response @cs_PASS {
			redir * /cs-pass/
			copy_response_headers {
				include Set-Cookie
			}
		}
		handle_response @cs_FAIL {
			redir * /cs-fail/
		}		
		header_down -Server
	}
}
#Site block for all calls with port 8081
:8081 {
	route {
		header X-CS-Status fail
		crowdsec

		@csCookieMissing not header_regexp Cookie (?i)(;\s*)?(cs-status=pass)
		handle @csCookieMissing {
			header X-CS-Status pass
			header +Set-Cookie cs-status=pass
		}
		handle {
			header -X-CS-Status
		}
		respond "File Server!"
	}
}

I am having a little bit of trouble to understand both site blocks for 8080 & 8081. I get the overall concept, but would like to understand the detail.
From what I have got so far is:

Call localhost:8080 url
since no match with /cs-pass/* OR /cs-fail/* reverse_proxy/redirect to localhost:8081
and here it gets for me difficult to understand

Maybe you fill in the gaps… I will test the code probably tomorrow to understand sequence. That is somehow what is somewhat missing in the documentation, or at least I don’t get it correctly.

In general, you got everything right.

As I mentioned earlier, I don’t have much experience with CrowdSec, but from what I’ve seen, there aren’t many options for flow control based on the crowdsec return value. It either lets you through or blocks you with a 403 - and at that point, it’s already too late to do anything, since the browser already received the 403.

That’s why I wrapped the “main site” (:8081) with a landing site (:8080) using reverse_proxy. The nice thing about reverse_proxy is that it lets you intercept responses from the upstream - and that’s exactly what I’m doing in my example.

	reverse_proxy 127.0.0.1:8081 {
		
		@cs_PASS {
			status 200
			header X-CS-Status pass
		}
		
		@cs_FAIL {
			status 403
			header X-CS-Status fail
		}
		
		handle_response @cs_PASS {
			redir * /cs-pass/
			copy_response_headers {
				include Set-Cookie
			}
		}

		handle_response @cs_FAIL {
			redir * /cs-fail/
		}
		
		header_down -Server
	}

I’ve got two response matchers - @cs_PASS and @cs_FAIL - and they both check what the upstream (the “main site”) is returning. I’m not just matching by status code, since the main site returns 200 for almost everything. And 403 might come from your main site even when CrowdSec isn’t involved. So, to avoid incorrectly sending users to “Allowed by CrowdSec!” or “Failed by CrowdSec!” every time, I’m using a custom X-CS-Status header that only gets set when CrowdSec is involved.

Now, because I’m using this reverse_proxy wrapper, the “main site” that contains the crowdsec directive will see all traffic coming from 127.0.0.1. Not very helpful, right? :slight_smile: To fix that, I added this to the global options:

	servers {
		trusted_proxies static 127.0.0.1
	}

This ensures that client_ip on the “main site” contains the real client IP, not just 127.0.0.1. That way, CrowdSec can actually see who’s connecting and make the right decision.

So here’s the flow: you land on the Caddy server - in my case, that’s port :8080, but in your setup it might be :80 or :443. I just use :8080 on my laptop for quick tests - nothing special about it. Once you hit this entry point, your request is forwarded to the “main site” on :8081 - unless you’re redirected to /cs-pass/ or /cs-fail/.

On the “main site,” the first thing that happens is it preemptively assumes you’ll fail CrowdSec by setting the header X-CS-Status fail. Then you pass through CrowdSec, which either lets you in or returns a 403.

  • If you fail CrowdSec, the main site responds with a 403, which is intercepted by the landing site. That triggers the @cs_FAIL matcher and executes handle_response @cs_FAIL, which redirects you to /cs-fail/.
  • If you pass CrowdSec, and your browser isn’t already sending the expected cookie, Caddy sets the cookie and X-CS-Status pass header and sends you to /cs-pass/. If the cookie is already there, it clears the X-CS-Status header and serves your content.

Sorry if I’m going in circles trying to explain this flow - it’s just a bit tricky to lay out clearly. One thing to keep in mind is that Caddy does some internal ordering optimizations. That’s why I pointed out the Directive Order as one of the most important parts of the docs, in my opinion. Matchers would be next on the list of things worth reading.

1 Like

Hey @timelordx , thanks for all the additional clarifications and explanations.
I have still 3 minor questions to some code snippets:
(1) what purpose serves this header_down manipulation

#request/header manipulation deletes (-) Server in header (WHY/PURPOSE?)
		header_down -Server

(2) Does this matcher check the response of crowdsec? I am having a hard time differencing request name matchers from response name matchers. My logic tells me, I check crowdsecs answer in this case, so should be a response matcher…

#is this a response matcher or request matcher??? if cs-status=pass in Cookie is not there then TRUE
		@csCookieMissing not header_regexp Cookie (?i)(;\s*)?(cs-status=pass)

(3) Why delete the X-CS-Status at this point? I get that it’s not needed, since all crowdsec “accepted” passthroughs are marked with Cookie cs-status=pass, but is there like a specific reason for this?

# handle directive, delete X-CS-Status before responding/reverse proxy to webpage or app
		handle {
			header -X-CS-Status

Getting a better handle of the documentation now. Thinking of creating a flow diagram so newbies like me can “see” the flow, witch is not that “easy” to understand. Proceeding to test the code. Thanks a lot!

Just my OCD :slight_smile: Caddy doesn’t replace the Server header but rather adds to it. So when reverse_proxy is involved, instead of a single Server header, the client ends up seeing multiple Server headers.

For example, without:

header_down -Server

the response looks like this:

$ curl http://localhost:8080 -I -H 'Cookie: cs-status=pass'
HTTP/1.1 200 OK
Content-Length: 12
Content-Type: text/plain; charset=utf-8
Date: Fri, 18 Apr 2025 21:40:17 GMT
Server: Caddy
Server: Caddy

It’s absolutely unrelated to the actual problem you’re solving - I just didn’t like it :slight_smile:

Also, this won’t be needed anymore. The latest Caddy v2.10.0, which was just released an hour ago, finally fixed this :folded_hands:

  • Via header: The reverse proxy now sets a Via header instead of a duplicate Server header.
$ curl http://localhost:8080 -I -H 'Cookie: cs-status=pass'
HTTP/1.1 200 OK
Content-Length: 12
Content-Type: text/plain; charset=utf-8
Date: Fri, 18 Apr 2025 22:09:49 GMT
Server: Caddy
Via: 1.1 Caddy

This one’s a request matcher. It checks whether the client is sending a cs-status=pass cookie.

The simplest version would be:

@csCookieMissing not header Cookie cs-status=pass

But that assumes the cookie header contains only cs-status=pass, which isn’t always the case. As you probably know, browsers can send multiple cookies as either separate headers or a single Cookie header with values separated by semicolons and spaces. So this version:

@csCookieMissing not header_regexp Cookie (?i)(;\s*)?(cs-status=pass)

takes care of both formats and is a bit more robust.

OCD again :slight_smile: Without cleaning it up, the header gets passed to the client - and that could confuse someone trying to troubleshoot.

Example:

$ curl http://localhost:8080 -I -H 'Cookie: cs-status=pass'
HTTP/1.1 200 OK
Content-Length: 12
Content-Type: text/plain; charset=utf-8
Date: Fri, 18 Apr 2025 21:47:04 GMT
Server: Caddy
X-Cs-Status: fail

That’s pretty misleading - a 200 OK response with X-Cs-Status: fail makes no sense to the end user. That header is meant as an internal signal for Caddy’s flow logic, not for public viewing. So I clean it up to keep the response nice and tidy.

1 Like

@timelordx Thanks for the answers! Again appreciate the time you’re investing to answer my questions.

Im now testing the code. As I mentioned above, running Caddy in rootless podman using xCaddy custom image. All this running on raspberry pi (for an IoT project)

I adapted the Caddyfile and I am getting a specific errors from Caddy while upping the container witch I can’t quite debug

{"level":"info","ts":1745013859.2826948,"msg":"using config from file","file":"/etc/caddy/Caddyfile"}
Error: adapting config using caddyfile: File to import not found: cs_status, at /etc/caddy/Caddyfile:54

it refers to the import section of the Caddyfile

handle /cs-pass/* {
		import cs_status "Allowed by CrowdSec!" 2 503 /

I am not sure if this has something to do with the syntax handing over args, or the import directive is expecting a html.file or if it has something to do with the container file itself

just in case I am adding my podman config, but I think it should be irrelevant

podman run \
--network slirp4netns:port_handler=slirp4netns \
--publish 9443:9443/tcp \
--publish 9080:9080/tcp \
--volume ~/projects/apps/caddy/caddy-log:/var/log/caddy \
--volume ~/projects/apps/caddy/caddy-data:/data \
--volume ~/projects/apps/caddy/caddy-conf:/etc/caddy \
--volume ~/projects/apps/caddy/caddy-config:/config \
--volume ~/projects/apps/caddy/html:/var/www:Z \
--secret CROWDSEC_API_KEY,type=env,target=BOUNCER_KEY_caddy \
--label "io.containers.autoupdate=registry" \
--detach \
--restart on-failure \
--name caddy localhost/xcaddy:latest

Also in this section of the Caddyfile I was wondering why pass an HTTP-Status 503 and not a HTTP-Status 200 as an argument to the html… sind 200 would mean all OK

Show the Caddyfile.

# Global Config
{
    #The port for the server to use for HTTP default is 80 but that would be for PROD (and needs some special firewall rules), for local network 9080 is fine
    http_port 9080
    #Enables debug mode
    debug
    #Disables both certificate automation and HTTP-to-HTTPS redirects. Not to apply on prod
    auto_https off
    #caddy module crowdsec config
    crowdsec {
	api_url http://127.0.0.1:8080
	api_key {$BOUNCER_KEY_caddy}
	ticker_interval 10s
	#appsec_url http://127.0.0.1:7422
	#disable_streaming
	#enable_hard_fails
    }
    #Server Options, settings that span multiple sites
    servers {
	#Trust requests only from specific proxy servers, in a pod man env all requests should come from localhost
	trusted_proxies static 127.0.0.1
}

##cs abbreviation stands for CrowdSec
##Argumenrts
## 0 - Message
## 1 - TTL
## 2 - HTTP Status
## 3 = Target

#Snipped cs_status meaning crowdsec status display through html page. Through this snipped a webpage is called by calling “cs_status” and you can pass arguments (in this case up to 4)
(cs_status) {
	header Content-Type text/html
	respond <<HTML
		<html>
			<head>
				<meta charset="UTF-8">
				<meta http-equiv="refresh" content="{args[1]};url={args[3]}" />
				<title>{args[0]}</title>
			</head>
			<body>
				<p>{args[0]}</p>
				<p>You will be redirected in {args[1]} seconds. If not, <a href="{args[3]}">click here</a>.</p>
			</body>
		</html>
		HTML {args[2]}
}

#Site block for all calls with port 9080, this is the landing site, that wraps “main site” :9081 using a reverse proxy
:9080 {
	#handle directive, if it matches “/cs-pass/*” then call sub directive import that includes/calls snipped with arguments
	handle /cs-pass/* {
		import cs_status "Allowed by CrowdSec!" 2 503 /
	}
	#handle directive, if it matches “/cs-pass/*” then call sub directive import that includes/calls snipped with arguments
	handle /cs-fail/* {
		import cs_status "Failed by CrowdSec!" 2 403 https://google.com
	}
	#reverse proxy directive, proxy request to backend upstream 127.0.0.1:9081 (without matcher)
	reverse_proxy 127.0.0.1:9081 {
		#respond matcher: optionally intercept responses from upstream
		@cs_PASS {
			status 200
			header X-CS-Status pass
		}
		#respond matcher: optionally intercept responses from upstream
		@cs_FAIL {
			status 403
			header X-CS-Status fail
		}
		#handle_response, Intercepting responses, defines the route to execute when matched by the given matcher. In block any other directive can be used
		handle_response @cs_PASS {
			#directive to redirect ALL to /cs-pass/ if
			redir * /cs-pass/
			#copy_response_headers copies the response headers from the backend to the client
			copy_response_headers {
				# include Cookie
				include Set-Cookie
			}
		}
		#handle_response, defines the route to execute when matched by the given matcher
		handle_response @cs_FAIL {
			#directive to redirect ALL to /cs-fail/
			redir * /cs-fail/
		}
		#request/header manipulation deletes (-) Server in header (WHY/PURPOSE?)
		header_down -Server
	}
}

#Site block for all calls with port 9081, this is the “main site”
:9081 {
	route {
		#preemptively assumption of a failed CrowdSec setting X-CS-Status to fail
		header X-CS-Status fail
		#check ip to crowdsec middelware call
		crowdsec

		#is this a response matcher or request matcher??? if cs-status=pass in Cookie is not there then TRUE
		@csCookieMissing not header_regexp Cookie (?i)(;\s*)?(cs-status=pass)
		#handle directive, if match to @csCoockieMissing (name)
		handle @csCookieMissing {
			header X-CS-Status pass
			header +Set-Cookie cs-status=pass
		}
		# handle directive, delete X-CS-Status before responding/reverse proxy to webpage or app
		handle {
			header -X-CS-Status
		}
		root */var/www
		encode gzip
		file_server
	}
}

sorry Caddy file full of comments for the time being

You don’t want Google or other search engines indexing your CrowdSec response page. A 200 OK would allow that, but a 503 prevents it. But you certainly can use 200 if that’s what you prefer.

1 Like

So, I have been trying to debug the issue with my Caddyfile, checking caddy documentation, concepts and structure, witch I think are met.
Seems that the snippet “cs_status” is not found, witch makes no sense, because snipped is places between global options and site address blocks.
Also I can’t find a syntax issue in the import directive.
Is there an additional way to debug properly the Caddyfile?
Maybe using caddy adapt command…?

You need to properly format your Caddyfile. If you did that, you would notice that you’re missing a closing } :slight_smile:

This:

needs to be this:

    ...
    #Server Options, settings that span multiple sites
    servers {
	#Trust requests only from specific proxy servers, in a pod man env all requests should come from localhost
	trusted_proxies static 127.0.0.1
    }
}
...

yes, that was it, I installed VSCode with vscode-caddyfile and just found the missing curly braces. My bad! Proceeding with testing.

@timelordx , finished my testing! works like a charm! Will keep experimenting all additional options now with crowdsec appsec and layer4! Thanks so much for your help!

1 Like