Files
rick-infra/roles/caddy

Caddy Web Server Role

A modern Ansible role for installing and configuring Caddy web server with automatic HTTPS, file-based service configuration, and security hardening.

Features

  • 🔐 Automatic HTTPS with Let's Encrypt certificates
  • 🌐 DNS Challenge Support for wildcard certificates (Cloudflare provider)
  • 📁 File-Based Configuration using sites-enabled directory pattern
  • 🛡️ Security Hardening with systemd restrictions and capability bounds
  • Zero Downtime configuration reloads without service restarts
  • 🎯 Simple Architecture following nginx-like configuration patterns
  • 🔄 Git-Friendly configuration files for version control

Requirements

  • Systemd-based Linux distribution (tested on Arch Linux)
  • Ansible 2.9+
  • For DNS challenges: Cloudflare account with API token

Quick Start

Infrastructure Setup (One-time)

Configure core Caddy infrastructure in your host vars:

# host_vars/myserver/main.yml
caddy_tls_enabled: true
caddy_domain: "example.com"
caddy_tls_email: "{{ vault_caddy_tls_email }}"
caddy_dns_provider: "cloudflare"
cloudflare_api_token: "{{ vault_cloudflare_api_token }}"

# host_vars/myserver/vault.yml (encrypted)
vault_caddy_tls_email: "admin@example.com"
vault_cloudflare_api_token: "your-api-token-here"

Service Configuration (Per-Service)

Services deploy configuration files to the sites-enabled directory:

# In your service role (e.g., roles/myapi/tasks/main.yml)
- name: Deploy API service
  # ... your service deployment tasks ...

- name: Deploy Caddy configuration
  template:
    src: myapi.caddy.j2
    dest: "{{ caddy_sites_enabled_dir }}/myapi.caddy"
    owner: root
    group: "{{ caddy_user }}"
    mode: '0644'
  notify: reload caddy
# roles/myapi/templates/myapi.caddy.j2
{{ service_domain }} {
    reverse_proxy {{ service_backend }}
}

Service Role Dependencies

# roles/myapi/meta/main.yml
dependencies:
  - role: caddy

Required Variables

For HTTPS/TLS

Variable Required When Description Example
caddy_tls_enabled Always for HTTPS Enable TLS/HTTPS true
caddy_tls_email HTTPS enabled Email for Let's Encrypt "admin@example.com"
caddy_domain HTTPS enabled Primary domain "example.com"

For DNS Challenge (Wildcard Certificates)

Variable Required When Description Example
caddy_dns_provider DNS challenge DNS provider "cloudflare"
cloudflare_api_token Cloudflare DNS API token for DNS "{{ vault_token }}"

For Service Configuration (Per-Service)

Variable Required Description Example
service_domain Yes Domain for the service "api.example.com"
service_backend Yes Backend address "localhost:8080"

These are typically set in service role defaults or passed as template variables.

Optional Variables

Service Configuration

Variable Default Description
caddy_version "latest" Caddy version to install
caddy_config_file "/etc/caddy/Caddyfile" Main config file path
caddy_sites_enabled_dir "/etc/caddy/sites-enabled" Service config directory
caddy_service_enabled true Enable systemd service
caddy_service_state "started" Service state
caddy_admin_listen "127.0.0.1:2019" Admin API endpoint

Directory Configuration

Variable Default Description
caddy_config_dir "/etc/caddy" Configuration directory
caddy_data_dir "/var/lib/caddy" Data/state directory
caddy_log_dir "/var/log/caddy" Log directory
caddy_web_root "/var/www" Web root directory

Security Configuration

Variable Default Description
caddy_systemd_security true Enable systemd security restrictions
caddy_firewall_ports [80, 443] Ports to open in firewall

Logging Configuration

Variable Default Description
caddy_log_level "INFO" Logging level (ERROR, WARN, INFO, DEBUG)
caddy_log_format "json" Log format (common, json)
caddy_log_credentials false Log credentials in access logs

ACME Configuration

Variable Default Description
caddy_acme_ca Let's Encrypt Prod ACME CA directory URL
caddy_dns_resolvers ["1.1.1.1:53", "1.0.0.1:53"] DNS resolvers
caddy_dns_propagation_timeout 120 DNS propagation timeout (seconds)

Service Configuration Patterns

Simple API Service

# roles/simple-api/templates/simple-api.caddy.j2
api.example.com {
    reverse_proxy localhost:3000
}
# roles/simple-api/tasks/main.yml
- name: Deploy API configuration
  template:
    src: simple-api.caddy.j2
    dest: "{{ caddy_sites_enabled_dir }}/simple-api.caddy"
    owner: root
    group: "{{ caddy_user }}"
    mode: '0644'
  notify: reload caddy

Load Balanced Service

# roles/balanced-api/templates/balanced-api.caddy.j2
api.example.com {
    reverse_proxy localhost:8080 localhost:8081 localhost:8082 {
        lb_policy least_conn
        health_timeout 5s
        health_interval 30s
    }
}

Static File Service

# roles/static-files/templates/static-files.caddy.j2
static.example.com {
    root * /var/www/static
    file_server browse
}

Service Cleanup

# Remove service when decommissioning
- name: Remove service configuration
  file:
    path: "{{ caddy_sites_enabled_dir }}/old-service.caddy"
    state: absent
  notify: reload caddy

Available Handlers

This role provides Ansible handlers for service management:

reload caddy

  • Reloads Caddy configuration without restart
  • Automatically triggered when .caddy files change
  • Picks up new configurations from sites-enabled directory
  • Safe to run multiple times

restart caddy

  • Fully restarts the Caddy service
  • Used for major configuration changes or binary updates
  • May cause brief downtime

reload systemd

  • Reloads systemd daemon configuration
  • Used when service files are modified

Advanced Configuration

Custom Systemd Security

caddy_systemd_security: false  # Disable if you need custom security settings

Staging Environment

# Use Let's Encrypt staging for testing
caddy_acme_ca: "https://acme-staging-v02.api.letsencrypt.org/directory"

Security Features

This role implements production-grade security hardening:

Systemd Security Restrictions

  • NoNewPrivileges: Prevents privilege escalation
  • CapabilityBoundingSet: Limits to CAP_NET_ADMIN and CAP_NET_BIND_SERVICE
  • ProtectSystem=strict: Read-only filesystem protection
  • ProtectKernelLogs/Modules/Tunables: Kernel protection
  • RestrictAddressFamilies: Limits to IPv4, IPv6, and Unix sockets
  • RestrictNamespaces/Realtime/SUIDSGID: Additional restrictions
  • MemoryDenyWriteExecute: Prevents code injection
  • SystemCallFilter=@system-service: Whitelist system calls

File System Security

  • Dedicated caddy user and group
  • Proper directory permissions
  • Read-only configuration binding
  • Isolated temporary files

File Structure

roles/caddy/
├── defaults/main.yml          # Default variables with documentation
├── tasks/main.yml             # Installation and configuration tasks  
├── handlers/main.yml          # Service management handlers
├── templates/
│   ├── Caddyfile.j2          # Main config with import directive
│   ├── caddy.service.j2      # Systemd service for custom builds
│   ├── index.html.j2         # Default welcome page
│   └── systemd-override.conf.j2  # Security hardening overrides
├── meta/main.yml              # Role metadata and dependencies
└── README.md                  # This file

Directory Structure After Deployment

/etc/caddy/
├── Caddyfile                 # Main config with "import sites-enabled/*"
└── sites-enabled/            # Service configurations
    ├── api.caddy            # API service config
    ├── dashboard.caddy      # Dashboard service config  
    └── static.caddy         # Static files config

Example Playbooks

Basic Deployment

- name: Deploy Caddy Web Server
  hosts: webservers
  become: yes
  roles:
    - caddy

Infrastructure with Services

- name: Deploy Infrastructure and Services
  hosts: production
  become: yes
  roles:
    - caddy        # Core web server infrastructure
    - myapi        # API service (deploys .caddy config automatically)
    - mydashboard  # Dashboard service (deploys .caddy config automatically)

Dependencies

This role automatically handles Caddy installation and configuration:

  • Standard Caddy binary for HTTP-only setups
  • Custom build with Cloudflare DNS plugin when DNS challenge is enabled
  • System user and directory creation
  • Systemd service configuration with security hardening
  • Sites-enabled directory structure for service configurations

Compatibility

  • Tested: Arch Linux
  • Should work: CentOS/RHEL 8+, Ubuntu 18.04+, Debian 10+
  • Requires: systemd, curl/wget

Troubleshooting

List Service Configurations

ls -la /etc/caddy/sites-enabled/

Check Configuration Files

cat /etc/caddy/sites-enabled/myservice.caddy

Validate Configuration

caddy validate --config /etc/caddy/Caddyfile

View Caddy Logs

journalctl -u caddy -f

Reload Configuration

systemctl reload caddy

Examples

See other roles associated with the rick-infra project

Additional Documentation

License

This role is part of the rick-infra project infrastructure configuration.