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_ADMINandCAP_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
caddyuser 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
- Caddy Service Configuration Guide - Comprehensive usage guide
- Caddy Official Documentation - Official Caddy documentation
License
This role is part of the rick-infra project infrastructure configuration.