Overview
Module ID:events
The events system enables:
- Event emission - Modules can broadcast events when important actions occur
- Event subscription - Handlers can react to events from specific modules
- Event propagation - Events bubble up the module tree (DOM-like)
- Flow control - Handlers can abort event propagation
- Metadata passing - Events carry data between emitters and handlers
Event Propagation
Events propagate in a DOM-like fashion:- Listen to specific modules
- Listen to entire namespaces
- Catch all events globally
Configuration
Basic Structure
Subscriptions
Subscriptions bind handlers to events.Event name(s) to listen for. If empty or omitted, matches all events.Examples:
["cert_obtained"]- Listen for certificate obtained events["cert_obtained", "cert_renewed"]- Multiple specific events[]- All events
Module ID(s) or namespaces from which to receive events. If empty or omitted, matches all modules.Examples:
["tls"]- All TLS events["http.handlers.file_server"]- Specific module["http.handlers"]- All HTTP handlers[]- All modules
Event handler modules that execute when events match. At least one handler is required.Handlers are invoked in an arbitrary order.
Subscription Examples
Listen to All TLS Events
Listen to Specific Event from Any Module
Listen to All HTTP Handler Events
Catch-All Subscription
Event Handlers
Event handlers implement the action to take when an event occurs.Handler Interface
- Context - Request context with cancellation
- Event - Event data including name, origin, timestamp, and metadata
- Read event data
- Add metadata to the event
- Return
caddy.ErrEventAbortedto stop propagation - Return other errors (logged but don’t stop propagation)
Event Object
Events contain:Unique identifier for this event occurrence
Event name (e.g.,
cert_obtained)When the event was created
The module that emitted the event
Event-specific metadata. Not copied for efficiency; don’t modify in other goroutines after emission.
Set to
caddy.ErrEventAborted if a handler aborted the eventEvent Data in Replacer
Event information is automatically available in the Caddy replacer:{event}- The full event object{event.id}- Event UUID{event.name}- Event name{event.time}- Event timestamp{event.time_unix}- Unix timestamp in milliseconds{event.module}- Origin module ID{event.data}- Full data map{event.data.field_name}- Specific field from event data
Aborting Events
Handlers can abort event propagation by returningcaddy.ErrEventAborted:
- Event propagation stops immediately
- Event is marked as aborted
- Emitter receives the aborted event
- Emitter may choose to adjust program flow
Not all event emitters support aborting. Check module documentation to see if aborting an event affects behavior.
Synchronous Execution
Event handlers are fired synchronously as part of the regular program flow. This means: ✅ Benefits:- Handlers can control program flow
- Handlers can add data back to the origin module
- Predictable execution order (within a subscription level)
- Slow handlers block the emitter
- Handlers should complete quickly
- Long-running tasks should spawn goroutines
Handler Ordering
Handlers should:- Be independent of each other
- Not rely on other handlers’ logic
- Be idempotent when possible
Programmatic Subscriptions
Go code can subscribe to events during provisioning:On() Method
Syntactic sugar for simple subscriptions:eventName- Event to listen for (empty string = all events)handler- Handler to invoke- Listens to events from all modules
Emitting Events
Modules emit events using:Example Emission
Common Event Names
While event names are module-specific, some common patterns:cert_obtained- Certificate was obtainedcert_renewed- Certificate was renewedcert_failed- Certificate operation failedconfig_loaded- Configuration loadedserver_started- Server startedserver_stopped- Server stoppedrequest_received- HTTP request receivedresponse_sent- HTTP response sent
Check specific module documentation for available events.
Use Cases
Certificate Lifecycle Hooks
Logging Events
Webhooks
Metrics Collection
Best Practices
- Keep handlers fast - Spawn goroutines for long-running work
- Handle errors gracefully - Don’t crash on event handling errors
- Be specific - Subscribe to specific events/modules when possible
- Don’t modify shared state - Event data map is shared; don’t mutate it
- Test abort behavior - Ensure aborting events works as expected
- Log appropriately - Use structured logging in handlers
Troubleshooting
Handlers Not Firing
Problem: Event handlers aren’t being invoked Solutions:- Verify event name matches exactly
- Check module ID is correct
- Ensure subscription is in
apps.events.subscriptions - Enable debug logging to see event emissions
Event Storms
Problem: Too many events causing performance issues Solutions:- Be more specific in subscriptions
- Add filtering in handlers
- Avoid emitting events in tight loops
Context Cancellation
Problem:context canceled errors in handlers
Solutions:
- Check for context cancellation in long-running handlers
- Respect context deadlines
- Don’t block indefinitely
Security Considerations
- Handler execution is synchronous - Malicious handlers can block operations
- Event data is shared - Don’t include secrets in event data
- Handlers run with Caddy’s privileges - Be cautious with exec handlers
- No sandboxing - Handlers have full access to Caddy’s internals