233 comments

[ 3.6 ms ] story [ 248 ms ] thread
I'd imagine most of everyone here knows about Caddy. Even mholt. :)
Interesting. Is this supposed to be an NGINX/reverse-proxy replacement, or is it complementary?
My understanding is that it is standalone.
It's a standalone replacement for NGINX / Apache. I swapped over to it years ago and it's been rock solid, handles HTTPS certificates via ZeroSSL, and config files are very simple to set up. The author of the project is also really responsive.
despite knowing what caddy is, this site turns me off for all the marketing fluff.

since when was hn for ads? there's nothing notably technical on the page

I agree that the website isn't very appealing for techies. Still what I use to serve my website, so simple a chimp could deploy with it; though I'm kinda dreading the moment I'll put cgit behind it.
> By default, Caddy automatically obtains and renews TLS certificates for all your sites.

This is pretty technical, and is an absolute game changer for local TLS setups. Three paragraphs under this quote go into more details.

Then a third of the way down the page there's a way to try it out live by changing your DNS records. Right after that are a few config samples. Then there are links to three different white papers. Then more code samples. Then more more code samples.

You get the idea.

What exactly are you missing here as far as technical stuff? Is it just that they add exciting fonts and stuff on top of the technical stuff?

it's all just marketing fluff
r u sure you want the technical stuff?
it looks like any other marketing site, no talk of how or why anything in particular is implemented the way it is, doesn't admit to any tradeoffs or limitations, all positive spin designed to lure you in.
Your comments are more devoid of technical details than the site is. People keep asking you to clarify what you mean and you seem unable to pin down exactly what is lacking.
Not sure how this is "fluff". That's a feature that has significant appeal for me as a sysadmin. The landing page is full of demos and technical features relevant for anyone who needs an HTTP server.

It took me ten minutes with Caddy to replace five years of Apache+Nginx. That was three years ago.

> game changer for local TLS setups

I wouldn't call it a game changer, because you have to expose port 80 and 443 to public internet to get a real certificate. If you can't do that then Caddy signs its own certificates. That means you have to install the root certificate... this is hard to do in most companies.

No you don't. DNS challenges are a thing, and they're easy to set up. That's how my local is configured.
Last year a coworker mentioned Caddy, so I decided to set it up on a spare box just to see how well it worked with WordPress, PHP etc. It did okay. I didn't do any big tests with it but it seemed to work well enough, and was super simple to configure. It does seem quite niche however.
I have had "move our PHP infrastructure to FrankenPHP" on my todo list for the last year or two - it's Caddy combined with PHP in Go
I did exactly this for my startup (https://easy.green). I'm very happy with this setup so far, with the code embedding feature for on premise delivery. I had to disable the worker mode though, it caused issues with uncommon features in the ORM.
Yep I've used franken a few times it's the easiest php setup one could imagine
> It did okay. I didn't do any big tests with it but it seemed to work well enough, and was super simple to configure.

What issue/experience stop you from saying "it did great"?

Nothing in particular, it just did what it was supposed to do quite easily. I didn't have the opportunity to load test it and the small VM it was one would have been a bigger bottleneck, I think.
One area we have found Caddy invaluable is for local testing of APIs with HTTP2 during development. Most dev servers are HTTP1 only, and so you are limited to max of 6 concurrent connections to localhost. HTTP2 requires SSL, which would normally make it a PITA to test/setup locally for development.

Throw a Caddy reverse proxy in front of your normal dev server and you immediately get HTTP2 via the root certificate it installs in your OS trust store. (https://caddyserver.com/docs/automatic-https)

We (ElectricSQL) recommend it for our users as our APIs do long polling, which with HTTP2 doesn't lock up those 6 concurrent connections.

I've also found that placing it in front of Vite for normal development makes reloads much faster. Vite uses the JS module system for loading individual files in the browser with support for HMR (hot module replacement), this can result in a lot of concurrent requests for larger apps, creating a queue for those files on the six connections. Other bundlers/build tools bundle the code during development, reducing the number of files loaded into the browser, this created a bit of a debate last year on which is the better approach. With HTTP2 via Caddy in front of Vite you solve all those problems!

> HTTP2 requires SSL

Strictly speaking it doesn't, unencrypted HTTP2 is allowed per the spec (and Caddy supports that mode) but the browsers chose not to support it so it's only really useful when testing non-browser clients or routing requests between servers. HTTP3 does require encryption for reals though, there's no opting out anymore.

Yep, it's really disappointing they didn't decide to support it for localhost.
I think the reason why they have decided to not support it over plain text is because they would have to detect if the server answers with HTTP/2 or HTTP/1.1, which could be complicated. This is not needed when using TLS since ALPN is used instead
This is meh excuse. If you want your browser to connect to a gopher server, you type gopher://example.com. If you want to use http2, http2://example.com should work. (I know, I know, everyone removed Gopher support a few years ago. Same idea though.)

Having said all that, I just copied the certs out of here https://cs.opensource.google/go/go/+/refs/tags/go1.24.0:src/... and use them to do browser/http2 stuff locally. Why steal Go's certificates? Because it took 1 second less than making my own!

Hey, thanks for this. It saves me even more than 1 second!
Which browsers/libraries trust these? Or does the go tool chain install them?
Nothing trusts them, they're just regular self-signed certificates. There is no benefit to using these over your own self-signed certificates except that you don't have to ask your LLM for the commands to generate them ;)
And of course once you trust them on localhost, you expose yourself to some risk, since the whole world can get a copy of the key.
Using http2 as protocol in the URL makes the initial transition complex: One can't share links between different users and clients and from other websites with a guarantee of the "best" experience for the user. Not to mention all old links outside the control of the server operator.
No, that's rather easy for up-to-date browsers. The real reason is that dumb middle proxies might cache/mangle HTTP/2 requests to the point that it simply fails hard, but encryption makes those concerns go away. Yes, there are proxies that can decrypt TLS, but due to how negotiation of HTTP/2 works in TLS (ALPN), dumb proxies will simply not send the signals that it's HTTP/2 capable.
Another way is to create a regular DNS name, and ave it redirect to localhost. If you are unable or unwilling to do so, there are free DNS services like https://traefik.me/ that provide you with a real domain name and related certificates.

I personally use traefik.me for my hobbyist fiddling, and I have a working HTTP/2 local development experience. It's also very nice to be able to build for production and test the performance locally, without having to deploy to a dev environment.

Aren't you exposing your dev instance to the world then? Not worried about that?
DNS to a local address doesn’t expose anything.

For example, postulate a DNS entry of myTopSecrets mapped to localhost. If you use it, it will be routed to your own computer. If someone else uses it, they would be routed to their own computer. The same follows for IP addresses within your local area network.

Unless you did extra work outside the scope of DNS, nothing in your lan is addressable from outside your lan.

You're still revealing the existence of myTopSecrets to the world, though.

Between this and certificate transparency logs, it seems insane to me that the commonly advised Correct Setup, to be able to experiment and hack random little personal stuff, and have it reliably work on modern browsers, requires you to 1) buy a subscription (domain), 2) enter into another subscription-ish contractual relationship (Let's Encrypt), and 3) announce to the whole world what you're doing (possibly in two places!).

Imagine your computer stops booting up because you repositioned your desk, and everyone tells you the Correct Way to do it is to file a form with the post office, and apply for a free building permit from the local council. That's how this feels.

Couldn't you just add the domain to /etc/hosts and have it resolve that way. No need to buy domain if you are just testing locally. Also you wouldn't be exposing anything to outside world.
Perhaps I could, but I'm afraid to do it[0]. And I'd still need a matching certificate, and generating a one that browsers won't refuse to look at and make them trust it across multiple devices (including mobile) is it's own kind of hell.

--

[0] - I'm honestly afraid of DNS. I keep losing too much of my life to random name resolution failures, whose fixes work non-deterministically. Or at least I was until ~yesterday, when I randomly found out about https://messwithdns.net, and there I learned that nameservers are required to have a negative cache, which they use to cache failed lookups, often with absurdly high timeout values. That little bit of knowledge finally lets me make sense of those problems.

I was only commenting about DNS part, self signed certificates come with their own lot of trouble. At least I havent ever run into any cache issues with local resolvers.

I have previously used https://github.com/jsha/minica which makes it at least easy to create a root certificate and matching server cert. How to get that root cert trusted on different array of devices is another story.

You can add what you want to /etc/hosts, but you need to actually control a domain to get a real cert for it that your browser will trust. Otherwise, you need to mess about with self-signed certs, browser exceptions, etc.

If you already own a domain, it's pretty convenient.

I totally agree, I have long since accepted that this is how things are but.. that doesn't mean it's right.. it feels like browsers are overtly obstructing the use of the local system as a development platform and local-hosting option. This also includes base-line features like the JS localStorage API that only works when connected to a domain name (localhost is a no-go). That last one in particular just feels perverse to me - in NO WAY should that require a domain name, it feels anti-democratic and clunky as can be. It also 100% stops webapps from being local-first (i.e. I save an HTML/JS bundle to a folder and run the "app", it's automatically isolated to said folder) with network connectivity as a secondary option. If browsers could do the latter then it would be a death-blow to a lot of remaining platform-specific apps.
> You're still revealing the existence of myTopSecrets to the world, though.

Not if you only present that name in local DNS, and use a wildcard certificate to avoid needing to reveal the name via a SAN cert or other externally referable information.

Also, perhaps refrain from calling it myTopSecrets. Perhaps ProjectLooBreak instead.

> to be able to experiment and hack random little personal stuff

Yes, a more sane approach is just use replit or the like, but this thread is about keeping it complicated.

> 2) enter into another subscription-ish contractual relationship (Let's Encrypt),

afaik, LE only does certs on machines for which they can see.

Taking a moment to look it up, I'm incorrect, it looks like you can establish LE with a DNS challenge instead of http. [0]

0. https://letsencrypt.org/docs/challenge-types/#dns-01-challen...

myTopSecrets can instead be mapped through a local redirection without needing to put that information out onto the Internet.
It is even simpler with `/etc/hosts`:

   127.0.0.1 localhost local.foobar.com
And just use wildcard `*.foobar.com` or SAN cert with anything local like Caddy, Nginx, Haproxy or whatever.
For people worried about somehow exposing commercially or otherwise sensitive information by the registration of DNS names, a SAN certificate is out because of certificate transparency logs.

A wildcard certificate is safe from that though. Or just choosing names that don't give secrets away.

A certificate signed by a locally trusted CA would work too of course, but unless you already have that setup for other reasons it is a bunch of admin most funny want to touch.

> a SAN certificate is out because of certificate transparency logs

First, all these certificates in the web PKI have SANs in them. X509 was designed for the X500 directory system, so when Netscape repurposed it for their SSL technology they initially used the arbitrary text fields and just wrote DNS names in as text. There are a number of reasons that's a terrible idea, but PKIX, RFC 2459 and its successors, defines a specific OID for "Subject Alternative Names". The word "alternative" here refers to the Internet's names (DNS names and IP addresses) being an alternative to the X500 directory system. PKIX says the legacy names should be phased out and everybody should use SANs.

That rule (you must use SANs in new certificates) was baked into the CA/Browser Forum Baseline Requirements (CA/B BRs or just "BRs" typically) which set the rules for certificates that will actually work in your web browser and thus in practice all the certificates people actually use. Enforcement of this rule was spotty for some time but the advent of CT logging made it possible to immediately spot any cert which violates the rule and so some years ago Google's Chome began to just reject the "legacy" just write it in a text field and hope approach and other browsers followed.

So what you're actually talking about are certificates with two or more specific DNS names rather than a single wildcard.

Secondly though, that's usually all a waste of your time if you're trying to mask the existence of named end points because of Passive DNS. A bunch of providers sell both live and historical feeds of DNS questions and answers. Who asked is not available, so this isn't PII, but if I'm wondering about your "sensitive" names at example.com I can easily ask a Passive DNS service, "Hey, other than www.example.com what else gets asked in similar DNS queries?" and get answers like "mysql.example.com" and "new-product-test.example.com".

Passive DNS isn't free, but then squirrelling away the entire CT log feed isn't free either, it's served free of charge on a small scale, but if you bang on crt.sh hard you'll get turned off.

> First, all these certificates in the web PKI have SANs in them.

Yes, and technically true is the best variety of true, but… Usually people don't refer to certificates where “Subject” is equal to the one and only “Subject Alternative Name”, as SAN certificates.

> So what you're actually talking about are certificates with two or more specific DNS names rather than a single wildcard.

If we are going to nitpick over the SAN designation a basic wildcard certificate is usually a SAN cert too, by the same definition. They have (at least mine always have had):

    Subject =
            “CN = *.domain.tld”
    Subject Alternate Name = 
            “DNS Name: *.domain.tld
             DNS Name: domain.tld”
(or similar for a wildcard hung off a sub-domain)

> "Hey, other than www.example.com what else gets asked in similar DNS queries?"

True, but only if those queries are hitting public DNS somehow. You can hide this by having your local DNS be authoritative for internal domains — your internal requests are never going to outside DNS servers. There could be leaks if someone who normally has access via VPN tries to connect without, but if you have something so truly sensitive that just knowing the name is a problem¹ then I hope your people are more careful than that (or your devices seriously locked down).

And I still say the easy workaround for this is names that only mean something internally. projectwhatthefuck.dev.company.tld is not going to mean much other than giving an attacker compared to projectousurpcompetitor.company.tld. Yes, they'll know the server name, and if it is publicly addressable they can connect to it, but if you have it properly configured they'll have to give it auth information they hopefully won't have before it hands over any useful information beyond the meaningless (to them) name that they already know.

--------

[1] Some of our contracts actually say that we can't reveal that we work with the other party, so technically² we could be in trouble if we leak the company name via DNS (bigwellknowmultinationalbank.ourservice.tld). Though when we offer a different name, in case the link between us can leak out that way, in those cases they've always declined.

[2] Really they don't care that much. They just don't want us to use their name/logo/other in promotional material.

I use a combination of mkcert and localdev.me … mkcert to generate a CA and install certs, then localdev.me redirects any subdomain to localhost.
Completely agree. If you want a nice way to do this with a shared config that you can commit to a git repo, check out my project, Localias. It also lets you visit dev servers from other devices on the same wifi network — great for mobile testing!

Localias is built on Caddy; my whole goal is to make local web dev with https as simple as possible.

https://github.com/peterldowns/localias

That only works on localhost, right? I am looking for a solution for intranet that doesn't require complex sysadmin skills such as setting up DNS servers and installing root certificates. This is for my customers who need to run my web server on the intranet while encrypting traffic (no need to verify that the server is who it claims to be).
Localias is not designed for your usecase and cannot solve your problem, sorry.
Without verifying the server identity, the encryption is useless.
Just a note, because this comment made me curious and prompted me to look into it:

Vite does use HTTP2 automatically if you configure the cert, which is easy to do locally without Caddy. In that case specifically there's no real reason to use Caddy locally that I can see, other than wanting to use Caddy's local cert instead of mkcert or the Vite plugin that automatically provides a local cert.

(comment deleted)
I assumed almost everyone (product, enterprise) uses ngork to expose development/localhost server to get HTTP2 now a days, but it's good to realize Caddy can do the job well.
> via the root certificate it installs in your OS trust store

This does not sound like the kind of feature I would want in a web server

It is optional for this purpose and you have to explicitly install it.
The six connections thing is just a default that you can change in about:config. Really it should probably have a higher default in $currentYear, but I don't expect major browser vendors to care.
I migrated all my Nginx hosts to use Caddy a while back. It doesn't do anything Nginx can't, but the default configuration is identical to the way I'd previously manually configured servers. It's so pleasant to get an HTTPS site up and running with 3 lines of setup.
I have used caddy for years as a reverse proxy for all my side projects. It is one of my favorite pieces of software.

So easy to setup and performs very well.

Caddy is pretty nice, I believe Coolify uses it as part of their self-hosted open source PaaS model. Just out of curiosity, are there any alternatives in Rust? I think Pingora is one, as well as River which is built on top of it [0], but I'm not sure how widely used the latter is as a Caddy replacement.

[0] https://github.com/memorysafety/river

As far as I know only NGINX Unit[0] might be considered a viable-ish real alternative to Caddy. But other than that nothing comes close to Caddy's ease of use and versatility. You get a lot of stuff and there are heaps of community modules for it, the only downside last time I checked was increased memory usage compared to standard nginx and slower performance as a reverse proxy.

Depending on your setup the fact that you have to choose between Caddyfiles (which are easier to reason about than nginx config files) or the REST API for configuration might be a downside to some people. There's a chance I might be wrong about this one though.

But to answer your question directly there are no real alternatives in Rust as of now.

[0] https://unit.nginx.org/

AFAIK traefik is written in rust. I don't have any experiences with it though. I only had a brief look when setting up some personal project, but ended up going with caddy, because traefik seemed overkill for my requirements.
Too bad river didn't have commits in five months.
Reading the website top to bottom, I’m now unsure about the trustworthiness of a project that seems so full of itself. Passage after passage about how great it is leaves a bad aftertaste. Maybe it’s just me—unsure.

I no longer trust the authors to be honest about known shortcomings, let alone be upfront, truthful, and transparent when dealing with security issues and reported vulnerabilities.

I hope I’m wrong. Does anyone know how they’ve handled disclosures in the past?

(comment deleted)
I dislike this style of documentation as well, but Caddy is a proven piece of technology. It can easily replace nginx or any other reverse proxy unless you're using a real niche configuration. Not needing to deal with certbot is also pretty nice.

Caddy's writing style isn't necessary big-enterprise-middle-management-friendly, but luckily for big enterprises that want lengthy, dry, and boring, there are plenty of alternatives.

You're unsure about a product because the landing page is positive, and even go so far as to not trust the authors any more? That does sound like a strange expectation for a landing page, which is usually intended to make you want to use a project.
(comment deleted)
I agree with the GP that hyperbole on a landing page (or anywhere else in the project’s communication) makes me not want to use the project. It communicates that the project lacks confidence that a down-to-earth description would speak for itself.
I understand the attitude because there are a lot of corporate websites which similarly claim the moon and the stars and when you dig right down a lot of it is bullshit. I have worked in places like this.

Such companies tend to imply that their product can do anything and tend to have pages of verbiage rather than the brass tacks README with examples you get on a good open source project's github page.

I just had my first experience with Caddy setting it up as a reverse proxy in front of Vaultwarden. Following along with Vaultwarden's documentation it worked like a charm and I was left thinking, "What a neat little project for hobbyists who want to get going quickly with the basics."

Then I checked out the home page and it's all "The most advanced HTTPS server in the world Raaawwrrr!"

Quite the divergence, but as other comments in the thread say, it's a legit good project.

Another great web server to try is h2o: https://h2o.examp1e.net/

Especially for its HTTP/2 and HTTP/3 QUIC support.

Wow, this is exactly what I've been looking for: lightweight, supports CONNECT and CONNECT-UDP with all three HTTP versions, and supports encrypted client hello. Thanks for the recommendation!
Agree and have been using it for many years for web sites we develop for customers. H3 support has been available for quite some time.

Support is also good and the developer is quite active.

I prefer to keep certificate management separate from individual applications like web servers, mail servers, XMPP servers, database servers and all the other services I run. All of these need certificates so I have centralised certificate management and distribution. This comes down to running certbot in a container with some hook scripts to distribute new or updated certificates to services (running on different containers and machines) which need them, restarting those services when needed. Adding a new site to nginx comes down to copying a template configuration, changing the site name to the correct one, adding whatever configuration needed for the specific service and requesting a new certificate for it. The new certificate automatically gets copied to the container or machine running the service so it is available after reloading the nginx configuration. The same is true for most other services, several of which share certificates because they're running in the same domain. I used the same scheme back when I used lighttpd and will probably use it should I move to another web (or mail or XMPP or whatnot) server.
Same here (not certbot and containers, but the part about reusing certificates for multiple services): it feels wrong to couple certificate acquisition with a web server. Apparently it is convenient when there is just a web server out of TLS-using services, or at least when it is in the center of the setup and HTTP-based certificate acquisition is used, which seems to be a common enough case to justify this, but still an odd coupling in general.
I also do this same thing, but my Nginx configs are templated out via automation. It gives me the best of both worlds: 95% of my sites and their certs are templated out from 3 lines of config each, then for the last special 5% I can insert literal Nginx config. For most uses I have the same experience as someone with Caddy, but for that last 5% I love the "access the full power of Nginx config from the same place" escape hatch.
I also find their library for Go (https://github.com/caddyserver/certmagic) a major timesaver! We're using it to make it easy for people to self-host our app and it takes care of all the TLS cert set up/renewal.
A great alternative is Traefik. We have been using v1 and v2 for several years now in a setup that uses the docker labels for configuration of services.
When I had first heard of Caddy, I was experimenting with Traefik to replace an nginx setup I had for a long time.

Traefik had good potential and momentum at the time. And then Caddy started to gain some momentum too. After that there was a brief moment Caddy made the mistake of taking an ad and including it in the `Server` response header and making it be an opt-out feature. Once that was walked back and the dust has settled, Caddy kept gaining more and more momentum and exposure.

Traefik had a web panel that I thought was cool back then but it tried to be too tightly coupled with containers and insisted on making service discovery be an essential core component of its configuration model.

At least this is what I remember. At this point I am very happy with caddy and it is what I use pretty much on all my services.

Thank you mholt for such a nice project and sorry for being overly critical of the ad in the Server response header very many years ago. :)

I also started using traefik a while back with docker labels. it was a bit more to set up than I thought it would be, but now that I've figured it all out it's not too bad.

at the time I had seem a lot of people talking about caddy as well and considered using it instead, but traefik had better perf/latency benchmarks and caddy seemed a bit too much geared toward or at least better suited for dev environment scenarios.

The friendly licensing (Apache v2) is important too, especially w/ Caddy's modular architecture (single, static binary compiled for any platform).

Meaning ecosystems around Caddy to make it even simpler and more secure, e.g. keep your server private while serving Internet clients. So VPNs like Tailscale (1) or zero implicit trust like OpenZiti (also Apache v2; (2)). Similar to what we have seen with open source k8s ecosystem for example.

(1) https://tailscale.com/blog/caddy (and other VPNs but the proprietary bits in the commercial TS service make it easier to use)

(2) https://github.com/openziti-test-kitchen/ziti-caddy (disclosure: maintainer...there may be other open source zero implicit trust options with these types of Caddy integrations)

> modular architecture

> single, static binary compiled for any platform

Huh? Aren't these exact opposites?

Plugins (modules) are compiled in statically.
Just like in the 1980's.
Build-time modularity is a great balance between flexibility, installation simplicity, startup reliability, and binary size.

Look at all these comments put off at the idea that maybe the tiny annoyance of building the software to have the exact features you want is worth it for reducing deployment complexity. It's kinda sad actually, compiling software should not be so scary.

It's not tiny when you include the need for ongoing support. It's the difference between enabling unattended-upgrades and (mostly) forgetting the thing exists, or adding another item onto your CVE tracking list and either building pipelines to automatically rebuild and update the server, or doing it manually every time a security bulletin comes out.

When you have more than one system, it can't be just dismissed away.

I absolutely love Caddy. Used it for years. Very reliable and so easy to setup once you learn the basics. The documentation is a bit hard to get, but it saved me so much time and energy compared to trying to get letsencrypt working reliable ontop of NGINX.
I used Caddy for a couple of years but eventually went back to Nginx.

For the Let's Encrypt certs I use certbot and have my Nginx configs set up to point to the appropriate directories for the challenges for each domain.

The only difficulty I sometimes have is the situation where I am setting up a new domain or subdomain, and Nginx refuses to start all together because I don’t have the cert yet.

It’s probably not too complicated to get the setup right so that Nginx starts listening on port 80 only, instead of refusing to start just because it doesn’t have the cert for TLS needed to start up the listener on port 443.

But for me it happens just rarely enough that I instead first make the config and outcomment the TLS/:443 parts and start it so that I can respond to the request from Let’s Encrypt for the /.well-known/blah blah stuff, and then I re-enable listening on with TLS and restart Nginx.

I also used DNS verification for a while as well, so I’m already aware that’s an option too. But I kind of like the response on :80 method. Even if I’ve managed to make it a bit inconvenient for myself to do so.

I tried to setup Caddy last year as a reverse proxy for all paths matching "/backend", and serve the rest as static files from a directory. I had to give up, because the documentation was not good enough.

I tried the JSON config format that seems to be the recommended format, but most examples on Google use the old format. To make it even more complicated the official documentation mentions configuration options, without informing that it requires plugins that is not necessarily installed on Ubuntu. Apparently they just assume that you will compile it from scratch with all options included. Lots of time was wasted before I found a casual mention of it in some discussion forum (maybe stack overflow, don't remember). I just wanted the path to be rewritten to remove the "/backend" path before proxying it to the service. I guess that is uncommon for a reverse proxy, and have to be placed in a separate module

I may appear overly critical, but I really spent a lot of time and made an honest attempt

I'll go back to nginx. Setting up let's encrypt requires some additional steps, but at least it's well documented and can be found in Google searches if necessary

Huh, sorry that it was that difficult. Based on what you wrote, this should suffice:

    example.com
    
    root /etc/www
    reverse_proxy /backend/* 127.0.0.1:8080
    file_server
It seems simple, but I used the JSON format which seems to add a lot of complexity (basically serialized version of objects)

I also had to install a separate module just to get a decent access log...

Could be my fault for going too far down a wrong path, but it could also be a sign of poor documentation

I found my last attempt here (before I gave up). I also spent a lot of time getting the basic logs working, by installing modules that wasn't part of the standard distribution

    {
      "logging": {
        "logs": {
          "default": {
            "level": "INFO",
            "encoder": {
              "format": "transform",
              "template":"{common_log}"
            },
            "writer": {
              "output": "file",
              "filename": "/var/log/caddy/access.log",
              "roll": true,
              "roll_size_mb": 5,
              "roll_gzip": true,
              "roll_local_time": true,
              "roll_keep": 5,
              "roll_keep_days": 7
            }
          },
          "dev_access": {
            "level": "INFO",
            "encoder": {
              "format": "transform",
              "template":"{common_log}"
            },
            "writer": {
              "output": "file",
              "filename": "/var/log/caddy/dev_access.log",
              "roll": true,
              "roll_size_mb": 5,
              "roll_gzip": true,
              "roll_local_time": true,
              "roll_keep": 5,
              "roll_keep_days": 7
            }
          },
          "errors": {
            "level": "ERROR",
            "writer": {
              "output": "file",
              "filename": "/var/log/caddy/error.log"
            }
          }
        }
      },
      "apps": {
        "http": {
          "servers": {
            "srv0": {
              "listen": [":443"],
              "logs": {
                "default_logger_name": "dev_access"
              },
              "routes": [
                {
                  "match": [
                    {
                      "host": ["<redacted>"],
                      "path": ["/backend/*"]
                    }
                  ],
                  "handle": [
                    {
                      "handler": "subroute",
                      "routes": [
                        {
                          "handle": [
                            {
                              "handler": "rewrite",
                              "strip_path_prefix": "/backend"
                            }
                          ]
                        },
                        {
                          "handle": [
                            {
                              "handler": "reverse_proxy",
                              "upstreams": [
                                {
                                  "dial": "localhost:8080"
                                }
                              ]
                            }
                          ]
                        }
                      ]
                    }
                  ]
                },
                {
                  "match": [
                    {
                      "host": ["<redacted>"]
                    },
                    {
                      "file": {
                        "try_files": ["{path}", "/index.html"]
                      }
                    }
         ...
> I had to give up, because the documentation was not good enough.

I had the same experience. And also somewhat bothered me that even a very basic and common functionality like rate limiting is not built-in.

I'm serving AI models on Lambda Labs and after some trial and error I found having a single vllm server along with caddy, behind cloudflare dns, to work really well and really easy to set up

vllm serve ${MODEL_REPO} --dtype auto --api-key $HF_TOKEN --guided-decoding-backend outlines --disable-fastapi-docs &

sudo caddy reverse-proxy --from ${SUBDOMAIN}.sugaku.net --to localhost:8000 &

You can use Cloudflare Tunnel, which is even better and simple than having an extra service.
It's really best to avoid running web servers as root. It's easy to forward the port 80 with iptables, change the kernel knob to let unprivileged users use port 80 and above, or set the network capability on the binary.

https://stackoverflow.com/questions/413807/

One day a number of years ago I decided I'd totally had anough of the arcane and difficult to debug Nginx configuration.

I heard about how Caddy did automatic https, and given the searing pain of doing https on Nginx, decided to make the switch.

Never regeretted it. Caddy it always up to the job even for sophisticated reverse proxying configs.

I just launched a new site with Caddy today: https://uchu.style

Caddy is so awesome. I actually have a few other sites on the same server and updating my config is hella simple.

I spent several years optimizing my nginx setup and I haven't touched it in years (I was obsessed about getting a perfect security score).

Caddy coupled with Caddy-Docker-Proxy [0] is a marvelous way to set up a server with multiple docker projects. I have it running on a couple of servers, and it just works!

[0] https://github.com/lucaslorentz/caddy-docker-proxy

I used this for my homelab before I decided my life was too easy and switched to k8s+cert manager. I use traefik[0] to handle multiple docker-compose projects in development, but this is pretty much a drop in replacement and gets you HTTPS if you have a domain and your registrar has an API. I ran a single docker-compose file with everything in it behind a single reverse proxy for quite a while.

[0] https://blog.gpkb.org/posts/multiple-web-projects-traefik/

This will be interesting to read, thank you. This sounds like what I was struggling with a while ago.

I found traefik to be a small nightmare in k8s. I struggled my way up to a working implementation years ago, then a k8s version change on my k8s hosting service forced me to start from scratch.

The whole thing soured me on traefik and also k8s. I wanted to learn pods and autoscaling while having interservice networking resolved for me. I don't want to spend hours struggling with the ingress and load balancing tools (I thought) k8s is supposed to solve for me.

If I ever try it again, I'd use a different ingress tool for sure.

I used traefik ingress for a while for k8s as it is included by default with k3s. But I very quickly switched to ingress-nginx. It's very easy to install and then it just takes care of itself letting you just declare Ingresses and forget about it.
That's what I wanted to do. If/when I try k8s again it absolutely will be a different ingress. There's no reason it should be such a struggle.
Have you tried Traefik? Could you compare the two? I was going to migrate to traefik soon but would consider any alternative
I've been using Caddy since the early days, and a few times looked into using Traefik, but it's config looked pretty complicated for what is a simple reverse proxy on Caddy.
I do not have in-depth knowledge of traefik unfortunately. I tried it a while ago, but decided to switch to the setup mentioned above for the setup simplicity. For my use-case, the setup mentioned on github under "Basic usage example, using docker-compose" plus adding two lines to a docker-compose file has been enough for most of my use-cases, and never given me any trouble.

I think achieving a similar setup in traefik (e.g. https://github.com/tiangolo/blog-posts/blob/master/deploying...) would be more complicated, and I felt like I was not sure what all the labels did or how to adapt the setup.

I get sick when I think about migrating my htaccess and apache rules to this format
I couldn't find (using Google) a good tutorial to deploy Django with Caddy to a Digital Ocean droplet. Can anyone suggest me what I should look for?

I could ask a LLM but I'd prefer the old way for this type of stuff...

Do you necessary need to deploy it to DO droplet? I can help you with that but have you considered any PaaS solutions? I've built the platform: seenode. You can take a look, it might be useful. If you need any help or you have a question let me know.
I love love caddy. I only use it for my homelab to get https everywhere, but it’s so much easier than traefik for me I honestly don’t know why everyone prefers it? What am I missing?
Traefik just integrates better with docker, especially docker compose. Depends on your use case honestly.

nothing wrong with caddy.

Love Caddy! Switched to it 2 years ago from NGINX/OpenResty and it made my config much less verbose and more simple. Previously used lua-resty-auto-ssl with OpenResty but it's kinda deprecated and I will never touch certbot but needed a "fire-and-forget" solution. Serving 70k visitors monthly very well :)