Header only on successful response

1. The problem I’m having:

I’m trying to setup Caddy to proxy traffic to a KirbyCMS instance and I want it to send a specific Cache-Control header for my /media/ path when the response is a non-successful one. I’ve tried a few different matchers and using routes vs handles but can’t seem to get the right combo going.

At the moment if I make a successful request (like below) you can see that on the response the correct Cache-Control header is added.

❯ curl -vso/dev/null 'http://localhost:8080/media/pages/reviews/we-are-warriors/a7746fefba-1730334929/jloh-600x.avif' -H 'If-Modified-Since: Thu, 31 Oct 2024 00:35:47 GMT' -H 'If-None-Match: "d59kjp9nf5m21uov"' -H 'Priority: u=5'
* Host localhost:8080 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:8080...
* Connected to localhost (::1) port 8080
GET /media/pages/reviews/we-are-warriors/a7746fefba-1730334929/jloh-600x.avif HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.7.1
If-Modified-Since: Thu, 31 Oct 2024 00:35:47 GMT
If-None-Match: "d59kjp9nf5m21uov"
Priority: u=5
Accept: application/json, */*

* Request completely sent off
HTTP/1.1 304 Not Modified
Cache-Control: public,max-age=31536000,immutable
Etag: "d59kjp9nf5m21uov"
Server: Caddy
Vary: Accept-Encoding
Date: Thu, 31 Oct 2024 01:29:28 GMT

* Connection #0 to host localhost left intact

But if I make a 404 response, its also added:

❯ vcurl 'http://localhost:8080/media/pages/reviews/we-are-warriors/a7746fefba-1730334929s/jloh-600x.avif' -H 'If-Modified-Since: Thu, 31 Oct 2024 00:35:47 GMT' -H 'If-None-Match: "d59kjp9nf5m21uov"' -H 'Priority: u=5'
* Host localhost:8080 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:8080...
* Connected to localhost (::1) port 8080
GET /media/pages/reviews/we-are-warriors/a7746fefba-1730334929s/jloh-600x.avif HTTP/1.1
Host: localhost:8080
User-Agent: curl/8.7.1
If-Modified-Since: Thu, 31 Oct 2024 00:35:47 GMT
If-None-Match: "d59kjp9nf5m21uov"
Priority: u=5
Accept: application/json, */*

* Request completely sent off
HTTP/1.1 404 Not Found
Cache-Control: public,max-age=31536000,immutable
Content-Type: text/html; charset=UTF-8
Server: Caddy
Status: 404 Not Found
X-Powered-By: PHP/8.2.7
Date: Thu, 31 Oct 2024 01:31:33 GMT
Content-Length: 563

I completely understand Caddy is acting as designed here and doing everything correctly, I would prefer to not add the Cache-Control header in this instance though and fallback to the global one set elsewhere in the config.

I’ve tried changing my /media/* handle to this:

	# Define a matcher to check for successful responses (200 status code)
	@success {
		expression {http.error.status_code} == 0 || {http.error.status_code} == 200
	}
	header @success {
		Cache-Control "public,max-age=31536000,immutable"
	}

And also tried setting a default handle_error block (I think this is what it should be used for?)

handle_errors 404 {
	header {
		Cache-Control "private,max-age=0,must-revalidate"
	}
}

But no luck there.

Ideally I’d want:

  1. Serve file system assets directly
  2. Anything that isn’t on the FS pass to PHP
  3. If its a successful request for /media/* or /assets/*, add the custom Cache-Control header
  4. Otherwise, use the default global one

Hopefully that makes sense?

2. Error messages and/or full log output:cat .

3. Caddy version:

❯ caddy --version
5.8.4

4. How I installed and ran Caddy:

Nix/DevBox adding to local dev environment.

a. System environment:

Installed via Nix/DevBox locally on OSX. DevBox runs the services by process-compose, not quite sure what that is but it runs the service locally.

b. Command:

caddy run --config={$DEVBOX_PROJECT_ROOT}/devbox.d/caddy/Caddyfile

c. Service/unit/compose file:

version: "0.6"

processes:
  caddy:
    command: "caddy run --config=$CADDY_CONFIG"
    availability:
      restart: on_failure
      max_restarts: 5

d. My complete Caddy config:

# See https://caddyserver.com/docs/caddyfile for more details
{
	admin 0.0.0.0:2020
	auto_https disable_certs
	http_port 8800
	https_port 4443
}

:8080 {
	root * {$DEVBOX_PROJECT_ROOT}/public/
	#encode gzip


	handle /assets/* {
		# Handle versioned assets
		@versionedAssets {
			path_regexp versionedAssets ^(.+)\.(?:[0-9a-f]+)\.(js|css)$
		}
		handle @versionedAssets {
			# Try to serve the versioned file, then the unversioned file
			try_files {path} {http.regexp.versionedAssets.1}.{http.regexp.versionedAssets.2}
			header Cache-Control "public,max-age=31536000,immutable"
		}
		file_server {
			precompressed br
		}
	}

	handle /media/* {
		php_fastcgi 127.0.0.1:{$PHPFPM_PORT} {
			try_files {path} _internal/cache/pages/{path}/index.html _internal/cache/pages/{path} index.php
		}
		file_server {
			precompressed br
		}
		header {
			Cache-Control "public,max-age=31536000,immutable"
		}
	}

	# Pass all other requests to PHP
	handle {
		header {
			Cache-Control "public,max-age=0,must-revalidate"
		}
		php_fastcgi 127.0.0.1:{$PHPFPM_PORT} {
			try_files {path} _internal/cache/pages/{path}/index.html _internal/cache/pages/{path} index.php
		}
		file_server {
			precompressed br
		}
	}

	header {
		Cache-Control "public,max-age=0,must-revalidate"
	}

	log {
		output file {$CADDY_LOG_DIR}/caddy.log
	}
}

5. Links to relevant resources:

Thanks in advance for any help!

lmao are you a time traveler? Latest is v2, not v5 :sweat_smile:

That matcher is run before the request is passed to your upstream, so that doesn’t work.

handle_errors is only run if an error is triggered inside Caddy. A non-200 status code from your upstream app is not an error, it’s still just a regular successfully completed response. An error would be something like Caddy failing to connect to the upstream server.

You can use handle_response inside reverse_proxy and/or php_fastcgi to intercept responses by status code. See reverse_proxy (Caddyfile directive) — Caddy Documentation

		php_fastcgi 127.0.0.1:{$PHPFPM_PORT} {
			try_files {path} _internal/cache/pages/{path}/index.html _internal/cache/pages/{path} index.php

			@404 status 404
			handle_response @404 {
				header Cache-Control "private,max-age=0,must-revalidate"
				copy_response
			}

			handle_response {
				header Cache-Control "public,max-age=31536000,immutable"
				copy_response
			}
		}
1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.