Only one 'header Link' applied

1. Caddy version (caddy version):

v2.4.6 h1:HGkGICFGvyrodcqOOclHKfvJC0qTU7vny/7FhYp9hNw=

2. How I run Caddy:

a. System environment:

Gentoo Linux, OpenRC

b. Command:

caddy run (using Caddyfile)

c. Service/unit/compose file:

d. My complete Caddyfile or JSON config: {
	import main
	@title {
		not file {
			try_files {path} {path}/
			split_path .php
		path_regexp title ^/(.*)$
	@preload {
		path /w/*
	@cache {
		path /mediawiki/resources/assets/* /mediawiki/resources/assets/*/* /mediawiki/images/* /mediawiki/images/*/* /mediawiki/images/*/*/* /favicon.ico /mediawiki/skins/Timeless/resources/images/*
	rewrite @title /mediawiki/index.php?title={re.title.1}&{query}
	redir / /w/Main_Page

    header {
		Permissions-Policy interest-cohort=()
		Strict-Transport-Security "max-age=15768000; preload"
		X-Frame-Options DENY
		#X-Content-Type-Options nosniff
	header @cache {
		Cache-Control max-age=31536000
	header @preload {
		Link "</mediawiki/resources/assets/fonts/KFOmCnqEu92Fr1Mu4mxK.woff2>; rel=preload; as=font"
		Link "</mediawiki/resources/assets/fonts/KFOmCnqEu92Fr1Mu4mxK.woff2-test>; rel=preload; as=font"
#		Link "</mediawiki/load.php?lang=en-gb&modules=site.styles&only=styles&skin=timeless>; rel=preload; as=style"
#		Link "</mediawiki/load.php?lang=en-gb&modules=ext.visualEditor.desktopArticleTarget.noscript%7Cskins.timeless&only=styles&skin=timeless>; rel=preload; as=style"
	root * /var/www/domains/
	php_fastcgi unix//var/run/php-fpm/fpm-wiki.socket

3. The problem I’m having:

The problem I have is that only the last Link header is added. In this case the KFOmCnqEu92Fr1Mu4mxK.woff2-test. I do want to add the two stylesheets as well but the result is the same.

# curl -I --http2
HTTP/2 200
alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
cache-control: no-cache, no-store, max-age=0, must-revalidate
content-language: en-GB
content-type: text/html; charset=UTF-8
expires: Thu, 01 Jan 1970 00:00:00 GMT
link: </mediawiki/resources/assets/fonts/KFOmCnqEu92Fr1Mu4mxK.woff2-test>; rel=preload; as=font
permissions-policy: interest-cohort=()
pragma: no-cache
server: Caddy
strict-transport-security: max-age=15768000; preload
vary: Accept-Encoding, Cookie
x-content-type-options: nosniff
x-frame-options: DENY
x-request-id: 99fdea95d1e529bd78406321
date: Sat, 01 Jan 2022 09:17:43 GMT

4. Error messages and/or full log output:

	"level": "info",
	"ts": 1641028663.0280214,
	"logger": "http.log.access.log21",
	"msg": "handled request",
	"request": {
		"remote_addr": "[2001:470:28:704::100]:37086",
		"proto": "HTTP/2.0",
		"method": "HEAD",
		"host": "",
		"uri": "/w/Main_Page",
		"headers": {
			"User-Agent": [
			"Accept": [
		"tls": {
			"resumed": false,
			"version": 772,
			"cipher_suite": 4865,
			"proto": "h2",
			"proto_mutual": true,
			"server_name": ""
	"common_log": "2001:470:28:704::100 - - [01/Jan/2022:10:17:43 +0100] \"HEAD /w/Main_Page HTTP/2.0\" 200 0",
	"user_id": "",
	"duration": 0.070666508,
	"size": 0,
	"status": 200,
	"resp_headers": {
		"Expires": [
			"Thu, 01 Jan 1970 00:00:00 GMT"
		"Cache-Control": [
			"no-cache, no-store, max-age=0, must-revalidate"
		"Pragma": [
		"X-Request-Id": [
		"Alt-Svc": [
			"h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000"
		"Strict-Transport-Security": [
			"max-age=15768000; preload"
		"Content-Language": [
		"Vary": [
			"Accept-Encoding, Cookie"
		"X-Content-Type-Options": [
		"Content-Type": [
			"text/html; charset=UTF-8"
		"Link": [
			"</mediawiki/resources/assets/fonts/KFOmCnqEu92Fr1Mu4mxK.woff2-test>; rel=preload; as=font"
		"X-Frame-Options": [
		"Permissions-Policy": [
		"Server": [

5. What I already tried:

I tried to nest like so:

header {
   header @cache {

Could route be an option to use?

6. Other info

Ultimately I’d like to use HTTP push for some resources if the user doesn’t have the resource cached, but how can I tell if a user haven’t visited the site before? I am thinking I could set a cookie, and then check for it. If not set, then use HTTP push, otherwise just add the preload link header.

I think you need to use the + prefix to make sure the header is added instead of replaced. For example:

	header @preload {
		+Link "</mediawiki/resources/assets/fonts/KFOmCnqEu92Fr1Mu4mxK.woff2>; rel=preload; as=font"
		+Link "</mediawiki/resources/assets/fonts/KFOmCnqEu92Fr1Mu4mxK.woff2-test>; rel=preload; as=font"

Make sure to also enable the push directive (I don’t see it in your config, I suppose it might be part of the imported snippet though, please make sure to post your entire config next time):

Hi. Sorry I forgot to add the imported parts. That’s to setup logging and TLS which is common to several vhosts.
This is the main Caddyfile.

	# debug
	auto_https off
	log {
		output file /var/log/caddy/main/caddy_main.log {
			roll_size 100MiB
			roll_keep_for 100d
		format json
	servers :443 {
		protocol {
	servers :80 {
		protocol {

## Snippets

(main) {
	tls /etc/letsencrypt/live/{args.0}/fullchain.pem /etc/letsencrypt/live/{args.0}/privkey.pem {
		curves x25519 secp384r1 secp256r1
	log {
		output file /var/log/caddy/{args.0}_443.log {
			roll_size 100MiB
			roll_keep_for 100d
		format json
	encode zstd gzip

## Hosts section

import vhosts/*.caddy

I think you’re right that using +Link should work. Unfortunately adding +Link didn’t help and only the last item gets added to the output.

Currently I don’t want to enable push, as I don’t yet have a method to determine if it is likely that a client has the resources cached or not. Link preload should, however give browsers a chance to load it from cache, or request it early if needed.

Edit: with push enabled, caddy pushed the one Link only, just like above.

I verified this with nghttp -ns .

Alright, turns out you need to do it like this:

	header @preload +Link "</mediawiki/resources/assets/fonts/KFOmCnqEu92Fr1Mu4mxK.woff2>; rel=preload; as=font"
	header @preload +Link "</mediawiki/resources/assets/fonts/KFOmCnqEu92Fr1Mu4mxK.woff2-test>; rel=preload; as=font"

A separate header directive for each header you want to add, and + is necessary.

I’ll need to do a bit more digging to figure out if there’s actually support in the header handler (in JSON config) for setting more than one value for a header at the same time… it might be a Caddyfile parsing oversight :thinking:


Thanks. It works as expected now, both with and without push enabled.

Should I open an issue on github about this?

Happy New year :blush:

Sure, that would help remind me :+1:

You too! Happy New Year :tada:


I wrote the PR to fix it. Super simple.


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