caddy-reverse-proxy

Featured

Set up a Caddy reverse proxy with automatic HTTPS.

Category: DevOps & Infrastructure Tier: Highly useful within a category Source: Newly authored Updated: 2026-07-20

What it does

The agent installs Caddy, writes a Caddyfile for your services, and starts the proxy. Caddy automatically obtains Let's Encrypt TLS certificates, redirects HTTP to HTTPS, and proxies requests to your backend services. You get production-grade HTTPS without managing certificates.

How an agent uses it

  • The user wants to expose a local service over HTTPS with a real domain.
  • The user wants to proxy multiple services through one server with TLS.
  • The user wants automatic certificate management (no certbot, no manual renewal).
  • The user says "set up a reverse proxy", "I need HTTPS for my service", or "proxy my Docker services".

What you get

Install this skill and your Hermes agent can set up a caddy reverse proxy with automatic https. No manual setup, no scripts to run — the agent handles it.

Install command

hermes skills install https://raw.githubusercontent.com/THEROCKSSS/hermes-skills-portfolio/main/skills/caddy-reverse-proxy/SKILL.md
View SKILL.md on GitHub
---
name: caddy-reverse-proxy
description: Use when the user wants to expose a local service over HTTPS with a real domain, proxy multiple services through one server with TLS, get automatic certificate management without certbot or manual renewal, or says "set up a reverse proxy", "I need HTTPS for my service", or "proxy my Docker services".
version: 1.0.0
author: Hermes Agent
license: MIT
metadata:
  hermes:
    tags: [caddy, reverse-proxy, tls, lets-encrypt, caddyfile, docker]
    related_skills: [docker-umbrella]
---

# caddy-reverse-proxy

## Overview

Set up Caddy as a reverse proxy with automatic HTTPS. Caddy obtains and renews Let's Encrypt certificates automatically, handles HTTP-to-HTTPS redirects, and proxies requests to your backend services. No manual certificate management.

## When to Use

- The user wants to expose a local service over HTTPS with a real domain.
- The user wants to proxy multiple services through one server with TLS.
- The user wants automatic certificate management (no certbot, no manual renewal).
- The user says "set up a reverse proxy", "I need HTTPS for my service", or "proxy my Docker services".

## Prerequisites

- A server with a public IP address
- A domain name pointing to your server (A record)
- Ports 80 and 443 open

## Installation

### Linux

```bash
# Debian/Ubuntu (official repo)
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
```

### macOS

```bash
brew install caddy
```

### Docker

```bash
docker run -d --name caddy \
  -p 80:80 -p 443:443 \
  -v caddy_data:/data \
  -v caddy_config:/config \
  -v $PWD/Caddyfile:/etc/caddy/Caddyfile \
  caddy:latest
```

## Caddyfile Syntax

The Caddyfile is Caddy's configuration format. It's intentionally simple.

### Single site

```
example.com {
    reverse_proxy localhost:8080
}
```

This one block does:
1. Listens on ports 80 and 443 for `example.com`
2. Obtains a Let's Encrypt certificate automatically
3. Redirects HTTP to HTTPS
4. Proxies all requests to `localhost:8080`

### Multiple sites

```
app.example.com {
    reverse_proxy localhost:8080
}

api.example.com {
    reverse_proxy localhost:3000
}

docs.example.com {
    root * /var/www/docs
    file_server
}
```

### Reverse proxy with header manipulation

```
api.example.com {
    reverse_proxy localhost:3000 {
        header_up Host {host}
        header_up X-Real-IP {remote_host}
        header_up X-Forwarded-For {remote_host}
        header_up X-Forwarded-Proto {scheme}
    }
}
```

### WebSocket support

Caddy supports WebSockets automatically — no special config needed:

```
chat.example.com {
    reverse_proxy localhost:3000
}
```

### Static files + API

```
example.com {
    # Serve static files
    handle /assets/* {
        root * /var/www/assets
        file_server
    }

    # Proxy API requests
    handle /api/* {
        reverse_proxy localhost:3000
    }

    # Everything else → frontend
    handle {
        reverse_proxy localhost:5173
    }
}
```

### Load balancing

```
api.example.com {
    reverse_proxy localhost:3000 localhost:3001 localhost:3002 {
        lb_policy round_robin
        health_uri /health
        health_interval 10s
    }
}
```

## Automatic HTTPS

Caddy handles certificates automatically:

| Feature | How it works |
|---|---|
| Certificate issuance | Caddy obtains certificates from Let's Encrypt on first request |
| Renewal | Caddy renews certificates 30 days before expiry |
| HTTP→HTTPS redirect | Automatic for all sites with a domain name |
| On-demand TLS | Optional — issue certificates on first request for any domain |

**On-demand TLS** (for wildcard/multi-tenant setups):

```
{
    on_demand_tls {
        ask https://api.example.com/check-domain
    }
}

https:// {
    tls {
        on_demand
    }
    reverse_proxy localhost:8080
}
```

Caddy will ask your API endpoint whether a domain is allowed before issuing a certificate.

## Running Caddy

### As a system service (Linux)

```bash
sudo systemctl enable caddy
sudo systemctl start caddy
sudo systemctl status caddy

# Reload config without downtime
sudo systemctl reload caddy

# View logs
sudo journalctl -u caddy -f
```

### With Docker Compose

```yaml
version: "3"
services:
  caddy:
    image: caddy:latest
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - caddy_data:/data
      - caddy_config:/config
      - ./Caddyfile:/etc/caddy/Caddyfile
    extra_hosts:
      - "host.docker.internal:host-gateway"

volumes:
  caddy_data:
  caddy_config:
```

Use `host.docker.internal` in the Caddyfile to proxy to services running on the host (not in Docker).

## Common Pitfalls

1. **No domain name.** Caddy's automatic HTTPS requires a domain name pointing to your server. Without a domain, Caddy can't obtain certificates. For local-only HTTPS, use Caddy's internal CA: `localhost:8080 { reverse_proxy localhost:3000 }` (generates a self-signed cert).
2. **Ports 80/443 not open.** Let's Encrypt uses the HTTP-01 challenge, which requires port 80 to be reachable. If your firewall blocks port 80, certificate issuance fails.
3. **Hitting Let's Encrypt rate limits.** Let's Encrypt allows 50 certificates per domain per week. Don't repeatedly restart Caddy with new domains in testing — you'll hit the limit and have to wait it out.
4. **Wrong host resolution in Docker.** If Caddy runs in Docker and your backend runs on the host, use `host.docker.internal` (with `extra_hosts` in compose). If both are in Docker, put them on the same network and use the container name instead.
5. **Restarting instead of reloading.** Use `caddy reload` (or `systemctl reload caddy`) to apply config changes without dropping connections. `caddy stop` + `caddy start` drops active connections.
6. **Large uploads silently rejected.** Caddy has a default body size limit. For large file uploads, set `request_body { max_size 100MB }` in the site block.

## Verification Checklist

- [ ] `curl -I https://<domain>` returns a valid certificate (no `-k` needed) and the expected backend response
- [ ] HTTP requests to the same domain redirect to HTTPS (`curl -I http://<domain>` shows a 301/308)
- [ ] `caddy validate --config Caddyfile` (or `docker exec caddy caddy validate`) passes before reload
- [ ] Config changes were applied with `caddy reload`, not a hard restart, if connections needed to stay up
- [ ] WebSocket-dependent backends were tested with an actual upgrade request, not just a plain GET
- [ ] Domain's A record resolves to the server's public IP before expecting certificate issuance to succeed
# caddy-reverse-proxy



Set up a Caddy reverse proxy with automatic HTTPS — no certbot, no manual certificate renewal.



## What it does



The agent installs Caddy, writes a Caddyfile for your services, and starts the proxy. Caddy automatically obtains Let's Encrypt TLS certificates, redirects HTTP to HTTPS, and proxies requests to your backend services. You get production-grade HTTPS without managing certificates.



## Install



```bash

hermes skills install https://raw.githubusercontent.com/THEROCKSSS/hermes-skills-portfolio/main/skills/caddy-reverse-proxy/SKILL.md

```



## How to use



```

"Proxy my API at api.mysite.com to localhost:3000 with HTTPS"

```



The agent:

1. Installs Caddy (or uses Docker)

2. Writes a Caddyfile:

   ```

   api.mysite.com {

       reverse_proxy localhost:3000

   }

   ```

3. Starts Caddy

4. Verifies: `curl https://api.mysite.com/health` returns 200



## Prerequisites



- A server with a public IP

- A domain name pointing to your server (A record)

- Ports 80 and 443 open



## What you get



| Feature | Automatic? |

|---|---|

| TLS certificate issuance | Yes — Let's Encrypt |

| Certificate renewal | Yes — 30 days before expiry |

| HTTP→HTTPS redirect | Yes |

| WebSocket proxying | Yes — no config needed |

| Multiple sites | Yes — one block per domain |



## Example



```

User: "I have three services: a web app on :5173, an API on :3000, and docs on :8080. I want them all under my domain with HTTPS."



Agent:

  1. Writes Caddyfile:

     mysite.com { reverse_proxy localhost:5173 }

     api.mysite.com { reverse_proxy localhost:3000 }

     docs.mysite.com { reverse_proxy localhost:8080 }

  2. Starts Caddy via Docker Compose

  3. Verifies all three domains return 200 over HTTPS

  4. Returns: "All three services are live with HTTPS."

```