Need help fixing a persistent Caddyfile validation error

1. The problem I’m having:

I’m experiencing an issue with my Caddyfile configuration where the header directive is being parsed as a site address instead of a known directive. This error persists despite ensuring all directives are within site blocks. The specific error message is:

Error: adapting config using caddyfile: /etc/caddy/Caddyfile:35: parsed 'header' as a site address, but it is a known directive; directives must appear in a site block

2. Error messages and/or full log output:

The full error message is shown above. I’ve checked the logs using journalctl -u caddy --no-pager | less +G, but there are no additional details beyond the error message.

3. Caddy version:

I’m using Caddy version 2.9.1, installed via the caddy-custom AUR package.

4. How I installed and ran Caddy:

a. System environment:

  • OS: Arch Linux ARM
  • Architecture: ARM
  • Relevant versions: Caddy 2.9.1, Authelia v4.38.19
  • Systemd: Yes

b. Command:

I run Caddy using a systemd service:

sudo systemctl start caddy

c. Service/unit/compose file:

My Caddy service file is as follows:

[Unit]
Description=Caddy
Documentation=https://caddyserver.com/docs/
After=network.target network-online.target
Requires=network-online.target

[Service]
Type=notify
User=caddy
Group=caddy
ExecStart=/usr/bin/caddy run --environ --config /etc/caddy/Caddyfile
ExecReload=/usr/bin/caddy reload --config /etc/caddy/Caddyfile --force
TimeoutStopSec=5s
LimitNOFILE=1048576
PrivateTmp=true
ProtectSystem=full
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target

d. My complete Caddy config:

{
    email laniecarmelo@gmail.com
    debug
    acme_dns cloudflare JpbcXkgbLQA3hSkl6WOWd_sm9tQvseuDGsf0mWsQ
    http_port 80
    https_port 443
    admin :2019 {
        origins 127.0.0.1:2019 0.0.0.0:2019 stormux:2019 caddy.laniecarmelo.tech
    }
}

(logconfig) {
    log {
        output stdout
        format json
    }
}

(proxy_config) {
    header_up Host {http.request.host}
    header_up X-Real-IP {http.request.remote}
}

(authelia_middleware) {
    forward_auth 127.0.0.1:9091 {
        uri /api/verify
        copy_headers Remote-User Remote-Email Remote-Groups Authorization
        header_up X-Forwarded-Method {method}
        header_up X-Forwarded-Proto {scheme}
        header_up X-Original-URL {orig_uri}
    }
}

(security_headers) {
    header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    header X-XSS-Protection "1; mode=block"
    header X-Content-Type-Options "nosniff"
    header Referrer-Policy "strict-origin-when-cross-origin"
    header Permissions-Policy "geolocation=(), midi=(), sync-xhr=(), microphone=(), camera=(), magnetometer=(), gyroscope=(), fullscreen=(self), payment=()"
}

# Authentication Service
auth.laniecarmelo.tech {
    reverse_proxy 127.0.0.1:9091 {
        import proxy_config
    }
    import logconfig
    import security_headers
}

# Grouped Services with Simple Reverse Proxy
home.laniecarmelo.tech {
    import authelia_middleware
    reverse_proxy 127.0.0.1:3000 {
        import proxy_config
    }
    import logconfig
    import security_headers
}

adguard.laniecarmelo.tech {
    @api {
        path /control* /api*
    }
    route @api {
        reverse_proxy 127.0.0.1:3001 {
            import proxy_config
        }
    }
    route {
        import authelia_middleware
        reverse_proxy 127.0.0.1:3001 {
            import proxy_config
        }
    }
    import logconfig
    import security_headers
}

portainer.laniecarmelo.tech {
    @api {
        path /api/* /auth/*
    }
    route @api {
        reverse_proxy 127.0.0.1:3002 {
            import proxy_config
        }
    }
    route {
        import authelia_middleware
        reverse_proxy 127.0.0.1:3002 {
            import proxy_config
        }
    }
    import logconfig
    import security_headers
}

dozzle.laniecarmelo.tech {
    import authelia_middleware
    reverse_proxy 127.0.0.1:8080 {
        import proxy_config
    }
    import logconfig
    import security_headers
}

uptime.laniecarmelo.tech {
    @api {
        path /api/* /status/* /status-page/*
    }
    route @api {
        reverse_proxy 127.0.0.1:3006 {
            import proxy_config
        }
        redir /api/status-page/* /api/status/{path_1}
    }
    route {
        import authelia_middleware
        reverse_proxy 127.0.0.1:3006 {
            import proxy_config
        }
    }
    import logconfig
    import security_headers
}

recipes.laniecarmelo.tech {
    @api {
        path /api/*
    }
    route @api {
        reverse_proxy 127.0.0.1:8081 {
            import proxy_config
        }
    }
    route {
        import authelia_middleware
        reverse_proxy 127.0.0.1:8081 {
            import proxy_config
        }
    }
    handle /media/* {
        root * /opt/docker/tandoor/media_files
        file_server {
            hide .env
            hide docker-compose.yml
        }
    }
    import logconfig
    import security_headers
}

bookmarks.laniecarmelo.tech {
    redir https://{host}{uri}
    reverse_proxy 127.0.0.1:3009 {
        header_up Host {host}
        header_up X-Real-IP {remote}
        header_up X-Forwarded-Proto {scheme}
        header_response Location {replace "http://bookmarks.laniecarmelo.tech" "https://bookmarks.laniecarmelo.tech"}
    }
    import security_headers
    import logconfig
}

git.laniecarmelo.tech {
    @api {
        path /api/* /api/v1/* /login/oauth/*
    }
    route @api {
        reverse_proxy 127.0.0.1:3008 {
            header_up Remote-User {http.auth.user.id}
            header_up Remote-Email {http.auth.user.email}
            import proxy_config
        }
    }
    route {
        import authelia_middleware
        reverse_proxy 127.0.0.1:3008 {
            header_up Remote-User {http.auth.user.id}
            header_up Remote-Email {http.auth.user.email}
            import proxy_config
        }
    }
    import logconfig
    import security_headers
}

irc.laniecarmelo.tech {
    import authelia_middleware
    reverse_proxy 127.0.0.1:3011 {
        import proxy_config
    }
    import logconfig
    import security_headers
}

code.laniecarmelo.tech {
    import authelia_middleware
    reverse_proxy 127.0.0.1:3012 {
        import proxy_config
    }
    import logconfig
    import security_headers
}

wiki.laniecarmelo.tech {
    import authelia_middleware
    reverse_proxy 127.0.0.1:5000 {
        import proxy_config
    }
    import logconfig
    import security_headers
}

glances.laniecarmelo.tech {
    @api {
        path /api/*
    }
    route @api {
        reverse_proxy 127.0.0.1:61208 {
            import proxy_config
        }
    }
    route {
        import authelia_middleware
        reverse_proxy 127.0.0.1:61208 {
            import proxy_config
        }
        encode gzip
    }
    encode gzip
    handle_errors {
        respond "Accessibility-first error: {err.status_code}"
    }
    import logconfig
    import security_headers
}

beszel.laniecarmelo.tech {
    @api {
        path /api/* /metrics*
    }
    route @api {
        reverse_proxy 127.0.0.1:8090 {
            import proxy_config
        }
    }
    route {
        import authelia_middleware
        reverse_proxy 127.0.0.1:8090 {
            import proxy_config
        }
    }
    import logconfig
    import security_headers
}

caddy.laniecarmelo.tech {
    @api {
        path /reverse_proxy/*
    }
    route @api {
        reverse_proxy 127.0.0.1:2019 {
            import proxy_config
        }
    }
    route {
        import authelia_middleware
        reverse_proxy 127.0.0.1:2019 {
            import proxy_config
        }
    }
    import logconfig
    import security_headers
}

rss.laniecarmelo.tech {
    @api {
        path /api/* /reader/api/* /v1/*
    }
    route @api {
        reverse_proxy 127.0.0.1:3010 {
            import proxy_config
        }
    }
    route {
        import authelia_middleware
        reverse_proxy 127.0.0.1:3010 {
            import proxy_config
        }
    }
    import logconfig
    import security_headers
}

# Fallback for undefined subdomains
*.laniecarmelo.tech {
    encode gzip
    header Content-Security-Policy "default-src 'self';"
    respond "Not Found" 404
}

5. Links to relevant resources:

Troubleshooting Steps Taken:

  1. Checked Directive Placement: Ensured all header directives are within site blocks.
  2. Reviewed Snippets: Verified that snippets are correctly defined and used within site blocks.
  3. Validated Indentation: Confirmed proper indentation and no stray characters.
  4. Simplified Configuration: Removed unnecessary directives to isolate the issue.

If you have any suggestions or insights, please let me know!

Try to replace this:

(security_headers) {
    header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    header X-XSS-Protection "1; mode=block"
    header X-Content-Type-Options "nosniff"
    header Referrer-Policy "strict-origin-when-cross-origin"
    header Permissions-Policy "geolocation=(), midi=(), sync-xhr=(), microphone=(), camera=(), magnetometer=(), gyroscope=(), fullscreen=(self), payment=()"
}

with this:

(security_headers) {
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        X-XSS-Protection "1; mode=block"
        X-Content-Type-Options "nosniff"
        Referrer-Policy "strict-origin-when-cross-origin"
        Permissions-Policy "geolocation=(), midi=(), sync-xhr=(), microphone=(), camera=(), magnetometer=(), gyroscope=(), fullscreen=(self), payment=()"
    }
}

See if it helps.

1 Like

No, no difference:

Error

[lanie@stormux ~] $ sudo caddy reload --config /etc/caddy/Caddyfile 
2025/02/24 20:34:25.804 INFO    using config from file  {"file": "/etc/caddy/Caddyfile"}
Error: adapting config using caddyfile: /etc/caddy/Caddyfile:35: parsed 'header' as a site address, but it is a known directive; directives must appear in a site block
[lanie@stormux ~] $ 

Updated Caddyfile

{
    email laniecarmelo@gmail.com
    debug
    acme_dns cloudflare JpbcXkgbLQA3hSkl6WOWd_sm9tQvseuDGsf0mWsQ
    http_port 80
    https_port 443
    admin :2019 {
        origins 127.0.0.1:2019 0.0.0.0:2019 stormux:2019 caddy.laniecarmelo.tech
    }
}

(logconfig) {
    log {
        output stdout
        format json
    }
}

(proxy_config) {
    header_up Host {http.request.host}
    header_up X-Real-IP {http.request.remote}
}

(authelia_middleware) {
    forward_auth 127.0.0.1:9091 {
        uri /api/verify
        copy_headers Remote-User Remote-Email Remote-Groups Authorization
        header_up X-Forwarded-Method {method}
        header_up X-Forwarded-Proto {scheme}
        header_up X-Original-URL {orig_uri}
    }
}

(security_headers) {
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        X-XSS-Protection "1; mode=block"
        X-Content-Type-Options "nosniff"
        Referrer-Policy "strict-origin-when-cross-origin"
        Permissions-Policy "geolocation=(), midi=(), sync-xhr=(), microphone=(), camera=(), magnetometer=(), gyroscope=(), fullscreen=(self), payment=()"
    }
}

# Authentication Service
auth.laniecarmelo.tech {
    reverse_proxy 127.0.0.1:9091 {
        import proxy_config
    }
    import logconfig
    import security_headers
}

# Grouped Services with Simple Reverse Proxy
home.laniecarmelo.tech {
    import authelia_middleware
    reverse_proxy 127.0.0.1:3000 {
        import proxy_config
    }
    import logconfig
    import security_headers
}

adguard.laniecarmelo.tech {
    @api {
        path /control* /api*
    }
    route @api {
        reverse_proxy 127.0.0.1:3001 {
            import proxy_config
        }
    }
    route {
        import authelia_middleware
        reverse_proxy 127.0.0.1:3001 {
            import proxy_config
        }
    }
    import logconfig
    import security_headers
}

portainer.laniecarmelo.tech {
    @api {
        path /api/* /auth/*
    }
    route @api {
        reverse_proxy 127.0.0.1:3002 {
            import proxy_config
        }
    }
    route {
        import authelia_middleware
        reverse_proxy 127.0.0.1:3002 {
            import proxy_config
        }
    }
    import logconfig
    import security_headers
}

dozzle.laniecarmelo.tech {
    import authelia_middleware
    reverse_proxy 127.0.0.1:8080 {
        import proxy_config
    }
    import logconfig
    import security_headers
}

uptime.laniecarmelo.tech {
    @api {
        path /api/* /status/* /status-page/*
    }
    route @api {
        reverse_proxy 127.0.0.1:3006 {
            import proxy_config
        }
        redir /api/status-page/* /api/status/{path_1}
    }
    route {
        import authelia_middleware
        reverse_proxy 127.0.0.1:3006 {
            import proxy_config
        }
    }
    import logconfig
    import security_headers
}

recipes.laniecarmelo.tech {
    @api {
        path /api/*
    }
    route @api {
        reverse_proxy 127.0.0.1:8081 {
            import proxy_config
        }
    }
    route {
        import authelia_middleware
        reverse_proxy 127.0.0.1:8081 {
            import proxy_config
        }
    }
    handle /media/* {
        root * /opt/docker/tandoor/media_files
        file_server {
            hide .env
            hide docker-compose.yml
        }
    }
    import logconfig
    import security_headers
}

bookmarks.laniecarmelo.tech {
    redir https://{host}{uri}
    reverse_proxy 127.0.0.1:3009 {
        header_up Host {host}
        header_up X-Real-IP {remote}
        header_up X-Forwarded-Proto {scheme}
        header_response Location {replace "http://bookmarks.laniecarmelo.tech" "https://bookmarks.laniecarmelo.tech"}
    }
    import security_headers
    import logconfig
}

git.laniecarmelo.tech {
    @api {
        path /api/* /api/v1/* /login/oauth/*
    }
    route @api {
        reverse_proxy 127.0.0.1:3008 {
            header_up Remote-User {http.auth.user.id}
            header_up Remote-Email {http.auth.user.email}
            import proxy_config
        }
    }
    route {
        import authelia_middleware
        reverse_proxy 127.0.0.1:3008 {
            header_up Remote-User {http.auth.user.id}
            header_up Remote-Email {http.auth.user.email}
            import proxy_config
        }
    }
    import logconfig
    import security_headers
}

irc.laniecarmelo.tech {
    import authelia_middleware
    reverse_proxy 127.0.0.1:3011 {
        import proxy_config
    }
    import logconfig
    import security_headers
}

code.laniecarmelo.tech {
    import authelia_middleware
    reverse_proxy 127.0.0.1:3012 {
        import proxy_config
    }
    import logconfig
    import security_headers
}

wiki.laniecarmelo.tech {
    import authelia_middleware
    reverse_proxy 127.0.0.1:5000 {
        import proxy_config
    }
    import logconfig
    import security_headers
}

glances.laniecarmelo.tech {
    @api {
        path /api/*
    }
    route @api {
        reverse_proxy 127.0.0.1:61208 {
            import proxy_config
        }
    }
    route {
        import authelia_middleware
        reverse_proxy 127.0.0.1:61208 {
            import proxy_config
        }
        encode gzip
    }
    encode gzip
    handle_errors {
        respond "Accessibility-first error: {err.status_code}"
    }
    import logconfig
    import security_headers
}

beszel.laniecarmelo.tech {
    @api {
        path /api/* /metrics*
    }
    route @api {
        reverse_proxy 127.0.0.1:8090 {
            import proxy_config
        }
    }
    route {
        import authelia_middleware
        reverse_proxy 127.0.0.1:8090 {
            import proxy_config
        }
    }
    import logconfig
    import security_headers
}

caddy.laniecarmelo.tech {
    @api {
        path /reverse_proxy/*
    }
    route @api {
        reverse_proxy 127.0.0.1:2019 {
            import proxy_config
        }
    }
    route {
        import authelia_middleware
        reverse_proxy 127.0.0.1:2019 {
            import proxy_config
        }
    }
    import logconfig
    import security_headers
}

rss.laniecarmelo.tech {
    @api {
        path /api/* /reader/api/* /v1/*
    }
    route @api {
        reverse_proxy 127.0.0.1:3010 {
            import proxy_config
        }
    }
    route {
        import authelia_middleware
        reverse_proxy 127.0.0.1:3010 {
            import proxy_config
        }
    }
    import logconfig
    import security_headers
}

# Fallback for undefined subdomains
*.laniecarmelo.tech {
    encode gzip
    header Content-Security-Policy "default-src 'self';"
    respond "Not Found" 404
}

This line is tripping the adapter because of the braces { }

header_response Location {replace "http://bookmarks.laniecarmelo.tech" "https://bookmarks.laniecarmelo.tech"}

Surround them with backticks ` on both ends

I tried a few things:

header_response Location `{replace "http://bookmarks.laniecarmelo.tech" "https://bookmarks.laniecarmelo.tech"}`

I also tried putting both addresses in backticks and moving the backticks inside the braces.

Errors:

[lanie@stormux ~] $ sudo caddy reload --config /etc/caddy/Caddyfile 
2025/02/24 21:06:14.963 INFO    using config from file  {"file": "/etc/caddy/Caddyfile"}
2025/02/24 21:06:14.968 WARN    caddyfile       Unnecessary header_up X-Forwarded-Proto: the reverse proxy's default behavior is to pass headers to the upstream
2025/02/24 21:06:14.968 WARN    caddyfile       Unnecessary header_up X-Forwarded-Proto: the reverse proxy's default behavior is to pass headers to the upstream
2025/02/24 21:06:14.969 WARN    caddyfile       Unnecessary header_up X-Forwarded-Proto: the reverse proxy's default behavior is to pass headers to the upstream
2025/02/24 21:06:14.970 WARN    caddyfile       Unnecessary header_up X-Forwarded-Proto: the reverse proxy's default behavior is to pass headers to the upstream
2025/02/24 21:06:14.970 WARN    caddyfile       Unnecessary header_up X-Forwarded-Proto: the reverse proxy's default behavior is to pass headers to the upstream
2025/02/24 21:06:14.971 WARN    caddyfile       Unnecessary header_up X-Forwarded-Proto: the reverse proxy's default behavior is to pass headers to the upstream
2025/02/24 21:06:14.973 WARN    caddyfile       Unnecessary header_up X-Forwarded-Proto: the reverse proxy's default behavior is to pass headers to the upstream
Error: adapting config using caddyfile: parsing caddyfile tokens for 'reverse_proxy': unrecognized subdirective header_response, at /etc/caddy/Caddyfile:162
[lanie@stormux ~] $ sudoedit /etc/caddy/Caddyfile
[lanie@stormux ~] $ sudo caddy reload --config /etc/caddy/Caddyfile 
2025/02/24 21:07:15.437 INFO    using config from file  {"file": "/etc/caddy/Caddyfile"}
Error: adapting config using caddyfile: unexpected EOF, at /etc/caddy/Caddyfile:162
[lanie@stormux ~] $ sudoedit /etc/caddy/Caddyfile
[lanie@stormux ~] $ sudo caddy reload --config /etc/caddy/Caddyfile 
2025/02/24 21:08:27.507 INFO    using config from file  {"file": "/etc/caddy/Caddyfile"}
Error: adapting config using caddyfile: /etc/caddy/Caddyfile:35: parsed 'header' as a site address, but it is a known directive; directives must appear in a site block
[lanie@stormux ~] $ 

Well, header_response doesn’t exist. I don’t know where you got it from.

You might want header_down.

1 Like

After following Mohammed’s latest comment, if you’re still having problems, post new Caddy logs. Enough has changed to justify new logs.