Why Caddy might be the fastest Go web server you can run
I switched from nginx to Caddy about a year ago. Automatic HTTPS was the selling point, and I fully expected to give up some performance in exchange for the convenience. A year on, I’m still waiting to notice the trade-off.
Caddy is a web server written entirely in Go. It speaks HTTP/1.1, HTTP/2 and HTTP/3 out of the box, and TLS is handled for you without a single config block. One caveat up front: “fastest” is a claim I’ll hedge rather than prove. I haven’t benchmarked every Go web server. What I can tell you is why Caddy is quick, and why in practice it’s almost never your bottleneck.
What makes Caddy fast
Caddy leans hard on Go’s concurrency model. Each incoming request gets its own goroutine, so thousands of concurrent connections don’t carry the thread overhead you’d be managing in a C-based server.
Underneath, it builds on Go’s net/http package rather than reinventing it, then layers optimisations on top: connection pooling, buffer reuse, sensible timeout handling. Nothing exotic. Just the standard library used well.
Here’s a complete Caddy setup using the Caddyfile:
example.com {
reverse_proxy localhost:8080
}
That’s it. From those two lines Caddy obtains a TLS certificate via ACME, redirects HTTP to HTTPS, enables HTTP/2 and HTTP/3, and renews the certificate before it expires. No certbot cron jobs, no nginx config blocks. I still find this slightly unreasonable every time I set up a new site.
Benchmarking Caddy as a reverse proxy
I ran some rough tests with Caddy proxying a simple Go backend. The backend just returns JSON:
package main
import (
"encoding/json"
"net/http"
)
type Response struct {
Message string `json:"message"`
Status int `json:"status"`
}
func main() {
http.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
resp := Response{
Message: "Hello from backend",
Status: 200,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
})
http.ListenAndServe(":8080", nil)
}
With Caddy in front of this, the proxy overhead was consistently sub-millisecond on my machine. Treat that as an anecdote rather than a benchmark, but it matches my experience in production: whatever latency problems I have, Caddy isn’t where they live.
Extending Caddy with Go
My favourite thing about Caddy is the plugin system. Custom modules are written in Go and compile directly into the binary, which means no interpreter, no IPC, no plugin-loading cost at runtime.
Here’s a minimal middleware:
package custommodule
import (
"net/http"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyhttp"
)
func init() {
caddy.RegisterModule(Middleware{})
}
type Middleware struct{}
func (Middleware) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "http.handlers.custom",
New: func() caddy.Module { return new(Middleware) },
}
}
func (m Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
// Add custom header
w.Header().Set("X-Custom-Header", "processed")
return next.ServeHTTP(w, r)
}
func (m *Middleware) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return nil
}
This becomes part of Caddy itself. Compare that with scripting nginx and the appeal is obvious: you’re writing the same language as your backend, with the same tooling and the same tests.
Automatic HTTPS performance
The automatic HTTPS feature uses the ACME protocol to obtain certificates. Caddy caches them and handles renewal in the background, so the only cost is first-time issuance on a new domain. After that there’s no ongoing overhead, and HTTP/3 with QUIC cuts connection setup time for repeat visitors.
If you’re building a Go HTTP server and need a production-ready front door, this pairing works well. You focus on your application logic and let Caddy own the infrastructure.
When to use Caddy
I’d reach for Caddy when I want automatic HTTPS with zero ceremony, a fast reverse proxy in front of Go backends, HTTP/3 support, or simply a config file I can still read six months later.
Where I’d hesitate: raw static file serving at serious volume, where nginx still edges ahead in some benchmarks. For most Go applications, though, you’re proxying to a backend, and there Caddy’s performance is excellent.
Final thoughts
Whether or not Caddy is the fastest Go web server, it’s fast enough that the question stops being interesting, and the certificate management alone has saved me hours of fiddly ops work.
If you want to dig deeper, the official Caddy documentation covers configuration well, and the extending Caddy guide is the place to start for custom middleware. The codebase itself is also worth 30 minutes of your time. It’s one of the better large Go projects you can read for free.