Could my Caddyfile be improved?

Hi,

I handcrafted my Caddyfile, but I’m quite sure there could be ways to improve it, make it shorter or less verbose.

{
    email myemailhere
    default_sni removed
    key_type p384
}

(globalOptions) {
    log {
        output file /var/log/caddy/access.log
        format json
    }

    header {
        Strict-Transport-Security "max-age=63072000"
        X-Content-Type-Options nosniff
        X-Frame-Options "DENY"
        X-XSS-Protection "1; mode=block"
        Content-Security-Policy "upgrade-insecure-requests"
        Referrer-Policy no-referrer-when-downgrade
        Cache-Control "public, no-cache, max-age=0"
    }

    handle_errors {
        respond "{http.error.status_code} {http.error.status_text}"
    }

    encode gzip zstd
    tls myemailhere
}

(webp) {
    @acceptWebpJpg {
        header Accept image/webp*
        path *.jpg
    }
    handle @acceptWebpJpg {
        try_files {path}.jpg {path}.webp
    }

    @acceptWebpJpeg {
        header Accept image/webp*
        path *.jpeg
    }
    handle @acceptWebpJpeg {
        try_files {path}.jpeg {path}.webp
    }

    @acceptWebpPng {
        header Accept image/webp*
        path *.png
    }
    handle @acceptWebpPng {
        try_files {path}.png {path}.webp
    }
}

domain.dev, domain.me, www.domain.dev, www.domain.me {
    root * /www/domain
    file_server browse
    file_server /grabs/*
    import globalOptions
}

paste.domain.dev, paste.domain.me {
    reverse_proxy localhost:2345 {
        header_up Host {http.reverse_proxy.upstream.hostport}
        header_up X-Real-IP {http.request.remote.host}
    }
    import globalOptions
}

droppy.domain.dev, droppy.domain.me {
    reverse_proxy localhost:8989 {
        header_up Host {http.reverse_proxy.upstream.hostport}
        header_up X-Real-IP {http.request.remote.host}
    }
    import globalOptions
}

flood.domain.dev, flood.domain.me {
    reverse_proxy localhost:3000 {
        header_up Host {http.reverse_proxy.upstream.hostport}
        header_up X-Real-IP {http.request.remote.host}
    }
    import globalOptions
}

domain2.sx, domain3.sx, www.domain2.sx, www.domain3.sx {
    root * /www/domain2
    file_server
    import webp
    import globalOptions
}

domain4.fr, domain5.com, www.domain4.fr, www.domain5.com {
    root * /www/domain4
    file_server
    import webp
    import globalOptions
}

I have removed domain names and emails, but nothing is lost by not having those here

I’m concerned with the following:

  • The webp snippet. I implented this in the Caddyfile to reproduce what I had in my nginx config file. The intent is to serve to clients that have image/webp in their Accept header the image in webp format. If they can accept webp, we send the webp image instead of the jpg/png they requested. On disk, the file would be image.png.webp. It currently works, but I feel like it’s too many lines for a simple thing. If there was a way to OR inside the match block, that could be easier to implement.
  • Having to add www.domain to the sites. Before Caddy, I had a wildcard certificate from LE. Now, since Caddy won’t behave nicely with nsupdate, I’m settling for the TLS challenge. I don’t think there is a better way to do this, but if there is, I’m all ears.

Any other suggestions or tips is appreciated. Writing a Caddyfile to replace a nginx server is difficult, as there are not many examples around. I would love your input :slight_smile:

I’m not sure your webp stuff will work as expected currently, because {path} will contain the file extension, so it will try to look for /foo/bar.jpg.jpg on disk for example, then /foo.bar.jpg.webp. The fix is to use path_regexp to make capture groups. That also allows you to combine them all together:

    @acceptWebp {
        header Accept image/webp*
        path_regexp image ^(.*).(jpe?g|png)$
    }
    handle @acceptWebp {
        try_files {re.image.0}.{re.image.1} {re.image.0}.webp
    }

So if the request is /foo/bar.jpeg, the first capture group will be /foo/bar and the second will be jpeg, so you can use those pieces to try first the file as requested, else the .webp instead.

Edit: I misread what you want when I wrote the above, I think you actually just want this:

    @acceptWebp header Accept image/webp*
    handle @acceptWebp {
        try_files {path}.webp
    }

try_files only rewrites the request if a file is found on disk, and your webp files include the original file extension, so this is probably enough. This will prioritize .webp files if they exist, otherwise it will get served by file_server normally.


    file_server browse
    file_server /grabs/*

I would re-order these two lines to make it clearer how it behaves - Caddy will sort same-directives based on their path matcher, so it will handle them in this order:

    file_server /grabs/*
    file_server browse

First of all, are you sure you need these header_up lines? Have you tried without them? It might work just fine, as long as your upstream service doesn’t explicitly expect localhost, and if it handles X-Forwarded-For already (which is the header more typically used by proxies for revealing the client IP)

    reverse_proxy localhost:8989 {
        header_up Host {http.reverse_proxy.upstream.hostport}
        header_up X-Real-IP {http.request.remote.host}
    }

If you do need them, since this sort of pattern is repeated a lot, so you could use a snippet with arguments to shorten this if you like:

(proxyWithHeaders) {
    reverse_proxy {args.0} {
        header_up Host {http.reverse_proxy.upstream.hostport}
        header_up X-Real-IP {http.request.remote.host}
    }
}

Then use it like this:

import proxyWithHeaders localhost:8989

I’m not sure I understand the question. You’re doing it correctly already, by adding the www. domain to your sites. Are you looking to redirect it? If so you can see the examples here:

You can definitely still do this.

Could you elaborate? I’m not sure I understand.

Thanks for the reply! I’m the same person you’re helping with the example for handle_errors on github.

I’ll use your much shorter snippet. Seems like checking if it’s actually jpg/png/jpeg is superfluous if we are already being asked for webp.

I was wondering about this as well. The idea here (and the behaviour I had in nginx) was to have /grabs not autoindexed (or browse for caddy). I was not sure which one would take precedence, but it worked the way I wanted it to. I’ll rearrange those to make it clear

I do need X-Real-IP for sure. Host, i’m not sure, but I don’t want to spend the time and effort finding it out.

Thanks! I read about the arguments in the documentation, but did not think to use it here

I think I don’t know what I want better here. I’s mostly related to the wildcard I had, making having to specify all subdomains superflous

I have tried in the past (when caddy 2 got just released) to make use of the DNS challenge with nsupdate, which is still only implemented using lego/providers/dns/rfc2136/rfc2136.go at master · go-acme/lego · GitHub

I failed to make it work and I might try again when this is implemented with the new DNS modules. It’s not a terribly important thing, and is not worth the time to make work in it’s deprecated state.

Thanks for the help so far!

1 Like

Update to say that your suggestions, especially regarding webp, are all working and nothing broke as a result. Thanks again

2 Likes

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