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.

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