Embedding Caddy Web Server in Go

Dec 12, 2018 · 5 min read

Web servers help us to manage and scale our web apps in production. They have been the mysterious part of the stack to some web developers. It’s because configuring a web server is fairly time consuming for first timers as they need to know how reverse-proxies work and also have to deal with quirky config files.

But thanks to Caddy. It removes complexity surrounding the webservers. Developers who have used Caddy know the drill. Download a binary file, write few lines of config, start the binary by pointing it to the config file. Now you will have a secure web server running as caddy takes care of SSL by itself.

We know that CaddyServer is simple. But do you know that it’s extensible too? Yes, you can import caddy as library and embed the web server inside your Go code.

import "github.com/mholt/caddy"

When the web server is embedded into the code, we get more control over it. Adding a battle tested web server into your code allows you to use your code as reverse-proxy with graceful restarts or even for load balancing.

Today you will learn how to use caddy server as library in Go code. We will build a simple web app that runs on port 8000 and route all the requests hitting port 80 to it using caddy server as library.

High level representation of what we are going to build

A basic Caddyfile looks like this when you reverse proxy a web service running on port 8000 to the port 80.

proxy / localhost:8000

Now let’s start building our components one by one.

Simple HTTP server

A simple http server using mux router

The code is fairly simple here, we use gorilla/mux as router and send a plain text response to the /ping endpoint by starting http server on port 8000.

Visiting the URL localhost:8000/ping in browser after running the above code should output this.


Now we have got our HTTP server up and running. Let us first proxy it through port 80 using standalone caddy server manually. The Caddyfile will look like this.

proxy / localhost:8000

Start the Caddy server by pointing it to the Caddyfile.

./caddy --conf Caddyfile

If both HTTP server and this caddy server is running, opening localhost:80/ping in the browser will route requests to localhost:8000/ping and give you the text response.

Embedding a basic caddy server

Here’s the code to embed a basic version of caddy server.

Basic example as seen in official wiki page.

Inside main() we first set the AppName and AppVersion for our caddy instance.

Then we simply load a default config with server type http by doing caddy.LoadCaddyfile("http") and start the caddy server. This does not load any config file from disk, instead it simply starts a server on port 2015.

⚠️ We are setting up server type as http, so we have to make sure server type package_ "github.com/mholt/caddy/caddyhttp" is imported, else code will panic.

Running the above code spits this output.

Activating privacy features... done.
2018/12/08 14:20:21 http://:2015

Caddy says the server has been started on port 2015. If you goto localhost:2015 you should get a 404 error from caddy server as there is no upstream connected to that port.

404 error returned from our caddy server

Now we have confirmed that the caddy server is running as library. It’s time to make use of it by reverse-proxying an upstream server, the simple HTTP server we initially built.

Reading caddy config file

The loadConfig() function assembles the CaddyfileInput struct by accepting contents of the Caddyfile, it’s Filepath and serverType. The ServerTypeName and Contents are important fields of the struct where as Filepath is just for logging or reference purpose.

Inside init() you tell the caddy server to use the function loaderConfig() to prepare the CaddyfileInput. When you run this snippet, it proxies the http port: port 80 to the port 8000. But before doing that we need to start our upstream HTTP server by running the snippet simpleHTTPServer.go.

Now all your requests to localhost:80 are proxied to the upstream server running on localhost:8000. Hitting /ping endpoint returns the response served from the upstream server.

‘/ping’ API served on port 80 from upstream server

Playing around with the code

Clone the repository and cd to embed_caddy folder. The code structure there should look like this:

|_ main.go
|_ glide.lock
|_ glide.yaml
|_ simpleserver/
| |
| |_ httpserver.go
|_ webserver/
|_ server.go

I have used Glide as a package manager here. So, install glide by doing brew install glide. If you’re not on macOS, do
go get github.com/Masterminds/glide.

Install the dependencies by running glide install from inside the embed_caddy folder. Now you are ready to build and run the code.

Found this post interesting?
It would mean a lot to me if you could hold the “clap” icon and give a shoutout to me on
twitter. That would really make my day.

Also you can subscribe to our newsletter to receive more blog posts and courses on backend engineering.
— thanks!

Backend Army

Scale your backend skills to the cloud

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store