Learning Center
Servers

Caddy: A Lightweight Reverse Proxy Server

13 Nov 2024
Hostman Team
Hostman Team

Caddy is a reverse proxy server written in Go. It is a completely free, open-source project with an Apache 2.0 license.

Caddy supports HTTP/2, HTTP, and HTTPS, and allows for automatic obtaining and renewing of Let’s Encrypt certificates. It is cross-platform and supports various processor architectures. Additionally, you can run Caddy in a Docker container.

Caddy Key Features

  • Simple and intuitive configuration: The configuration file is easy to read and write, and is minimalist.
  • Automatic obtaining and renewing of SSL certificates.
  • Default HTTPS usage.
  • Configuration via API: You can configure Caddy using REST API requests in JSON format, which makes it ideal for automation setups and provides additional configuration possibilities.
  • Plugins: Caddy works out of the box with a basic set of features, including a proxy server adapter, but with plugins, you can add extra functions: page authentication, integration with DNS providers (like Cloudflare), and more.

Installing Caddy
Copy link

In this guide, we will use a cloud server with Ubuntu 22.04 and minimal configuration.

7e59ec5f D977 4b16 Bad6 11e9f51921b2

Connect to the server and install Caddy according to the official documentation:

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https

curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg

curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list

sudo apt update

sudo apt install caddy

Check the service status:

systemctl status caddy

Check the version:

caddy version
v2.7.4

To verify, open the page in your browser using the host's IP address.

F761808a 0148 46eb 8b29 Ea77cdaeb19f

Next, follow the step-by-step configuration recommendations provided by Caddy.

Caddy Configuration
Copy link

Step 1. Create a domain name and point it to the server's IP address

Create a third-level domain name. For this example, we will use caddy.example.com

Set the A record to point to the public IP address of the created server.

Step 2. Change the path and use your static files for display

Create your own index.html at the specified path /var/www/html/. Add an image, for example, image.jpg:

<!DOCTYPE html>
<head>
    <meta charset="UTF-8">
    <title>Header</title>
</head>
<body>
    <header>
        <h1>Simple index file for demo</h1>
    </header>
    <main>
        <h2>Caddy checklist</h2>
        <p>Let's configure Caddy following the instructions:</p>
        <ul>
            <li>Set the A record</li>
            <li>Upload files</li>
            <li>Edit Caddyfile</li>
            <li>Reload configuration</li>
            <li>Visit the site</li>
        </ul>
        <img src="image.jpg" alt="new_image">
    </main>
</body>
</html>

Step 3. In the configuration, change port :80 to your domain name

Open the configuration file for editing:

nano /etc/caddy/Caddyfile

Remove all the content and the following lines:

caddy.example.com {
    root * /var/www/html
    file_server
}

Step 4. Restart Caddy

Restart Caddy using the command:

systemctl reload caddy

Open the page using your domain: caddy.example.com.

The page will load immediately over HTTPS with a trusted certificate — all of this in just four lines of configuration, keeping everything minimal, clean, and understandable.

Running Caddy in Docker Compose
Copy link

Suppose you need to isolate the service and redirect traffic. Using containers without installing additional packages or dependencies is very convenient, and starting everything with a single command. Therefore, let’s stop the Caddy service and install Docker:

systemctl stop caddy
apt remove caddy
apt install docker.io docker-compose

Now we repeat the same steps as before, but now in the container. 

Create a directory at /srv/caddy and copy the static files and configuration files there:

mkdir -p /srv/caddy/site
cp -r /var/www/html /srv/caddy/site
cp /etc/caddy/Caddyfile /srv/caddy/Caddyfile

Create a docker-compose.yml file with the following configuration:

version: '3.8'
services:
  caddy:
    image: caddy:2.7.4
    restart: always
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - ./site:/var/www/html

Here, we use volumes to transfer the files to the familiar directories, ensuring nothing is saved in the configuration.

After this, run:

docker-compose up -d

Let's look at how we can further secure the page using BasicAuth with just a few configuration lines.

Edit the Caddyfile as follows, adding basicauth * {login-password}:

caddy.example.com {
   basicauth * {
        image $2y$10$v8t9CqkLFEon3UTYKUsRs.8zhMMLFX5.9WyDERzd7ESRT75PICkiW
    }
    root * /var/www/html
    file_server
}

You can generate the login and password in the console or use online .htpasswd generators (the default algorithm is bcrypt).

Then, restart with:

docker-compose restart

Now, when you open the page, authentication windows will appear.

Next, in docker-compose, let’s use a service with a web interface, such as Grafana.

version: '3.8'
  caddy:
    image: caddy:2.7.4
    restart: always

    ports:
      - 80:80
      - 443:443
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - ./site:/var/www/html
  grafana:
    image: grafana/grafana:10.0.5-ubuntu
    container_name: grafana
  # ports:
    #  - 3000:3000
    environment:
      GF_SECURITY_ADMIN_USER: 'admin'
      GF_SECURITY_ADMIN_PASSWORD: 'admin'

The ports are commented out because both Grafana and Caddy are connected to the same group and can communicate by service names, so external port forwarding is not needed. If you need to access the service not only by the domain name but also by the local IP address and port, you will need to uncomment those lines.

For the Grafana service, create another third-level domain name in DNS, for example, test.example.com, and add it to the Caddyfile configuration:

caddy.example.com {
    root * /var/www/html
    file_server
}
test.example.com {
    reverse_proxy grafana:3000
}

So far we have the following structure:

3a7e1be3 21fb 43f1 B5b1 Eb4bda73eb28

In the site directory, there are two files:

  • index.html (the main page of the site).

  • image.jpg.

The docker-compose.yml file starts two services:

  • Caddy for proxying and obtaining certificates.

  • Grafana as a separate web application.

In the Caddyfile (the configuration file), using volumes, we share the internal container to /etc/caddy/Caddyfile.

When accessing caddy.example.com, we get static files from the site directory. Accessing test.example.com forwards requests to Grafana.

To check that the new domain name is resolving correctly on this host, verify the DNS records:

nslookup test.example.com

If everything works, you can check it in a browser. Go to test.example.com — it should open the Grafana login page.

Conclusion
Copy link

In this article, we provided simple examples, but you can also read in the documentation about load balancing to distribute traffic across different hosts, adding health checks to verify the availability of hosts, how to trim, add, or modify headers in requests, etc.

We don't dive too deeply into the configuration, as Caddy is primarily about simplicity. The configurations shown in the article are very convenient and clear, and you might think there can't be anything simpler. But it turns out, there is.

For example, if on the same cloud machine we needed to deploy just Grafana, obtain a certificate, and secure it behind a proxy server, the Caddyfile configuration would look like this:

test.example.com
reverse_proxy grafana:3000

With just two lines in the source, we get a full-fledged proxy server, with HTTP-to-HTTPS redirection and automatic SSL certificate generation. This is exactly why we like Caddy.