Caddy: A Modern Web Server Built with Golang

8grams
9 min readNov 2, 2023

Introduction

Caddy Webserver, often simply referred to as “Caddy”, is a web server written in Go (Golang) that is known for its automatic HTTPS capabilities, ease of use, and extensibility. Its primary distinguishing feature is its ability to set up and renew SSL/TLS certificates automatically from Let’s Encrypt, providing websites with secure connections with minimal effort from the site administrator.

History

Caddy was created by Matt Holt and first released in April 2015. The motivation behind Caddy was to simplify web server configuration, especially in terms of HTTPS setup, which traditionally required manual steps like obtaining and renewing SSL certificates. Due to its unique automatic HTTPS feature, Caddy quickly gained traction among web developers and administrators who sought a simpler and more secure approach to serving their websites.

Caddy 2, a significant update to the original version, was released in May 2020. It came with a more flexible configuration, an enhanced codebase, and better extensibility. Caddy 2 was designed from the ground up based on the feedback and needs of the community and users of Caddy 1. Caddy underwent several licensing changes throughout its history. Originally released under the MIT License, it was later switched to a commercial license for binaries (while the source code remained open under the Apache License). This change was met with some concerns from the community. However, eventually, the project returned to a more open licensing model with the Apache 2.0 license for both binaries and source code.

Over the years, a vibrant community of developers and users formed around Caddy. This community has contributed numerous plugins that extend Caddy’s core functionality, adding features such as authentication, integrations with various platforms, and more.

Highlight Feature

The main feature that distinguishes Caddy from other web servers is its Automatic HTTPS capability.

Automatic HTTPS involves two primary functionalities:

  1. Auto-obtaining Certificates: When you configure Caddy with a domain name, it will automatically obtain an SSL/TLS certificate from Let’s Encrypt (or other supported certificate authorities) for that domain without requiring manual intervention. This means that site administrators don’t need to go through the traditional process of buying, validating, and installing certificates.
  2. Auto-renewal: Caddy automatically renews the SSL/TLS certificates before they expire, ensuring that the website remains secure without the site administrator needing to remember to renew.

This automatic HTTPS capability greatly simplifies the process of setting up and maintaining a secure website, reducing the barrier for website administrators to deploy HTTPS on their sites. The feature promotes better security practices on the web by making encryption accessible and hassle-free.

Caddy Architecture

Written in Go
Being written in Go, Caddy benefits from Go’s efficient concurrency model using Goroutines. The static binary compilation ensures that Caddy can be deployed without external dependencies, which simplifies installation and deployment.

Modular Architecture
Caddy’s architecture is based on a plugin system. This means that functionality in Caddy isn’t just limited to what the core developers add; third-party developers can create plugins to extend Caddy’s capabilities.

How Plugins Work:

  • Each plugin is registered with a unique ID and implements a specific interface. This allows Caddy to recognize and utilize the plugin.
  • These plugins are dynamically loadable, so you can choose which capabilities to include in your Caddy binary at compile-time.

Automatic HTTPS
Caddy’s Automatic HTTPS isn’t just about acquiring certificates but also managing their entire lifecycle.

Technical Insights:

  • Caddy utilizes the ACME (Automatic Certificate Management Environment) protocol to communicate with certificate authorities like Let’s Encrypt.
  • It maintains an internal certificate storage to cache and manage these certificates.
  • For challenges, it usually uses the HTTP and TLS-ALPN challenges, setting up temporary routes to fulfill them.
  • Certificate renewals are automated and are handled before they expire, with Caddy managing potential failures to ensure continuous availability.

Configuration
Caddyfile: The Caddyfile uses a domain-centric configuration style. Directives within each block are applied in a specific order, which might not necessarily be the order in which they’re written. This can be a little tricky for newcomers but is designed to abstract complexities.

JSON: Caddy’s internals actually work with a JSON configuration. When using a Caddyfile, Caddy translates it into this JSON structure. This JSON structure is a direct representation of Caddy’s runtime structures.

Admin API
The API isn’t just an interface layered on top; it interacts directly with Caddy’s core.

  • It’s a RESTful API serving over HTTP.
  • The API allows full control over the server’s configuration in real-time.
  • Loading a new configuration is atomic and transactional. If a new configuration is invalid, it won’t be applied.

Event-Driven and Concurrency Model
Caddy, like Nginx, uses an event-driven model. But Caddy also heavily leverages Goroutines for lightweight concurrent processing.

  • Incoming requests can be processed concurrently using separate Goroutines, allowing Caddy to handle many requests without blocking.
  • The internal architecture utilizes Go’s channels and select statements for efficient event handling and inter-goroutine communication.

Embedded Server
Technically, any Go application can import Caddy as a library and use it as an embedded server.

  • This is possible because of Go’s package system and Caddy’s modular design.
  • When embedded, you can interact with Caddy’s internals directly through Go code, offering a high level of integration and control.

Middleware Chain
Caddy’s request handling is based on a middleware chain (sometimes called handlers in Caddy’s context).

  • Each middleware performs its function and then calls the next one in the chain.
  • This chain is constructed based on the configuration, allowing dynamic and flexible request processing.

Caddy vs Nginx vs Apache

Deciding between Caddy, Nginx, and Apache often boils down to the specific needs of your project, your familiarity with the tools, and your long-term goals. Here’s a general guideline on when to use each:

Use Caddy when:

  • Automatic HTTPS is Desirable: Caddy sets up HTTPS by default using Let’s Encrypt, making the process of securing your site trivially easy.
  • You Want Simplicity: Caddy’s configuration (via the Caddyfile) is often simpler and more readable for basic to moderately complex sites compared to Nginx and Apache.
  • You Value Modern Features with Sane Defaults: Caddy comes with many modern web features out of the box and aims to provide sensible defaults for most settings.
  • Dynamic Configuration is Needed: With its admin API, Caddy can be reconfigured on the fly without needing a restart.
  • You Prefer Go (Golang): If you’re developing in Go or have a Go-based stack, Caddy, being written in Go, might fit better in terms of deployment, customization, and extension.

Caddy is relatively new but has made a significant impact due to its simplicity and “batteries-included” philosophy. Automatic HTTPS by default and a configuration file format (Caddyfile) that abstracts away complexities make it unique. However, some might find its behavior too abstracted, especially if coming from a more manual configuration mindset like Nginx or Apache.

Use Nginx when:

  • Performance is Critical: Nginx’s event-driven architecture is designed to handle many simultaneous connections with minimal overhead.
  • You Need a Reverse Proxy or Load Balancer: While all three can act as reverse proxies, Nginx is particularly known for this use case and offers a variety of features to support load balancing, caching, etc.
  • Flexible Configuration is Desired: Nginx’s configuration can be fine-tuned to a high degree, allowing for a broad range of setups and optimizations.
  • You’re Migrating from Legacy Systems: Many large and older infrastructures use Nginx, and if you’re migrating or integrating with such systems, it might be easier to stick with Nginx.
  • Commercial Support is a Factor: Nginx Plus, the commercial version, offers additional features, guaranteed support, and SLAs.

Nginx is known for its performance and has been used extensively as both a web server and a reverse proxy/load balancer. Its non-blocking event-driven architecture allows it to handle many simultaneous connections with minimal overhead. While its configuration is straightforward, HTTPS isn’t set up by default, and some of its features (like dynamic module loading) are reserved for the commercial version.

Use Apache when:

  • You Need Extensive Module Support: Apache’s modularity is one of its strengths. There are modules for nearly everything, from different languages (like PHP via mod_php) to various authentication mechanisms.
  • .htaccess is Essential: If you need directory-level configuration overrides (especially in shared hosting environments), Apache’s .htaccess provides this feature.
  • You’re in a Legacy Environment: Apache has been around for a long time and is often the default in many older systems, shared hosting platforms, and enterprise environments.
  • Familiarity and Community: If your team is already well-acquainted with Apache and its ecosystem, or if you value its large community and wealth of tutorials and resources, it might be the right choice.
  • Process-Driven/Threaded Model Fits Your Needs: Unlike Nginx’s event-driven model, Apache uses a process-driven or threaded model. While it can be more resource-intensive, it might suit particular workloads or environments better.

As one of the oldest and most versatile web servers, Apache has a rich ecosystem and extensive module system. Its .htaccess feature allows directory-level configurations, offering flexibility but potentially introducing complexity and performance overhead. Apache uses a process-driven or threaded model, which can be more resource-intensive than Nginx’s event-driven model.

Caddyfile

The Caddyfile is the primary configuration method for most Caddy users due to its readability and simplicity. Let’s go deeper into its structure and syntax.

Basic Structure

A Caddyfile consists of a series of site blocks. Each site block starts with one or more site addresses, followed by a series of directives that apply to that address.

example.com {
directive1
directive2
...
}

Directives

Directives instruct Caddy on how to handle requests. They are executed in a specific default order, which can be critical to proper functionality. Some common directives include:

  • root: Specifies the root directory from which to serve files.
  • file_server: Enables serving static files.
  • reverse_proxy: Proxies requests to a backend server or service.
  • encode: Enables response encoding (e.g., gzip).
  • log: Configures request logging.
  • header: Adds or manipulates headers.

Matchers

Matchers determine which requests a directive should apply to. They come before the directive they influence.

@matcherName {
path /specific-path/*
}
header @matcherName X-Header-Name "Header Value"

Here, the header directive will only apply to requests that match the path /specific-path/*.

Multiple Sites

You can define configurations for multiple sites in one Caddyfile:

example.com {
...
}
another.com {
...
}

Global Options

These are options that aren’t specific to a site. They should be at the very top of the Caddyfile.

{
email youremail@example.com
}
example.com {
...
}

The email global option, for example, sets the email address to use for ACME (Let's Encrypt).

Environment Variables

You can use environment variables in the Caddyfile:

example.com {
root * {$SITE_ROOT}
}

In this case, $SITE_ROOT would be replaced by the value of the SITE_ROOT environment variable.

Snippets

Snippets are reusable bits of configuration:

(common) {
header X-Powered-By "Caddy"
}
example.com {
import common
...
}

Here, import common inserts the snippet (common) into the site block.

Comments

Use the # symbol to denote a comment:

# This is a comment
example.com {
...
}

Placeholders

Caddy has various placeholders you can use that will be replaced when the configuration is applied. For instance:

  • {http.request.method} - The HTTP request method (e.g., GET, POST).
  • {http.request.host} - The host of the request.
  • {http.request.uri} - The request's URI.

These can be used in various directives for dynamic configurations.

Caddy Template

Caddy’s templates directive allows you to use dynamic templating in your static files. This is especially useful when you want to embed dynamic content or values within otherwise static HTML, CSS, or JavaScript files. The templating feature uses Go's built-in templating language.

Basics of Caddy Templates

  1. Placeholders: Templates can use any of Caddy’s placeholders, allowing dynamic content to be inserted based on the request, environment, server, etc.
  2. Control Structures: You can use various control structures, like loops and conditionals, provided by Go’s templating language.
  3. Custom Data: You can introduce custom data into your templates.

Imagine you have an HTML file, index.html, and you'd like to greet the visitor based on the current time. Let's see how you can do that using Caddy templates:

index.html

<!DOCTYPE html>
<html>
<head>
<title>Dynamic Greeting</title>
</head>
<body>
{{if eq (time.Hour) 12}}
<p>Good noon!</p>
{{else if le (time.Hour) 12}}
<p>Good morning!</p>
{{else}}
<p>Good evening!</p>
{{end}}
</body>
</html>

Caddyfile

example.com {
root * /path/to/your/site
file_server
templates
}

In this example:

  • time.Hour is a placeholder that will be replaced by the current hour.
  • The template checks the current hour to decide whether to display “Good morning”, “Good noon”, or “Good evening”.

When someone visits example.com, Caddy processes the index.html template and serves the appropriate greeting based on the current time.

Advanced Templating

  • Custom Functions: Go’s templating language includes many built-in functions, but Caddy also adds some custom ones, such as http for making HTTP requests or json for encoding data as JSON.
  • Including Other Files: You can include content from other files using the include function.
{{include "path/to/another/file.html"}}
  • Comments: You can add comments in your templates that won’t appear in the rendered output.
{{/* This is a comment and will not appear in the final rendered HTML. */}}

Caddy’s template system is a powerful way to introduce dynamic behavior into static sites. By leveraging Go’s templating language, you can implement a wide range of dynamic content generation without needing a full-blown backend system. Always refer to Caddy’s official documentation for a comprehensive list of functions, placeholders, and examples when working with templates.

Example

You can check the real world case how to use Caddy from this repository: https://github.com/8grams/caddy-example. This repository demonstrate how to use Caddy to host static web using Caddy Template and bundle it in Distroless Image.

--

--

8grams

We are a DevOps Consulting Firm with a mission to empower businesses with modern DevOps practices and technologies