A simple Go library for managing Caddy server configurations through its admin API. CaddyGo provides a clean, intuitive interface to add, remove, and configure domains with automatic or custom TLS certificates, following Go best practices and clean architecture principles.
- Domain Management: Add and remove domains with ease
- Automatic TLS: Support for Let's Encrypt and other ACME providers
- Custom Certificates: Use your own TLS certificates
- Security Headers: Configurable security headers (HSTS, X-Frame-Options, etc.)
- Compression: Enable gzip and zstd compression
- Redirects: Support for www/non-www redirects
- Configuration Reload: Force Caddy to reload configurations
- Clean Architecture: Well-organized, maintainable codebase
go get github.com/raghavyuva/caddygo
package main
import (
"log"
"github.com/raghavyuva/caddygo"
)
func main() {
// Create a new client
client := caddygo.NewClient("http://localhost:2019")
// Configure domain options
options := caddygo.DomainOptions{
EnableSecurityHeaders: true,
EnableHSTS: true,
EnableCompression: true,
RedirectMode: "www_to_domain",
}
// Add a domain with automatic TLS
err := client.AddDomainWithAutoTLS("example.com", "localhost", 8080, options)
if err != nil {
log.Fatal(err)
}
// Reload configuration
err = client.Reload()
if err != nil {
log.Fatal(err)
}
log.Println("Domain configured successfully!")
}
CaddyGo is organized into focused, single-responsibility modules:
caddygo/
โโโ types.go # Type definitions and interfaces
โโโ client.go # HTTP client and request handling
โโโ domain.go # Domain management operations
โโโ tls.go # TLS and certificate management
โโโ config.go # Configuration helpers and utilities
โโโ caddygo.go # Package entry point
The main client for interacting with Caddy's admin API.
type Client struct {
BaseURL string // Base URL of Caddy's admin API
HTTPClient *http.Client // HTTP client for API requests
}
Configuration options for domain setup.
type DomainOptions struct {
EnableSecurityHeaders bool // Enable security-related HTTP headers
EnableHSTS bool // Enable HTTP Strict Transport Security
FrameOptions string // X-Frame-Options header value
EnableCompression bool // Enable gzip and zstd compression
RedirectMode string // Redirect behavior: "www_to_domain" or "domain_to_www"
}
Creates a new Caddy API client.
func NewClient(baseURL string) *Client
Parameters:
baseURL
: The base URL of Caddy's admin API (defaults to "http://localhost:2019" if empty)
Returns: A configured Client instance
Adds a domain with automatic TLS configuration using Let's Encrypt or other ACME providers.
func (c *Client) AddDomainWithAutoTLS(domain, target string, targetPort int, options DomainOptions) error
Parameters:
domain
: The domain name to configure (e.g., "example.com")target
: The target hostname or IP addresstargetPort
: The target port numberoptions
: Configuration options for security, compression, and redirects
Returns: Error if configuration fails
Adds a domain with a custom TLS certificate.
func (c *Client) AddDomainWithTLS(domain, target string, targetPort int, certificate, privateKey string, options DomainOptions) error
Parameters:
domain
: The domain name to configuretarget
: The target hostname or IP addresstargetPort
: The target port numbercertificate
: PEM-encoded certificate contentprivateKey
: PEM-encoded private key contentoptions
: Configuration options
Returns: Error if configuration fails
Removes a domain configuration and all associated routes, TLS policies, and automation rules.
func (c *Client) DeleteDomain(domain string) error
Parameters:
domain
: The domain name to remove
Returns: Error if deletion fails
Forces a reload of the Caddy configuration with cache invalidation.
func (c *Client) Reload() error
Returns: Error if reload fails
client := caddygo.NewClient("http://localhost:2019")
options := caddygo.DomainOptions{
EnableSecurityHeaders: true,
EnableHSTS: true,
EnableCompression: true,
}
err := client.AddDomainWithAutoTLS("myapp.com", "localhost", 3000, options)
if err != nil {
log.Fatalf("Failed to configure domain: %v", err)
}
err = client.Reload()
if err != nil {
log.Fatalf("Failed to reload config: %v", err)
}
certificate := `-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAKoK/OvH8T5TMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTkwMzI2MTIzNDU5WhcNMjAwMzI1MTIzNDU5WjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAu1SUU7/3KryQigDTTw8cFDIehIUWqqK9t20Df1StvAhx3Xmddp/Wm3H
-----END CERTIFICATE-----`
privateKey := `-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7VJTUt9Us8cKj
MzEfYyjiWA4R4/M2bS1GB4t7NXp98C3SC6dVMvDuictGeurT8jNbvJZHtCSuYEvu
NMoSfm76oqFvAp8Gy0iz5sxjZmSnXyCdPEovGhLa0VzMaQ8s+CLOyS56YyCFGeJZ
-----END PRIVATE KEY-----`
options := caddygo.DomainOptions{
EnableSecurityHeaders: true,
EnableHSTS: true,
FrameOptions: "SAMEORIGIN",
EnableCompression: true,
}
err := client.AddDomainWithTLS("secure.myapp.com", "localhost", 8443, certificate, privateKey, options)
domains := []struct {
name string
target string
port int
}{
{"api.myapp.com", "localhost", 3001},
{"admin.myapp.com", "localhost", 3002},
{"cdn.myapp.com", "localhost", 3003},
}
for _, d := range domains {
options := caddygo.DomainOptions{
EnableSecurityHeaders: true,
EnableCompression: true,
RedirectMode: "www_to_domain",
}
err := client.AddDomainWithAutoTLS(d.name, d.target, d.port, options)
if err != nil {
log.Printf("Failed to add domain %s: %v", d.name, err)
continue
}
log.Printf("Successfully configured domain: %s", d.name)
}
// Reload all configurations at once
err := client.Reload()
if err != nil {
log.Fatalf("Failed to reload configuration: %v", err)
}
// Remove a specific domain
err := client.DeleteDomain("old.example.com")
if err != nil {
log.Printf("Failed to remove domain: %v", err)
}
// Remove multiple domains
domainsToRemove := []string{"staging.example.com", "test.example.com"}
for _, domain := range domainsToRemove {
err := client.DeleteDomain(domain)
if err != nil {
log.Printf("Failed to remove %s: %v", domain, err)
}
}
// Reload to apply changes
err = client.Reload()
options := caddygo.DomainOptions{
EnableSecurityHeaders: true,
EnableHSTS: true,
FrameOptions: "DENY", // Options: "DENY", "SAMEORIGIN", "ALLOW-FROM"
}
options := caddygo.DomainOptions{
EnableCompression: true, // Enables both gzip and zstd compression
}
options := caddygo.DomainOptions{
RedirectMode: "www_to_domain", // Options: "www_to_domain", "domain_to_www"
}
CaddyGo provides descriptive errors for various failure scenarios:
err := client.AddDomainWithAutoTLS("example.com", "localhost", 8080, options)
if err != nil {
switch {
case strings.Contains(err.Error(), "failed to get current config"):
log.Println("Unable to connect to Caddy admin API")
case strings.Contains(err.Error(), "failed to marshal"):
log.Println("Configuration serialization error")
case strings.Contains(err.Error(), "failed to update config"):
log.Println("Failed to update Caddy configuration")
default:
log.Printf("Unexpected error: %v", err)
}
return
}
- Go: 1.21 or later
- Caddy: v2 with admin API enabled
- Network: Access to Caddy's admin endpoint
Run the test suite:
go test -v
Run with coverage:
go test -cover
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Built with Caddy - the modern web server
- Following Go best practices and clean architecture principles
- Inspired by the need for a clean, maintainable Caddy management library
- Reference - https://github.com/migetapp/caddy-api-client
Made with โค๏ธ for the Go community