Prerequisites
We’ll use the Caddy modules and adapters we built in our previous section in this post.
What We Will Build
We now have everything we need to create something meaningful in Caddy. We’ll be configuring Caddy to reverse-proxy requests to our backend web service and serve static files on behalf of it. There will be two endpoints: one will serve up static files from Caddy, and the other will handle our requests to our backend service. In turn, this will show you how our web services can rely on Caddy to serve static content on their behalf.
Before we start, we need to set up our directory structure. Right now, you’re currently in the caddy directory which has a caddy
binary we built earlier. Create two subdirectories:
$ mkdir files backend
Backend Web Service
In the backend
folder created above, create a file called backend.go
:
$ touch backend/backend.go
Add the following code:
func run(addr string, c chan os.Signal) error {
mux := http.NewServeMux()
mux.Handle("/",
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
clientAddr := r.Header.Get("X-Forwarded-For")
log.Printf("%s -> %s -> %s", clientAddr, r.RemoteAddr, r.URL)
_, _ = w.Write(index)
}),
)
srv := &http.Server{
Addr: addr,
Handler: mux,
IdleTimeout: time.Minute,
ReadHeaderTimeout: 30 * time.Second,
}
go func() {
for {
if <-c == os.Interrupt {
_ = srv.Close()
return
}
}
}()
fmt.Printf("Listening on %s ...\n", srv.Addr)
err := srv.ListenAndServe()
if err == http.ErrServerClosed {
err = nil
}
return err
}
func main() {
addr := flag.String("listen", "localhost:8080", "listen address")
flag.Parse()
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
err := run(*addr, c)
if err != nil {
log.Fatal(err)
}
log.Println("Server stopped")
}
var index = []byte(`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Caddy Backend Test</title>
</head>
<body>
<h1>Hello from Caddy!</h1>
</body>
</html>`)
If you’ve been following along, a lot of this code is pretty self-explanatory. You’re setting up the service to listen on port 8080 of localhost which Caddy will use to direct requests. Caddy then implements an X-Forwarded-For
header for requests that originate from the client’s address. The handler then writes a slice of bytes to the response with the HTML code. Your backend service is now ready to start receiving requests.
Setting Up Caddy’s Configuration
At the root of your directory, create a new file caddy.toml
and add the following code:
[apps.http.servers.test_server]
listen = [
'localhost:2020',
]
[[apps.http.servers.test_server.routes]]
[[apps.http.servers.test_server.routes.match]]
path = [
'/backend',
'/backend/*',
]
[[apps.http.servers.test_server.routes.handle]]
handler = 'reverse_proxy'
[[apps.http.servers.test_server.routes.handle.upstreams]]
dial = 'localhost:8080'
[[apps.http.servers.test_server.routes]]
[[apps.http.servers.test_server.routes.handle]]
handler = 'auth_prefix'
prefix = '.'
[[apps.http.servers.test_server.routes.handle]]
handler = 'file_server'
root = './files'
index_names = [
'index.html',
]
The test_server
configuration has a routes
array and each route in the array has zero or more matchers. Matchers are special modules that allow you to specify matching criteria for incoming requests like the http.ServeMux.Handle
method, for instance. For this route, you have a matcher that matches any request to /backend or /backend/. You then tell Caddy that you want to send all matching requests to the reverse-proxy handler which sends all its requests to port 8080.
Remember us using, http.FileServer
to serve static files? Caddy’s version is the file_server
handler. And unlike the previous route, this one doesn’t have a matcher. This is our default route and its position matters. If you put it above the reverse_proxy route, all requests would match it and no requests would ever make it to the reverse-proxy. Another major difference is that it uses the auth_prefix
middleware because we wouldn’t want unauthorized users to access restricted content on our service. Lastly, you return the index.html file.
Let’s check our work. In your terminal, run the following command:
$ ./caddy start --config caddy.toml --adapter toml
You should get the following response that lets you know Caddy is running in the background:
Start your backend service:
$ cd backend
$ go run backend.go
Output:
Now open your browser and visit http://localhost:2020
. Caddy will send your request to the file server and respond with the index.html. If everything succeeds, you should be looking at this:
Great! Now let’s test our reverse proxy by visiting http://localhost:2020/backend
. This request matches the reverse-proxy route’s matcher so the reverse-proxy handler should handle it by sending the request to the backend service. It should look like this:
Success! Now let’s see how can integrate Caddy’s key feature: automatic HTTPS.
Adding Automatic HTTPS
Caddy can help you set up a website with full HTTPS support using certificates trusted by all modern web browsers in minutes. It automatically enables TLS and all you need to do is tell it which domain name to serve. Since we’re in a testing environment, Caddy won’t use Let’s Encrypt but it has an internal certificate authority for enabling HTTPS over localhost. Here’s how you add it to your matcher:
[[apps.http.servers.test_server.routes.match]]
host = [
'localhost',
]
If you restart all the services and load up localhost:2020
again, you’ll be met with a page that has HTTPS enabled as compared to earlier:
That’s it!
Depending on your needs, Caddy can be deployed at the edge of your application so that you can spend most of your time working on your backend web service.
Thank you for reading, until next time.