Caddy is the first and only web server to use HTTPS automatically and by default
caddyserver.com
caddyserver.com
I have moved all my servers from NGINX to Caddy for the pass few years and I couldn't be happier.
Also, I would like to give a shoutout to the team behind Caddy. They have been nothing but great about constantly shipping updates and being incredibly helpful in their community forum.
Put the following in your Caddyfile at the lowest scope to disable it:
{
admin off
}Edit, I just checked the Caddyfile for one of my sites. There is no config for redirecting HTTP to HTTPS is does it automatically. So this is entirely unnecessary.
This is unrelated to sites hosted using HTTP. I was clumsily using the term "HTTP" to refer to the fact that this configuration mechanism is based on HTTP-communication.
Seems counter to their objective of having secure defaults.
I'd imagine if someone already has local access to the server, it's already too late.
You could have a blind SSRF vulnerability in an application and while that's not great, it is difficult for an attacker to exploit successfully.
If the attacker knows or guesses you're hosting Caddy on the same machine, they know you most likely have an admin interface on localhost:2019 that they can use to make further local network requests and also makes it possible for them to access the results of their local network requests they were making through the blind SSRF vulnerability hypothesised above.
Basically, if you're not using it (and you shouldn't be using such functionality on a production machine), then you don't need it and should disable it, see: https://owasp.org/Top10/A05_2021-Security_Misconfiguration/
Actually, most everyone wants zero-downtime config reloads. The API is necessary to perform config reloads.
As others have said, you may use a unix socket instead for the admin endpoint. And see https://news.ycombinator.com/item?id=37482096, we plan to make that the default in certain distributions.
Of course it isn't. It could reload the config from the same path it loaded the config from in the first place. Like practically all other software has done for decades.
Happy to hear you're moving to sockets by default on *nix!
However, I'd like to point out that the default should be in the binary, not in the distros default environment variables, otherwise it won't reach people who build their own binary, and depending on how you start your Caddy server you may clear environment variables for that process, and end up with the insecure HTTP-based admin endpoint enabled by accident.
If you're correct that I'm overestimating users then what are you guys doing? You're expecting users to know how to secure their Caddy configuration when in reality most users probably have no idea that this API even exists, they'll put their config in Caddyfile, start the server, and be done with it.
We should be expecting that they don't know anything about the risks involved with leaving an unauthenticated HTTP API on localhost, and instead shipping a default that doesn't place their system and network at unnecessary risk.
Exactly, which is why the environment variable approach is perfectly fine. The env var will be set in the systemd config.
> You're expecting users to know how to secure their systems
Again, our view is that the TCP socket for admin is secure enough for 99.99% of users, and has been for over 3 years since Caddy v2 was released. We've still not seen any evidence of a practical exploit in the wild.
Also SSRF risks as mentioned elsewhere ...
Security follows the Swiss cheese model: each individual measure has known limitations but by layering them, you reduce the overall number of attack vectors.
Getting the server to make arbitrary HTTP requests is bad, yes, but limiting what the attacker can do with that makes it less dangerous if you somehow screw that one thing up.
A blind SSRF vulnerability (with payload control) in your application could be used to gain full control over the reverse proxy resulting in the attacker gaining full unfettered access to your network.
If you're not using it (and you shouldn't be using such functionality on a production machine), then you don't need it and should disable it, see: https://owasp.org/Top10/A05_2021-Security_Misconfiguration/
https://caddyserver.com/docs/json/admin/remote/access_contro...
admin listen unix//var/run/caddy/admin.sockWhat if rm would by default just delete everything, as it assumes that makes sense? Stupid comparison, I know, also a stupid default.
It would be more appropriate to handle signals, particularly SIGHUP. That's how most services have been handling reloads.
It's fine to offer an admin API, especially if I want a peer to be able to affect the local instance, but this shouldn't be the position init is placed in.
Put simply, the init process is what we depend on if everything else fails.
The API can still be there, I'm just asking for better integration where feasible. Signal handling on Linux/similar
It's silly to tell my init process to go out 'to the network' to do something it can do directly against the child.
I would not expect turning off an admin API to effectively limit my way to administer the process.
Services will generally ordain a path for a config, overridable with arguments. The same file used then is what is re-read on reload.
Argument/command line changing during a reload isn't a thing, that's restarting. We give it config files as an argument (or implicit default) so that can be reloaded.
It's uncommon to start a process with one file, decide you want a new file path, but keep the PID.
But it won't be possible to add signals support. We've thought hard about it but it's simply not a fit. There's discussion in GitHub: https://github.com/caddyserver/caddy/issues/3967
ie: admin interface disabled, I can't reload to bring it back... because that depends on it.
With sockets we gain a permission model; one simply being in the 'localhost' scope can't do funny/scary things - either a user or another service on the system.
Thank you for the discussion, I'll give it a read - have a meeting then I can finally use my computer to 'catch up'
I'm also a huge fan of Caddy's "handle" and "handle_path" directives for their simplicity.
One thing I will say against it though is that it does seem to run a little hotter than Nginx on my Pi4. Just random spikes here and there, whereas Nginx barely used to blip.
CPU Usage:
with NGINX - 15-20% with Caddy - 70-80%
I tried multiple tweaks but nothing helped to get NGINX-level performance. So, after a few weeks, we migrated back to NGINX.
That being said, I still absolutely love Caddy and use it in a few small scale apps.
- The DX it provides is amazing. - Creating a PHP-FPM reverse proxy is just a couple of lines. - Generating SSL certificates on the server is a breeze. With NGINX, you have to mess with other software like certbot. - It just works :)
I'd love to capture a profile next time you have a chance. We've been primarily focused on features until just about 6 months ago, so we have started making significant optimizations only recently.
I set up a whole reverse proxy stack on my personal webserver (thanks to the swag docker image: nginx + auto-renewing let's encrypt + fail2ban). I hadn't really done any web stuff before, certainly nothing on the public Internet.
I didn't have time to read the nginx doc so I had ChatGPT do most of it. I'd then ask for changes depending on the challenges of a specific webapp (for example, not requiring basic_auth on certain routes that were using the tool's own auth).
I realize this is a simple example, but if I was able to achieve all my goals quickly with zero prior experience, surely it can't be that hard for someone who does this stuff for a living.
Good luck with such setups. Best case they fail, worst case they become security nightmares.
What got me away from using it:
- the directives feel intuitive but as soon as I needed a complex config it all became a chain of very implicit strings
- the caddy author(s) decided few years ago to add custom http header with their sponsors[0]. That header could not be removed, it's no longer present in current Caddy but the bad taste still remains.
Adding a sponsor header is harmless (albeit useless IMO). For me that would be no reason to not choosing this software, and certainly no ground for having a 'bad taste'.
It's also a security risk if your web server is the only one doing it, as it is a way for an attacker to fingerprint the web server software in use.
I understand and agree with melx's view here completely, even if I do feel Caddy's strengths outweigh it's weaknesses.
devs presumably
I thought it was a good idea at the time. ¯\_(ツ)_/¯
Taints the pool with the vibe of "We can do this; we will do this and you schmucks can't do anything about it because it's in the EULA".
Regardless to how harmless it is. It's still an unprofessional quality to implement such and then deny the ability to disable it.
Same example as if when you honked your cars horn it tooted the car model. You'd be annoyed right?
Sure, its harmless because how often do you use your car horn but you expect a car horn to horn not advertise the model your driving.
Let's hope Elon doesn't read this.
Harmless or not - I think it's worth looking past this point. Maybe the http header was a way for them to search the internet and find *commercial* sites that didn't pay for Caddy license? Not very pro behaviour.
[0] https://web.archive.org/web/20180216153020/https://caddyserv...
When they say it's "fast" they mean relative to something like Python.
Through in this case I would say it being "fast enough" for many use-cases is the relevant part.
Some GC'd languages also offer tools to manage these problems, of course, all I mean to say is that there are a lot of factors at play here.
But automatic https does look convenient, no need to have separate certbot running
The configuration is simply:
Header X-Clacks-Overhead "GNU Terry Pratchett"
labels:
- traefik.enable=true
- traefik.http.routers.foundryvtt-http.entrypoints=web
- traefik.http.routers.foundryvtt-http.rule=Host(`vtt.xxx.nl`)
- traefik.http.routers.foundryvtt-http.middlewares=foundryvtt-https
- traefik.http.middlewares.foundryvtt-https.redirectscheme.scheme=https
- traefik.http.routers.foundryvtt.middlewares=foundryvtt-auth
- traefik.http.middlewares.foundryvtt-auth.basicauth.users=${foundryvtt-BASIC_AUTH}
- traefik.http.routers.foundryvtt.entrypoints=websecure
- traefik.http.routers.foundryvtt.rule=Host(`vtt.xxx.nl`)
- traefik.http.routers.foundryvtt.tls=true
- traefik.http.routers.foundryvtt.tls.certresolver=mytlschallenge
- traefik.http.services.foundryvtt.loadbalancer.server.port=30000
Now I just add the containers (by name), no labels, and map Caddy to their port, like so (in the Caddyfile): data.xxx.com {
reverse_proxy projectsend:80
}
or, this snippet refers to a WordPress container with BasicAuth in front of it: restricted.xxxx.com {
root * /var/www/html/restricted.xxxx.com/wordpress
php_fastcgi wordpress-xxxx-restricted:9000 {
root /var/www/html
}
basicauth /* {
xxx $xx$x05xxxxxxxxx.xx
}
file_server
}
Here's just an index.html (from Hugo in this case) in some dir: blog.xxx.nl {
# Set this path to your site's directory.
root * /var/www/html/blog.xxx.nl
# Enable the static file server.
file_server
}
I love the simplicity.12 factor app did untold damage to the industry convincing smart-but-inexperienced developers that key-value-only systems are somehow good way to configure anything more complex than "this app needs server, password and user"
I mean, I want https, and I want it in front of my standard docker container that listens on some random port. Caddy requires me to enter only exactly what I need, no more (container name and port and required function (rev-proxy), 2 lines, boom).
I really need to grab a few beers and check out the documentation, my current docker setup is a cargo-cult traefik label monster I just copied from a friend =)
It works, but I really don't know why or how
https://github.com/lucaslorentz/caddy-docker-proxy
I actually do this, because I kinda like having the proxy config right next to the app config in my Compose file, but I also dislike how much manual configuration Traefik needs. Downside is you need to know how to write Caddyfile (easy enough) and then also know how to write labels so CDP translates them into the correct Caddyfile (also easy enough, but could be annoying if you're learning both at the same time). Upshot is that once you know how it translates and you know what you need to write, it works just like Traefik but with just two labels, and I think that's pretty neat.
Caddy can support a surprising amount of weird and wonderful configurations, too.
labels:
caddy: subdomain.${DOMAIN_NAME}
caddy.reverse_proxy: "{{upstreams 8000}}"And, having mostly worked with Rails, PHP and Python http services, the proxying webserver is hardly ever a performance issue. You can stick the slowest webserver in front of a Rails (Rack) app and still be unable to measure the latency it adds.
I've been using caddy for years and while it's slower in benchmarks, than others, I've never had the practical situation where that mattered.
But I see and agree with your overall point. Caddy is like a breath of fresh air, and just as useful!
Has it improved since then?
disclaimer: I don't remember the details, I was just told to use nginx because caddy is problematic, so I built the system with nginx open source.
eggcorn or autocorrect?
(English is not my mother tongue, which is a phonetic language, Hungarian, and while I know the difference very well, being tired causes these kinds of typos sometimes)
Not sure if I’d make those mistakes in German as well, as I write far less in it ;)
Glad to hear you use Caddy! :)
But, instead of handing the user a empty slate with nothing, it should contain a note saying “look, these are the recommended options”. If the initial config is interactive, it could even prompt to activate them: “want to use compression?” - “yes”, etc.
Interactive configuration is easier said than done. We don't realistically have the time to build and maintain that on top of the core program and config. We're already stretched pretty thin.
That still gets served over HTTPS.
The only time HTTPS isn't used is if a host portion is missing entirely (IP or name).
* popular webserver anyway; we can't reasonably count yours with only 6 github stars that we've never heard of :P
I recall once needing to help a new person in another team setup TLS after they had tried to do it unsuccessfully themselves in some configuration (that might have had a networking setup where HTTP-01 for ACME doesn't work, actually). I just started recording my desktop, grabbed a server from Hetzner live and thanks to Caddy could give them an example of how things should work with all of the steps in like 5 minutes total.
Nowadays I use Apache (mod_md) for my personal needs due to some plugins I need, it actually makes me wonder why Nginx doesn't seem to have integrated support for ACME yet, even if certbot is serviceable too. Either way, props to Caddy for raising the bar for web servers.
> The documentation is kinda crap
We continually get comments like this, but we rarely get elaboration on why people think this. Please explain what you mean. What's crap about it? We spend a lot of time improving the docs, and without specific feedback we're surprised to hear this.
e.g. In nginx, I use "resolver 127.0.0.11 valid=30s" so "proxy_pass {container}:80" will only cache the {container}'s IP address for 30s
From the forums it looks like Caddy doesn’t explicitly define any DNS behaviour, it relies on Golangs defaults, which in turn simply uses whatever the host provides. I.e. whatever IP your host DNS resolution returns is used, and Caddy doesn’t cache internally, it relies on your hosts DNS cache. It’s reasonable to assume that any modern OS respects DNS TTL, and for something like Docker it’s gonna be doing a lookup on every request (which should be pretty much instant, as everything is on the same machine).
https://caddy.community/t/proxy-dns-resolver-mechanism/5934
https://stackoverflow.com/questions/40251727/does-go-cache-d...
I.e. it makes Caddy act a bit more like Traefik. Most of the time, you'll just add the label `caddy.reverse_proxy={{upstreams http 8080}}` to your containers and the plugin will regenerate Caddy's configuration whenever the container is modified.
If DNS-01 is not an option or to complicated, this saves you from exposing a host to the internet for no good reason.
Could you open an issue to discuss your requirements? We'll take a look at solutions.
Thank you for your sponsorship!!
Aside from creating CertMagic, I think CertMagic has a few benefits over autocert. It is designed to scale to thousands of certificates. It will staple OCSP for you automatically. It can obtain certificates "on demand" during handshakes. It is more robust to failures and can keep sites up even when other ACME libs let you down. CertMagic supports all challenge types; I think autocert only does TLS-ALPN challenge, which requires port 443. There's a lot of other improvements and enhancements that autocert is lacking IMO. A scan of the readme should help illustrate: https://pkg.go.dev/github.com/caddyserver/certmagic#section-...
But I guess use what works for you!
-are non-empty
-consist only of alphanumerics, hyphens, dots, and wildcard (*)
-do not start or end with a dot (RFC 1034)
Someone help me understand this part...didn't know this
Server: caddy
Header that is impossible to turn off since it's hardcoded here: https://github.com/caddyserver/caddy/blob/master/modules/cad...The developer's annoying response is "it doesnt improve privacy or security, so we won't give you the option to remove it".
With Caddy, you just need:
header -Server
in your config. header -Server
However as this isn't global configuration it'll tend to pop back up in implicit configs like HTTP redirects and error handling if not overridden.https://caddyserver.com/docs/running#unit-files
Or are you saying they should be included with the download?
:2080
reverse_proxy :9000
It also has a nice one line shortcut directive which applies to the vast majority of php sites out there.
Here's some older instructions for zerossl where email seems to be necessary. (https://caddy.community/t/using-zerossls-acme-endpoint/9406)
Sure if it is a 3rd party app, but if you're writing one just adding it to your app and simplifying everything around it is worth compared to adding additional component to deploy
$ caddy reverse-proxy --from :2080 --to :9000